任何一门语言的学习都可以从下面几个方面入手:
数据类型
操作符
表达式
语句
流程控制语句
函数
对象
内置数据类型的学习
内置函数的学习
内置对象的学习
数据库的学习
功能的实现
错误,调试和运行
javascript也不例外,我将根据自己总结的这个流程来学习任何一门语言。
javascript的数据类型
[if !supportLists]1. [endif]1.Number 类型
[if !supportLists]2. [endif]2.String 类型
[if !supportLists]3. [endif]3.Object 类型
[if !supportLists]4. [endif]4.Boolean 类型
[if !supportLists]5. [endif]5.Null 类型
[if !supportLists]6. [endif]6.Undefined 类型
Var
num = 100 ; tpye of 是number类型
Var
str = ‘闫岐’; type of是string 类型
Var un; tpye of是undefined 类型
Var box = true; type of 是 boolean类型
Var base = {}; type of 是object 类型
Var box = null; type of 是object类型
NULL 和Undefined的区别
Undefined 是什么意思?当你
Var test;
声明了一个值的时候,却没有给它赋值,那这个就是undefined
null是什么?当你
Var test = null
为它赋了一个NULL值的时候,你是希望它以后是为了保存某个值的,只是暂时把它设置为“空”,这样,当你以后在用
If(test !== null){
//说明已经赋值了,可以进行操作了
}
检查的时候,就可以进行操作了。
这种情况应用于当你需要创建一个对象,却不知道这个对象应该初始化为那个值的时候使用
undefined: 代表一切未知的事物,啥都没有,无法想象,代码也就更无法去处理了。null: 有那么一个概念,但没有东西。无中似有,有中还无。虽难以想象,但已经可以用代码来处理了。
boolean类型
String 任何非空字符为真,空字符为假
Number 任何不等于0的为真,为0和为NaN的为假
Object 任何对象都为真,NULL的对象为假
Undefined 为false
Boolean () 用来将任何类型的值转化为boolean类型
空的字符串,数值0,null,undefined 都是假,其它所有都为真
Number类型
NaN是一个非数值的值,用来表示一个本来要返回数值的操作数未返回数值的情况
Var box = 0/0 NaN
Var box = 12/0
Var box =12/0*0 NaN
任何与NAN相运算的结果都是NAN,NAN不等于NAN。
Number() 可以将任何数据类型转化为整数。
记住一点,null和undefined都不等于0,他们不是一个概念,但是它会默认转为0和NaN,也就是不能进行比较运算
专门的字符串转换整数函数:
parseInt()整数类型的转换;
Alert(parseInt(‘456Lee’)); //返回456整数部分
Alert(parseInt(‘Lee456Lee’)); //NaN,如果第一个不是数值,只能返回NAN
Alert(parseInt(‘12Lee456’)); // 12,从第一个数值开始,到最后一个连续数值结束
Alert(parseInt(‘56.12)); // 56,小数点不是整数,会被去掉
Alert(parseInt(‘*’)); //不是数字,返回NAN
//它提供了第二个参数来转换进制
parseFloat()是用来浮点数的转换的
Alert(parseFlaot(“ 123LEE ”)); 123,去掉LEE的部分
Alert(parseFlaot(“ oxA ”)); 不认识16进制
Alert(parseFlaot(“ 123.4.5 ”)); 只认一个小数点
Alert(parseFlaot(“0123.400 ”)); 123.4去掉前后导
Alert(parseFlaot(“ 1.234e7 ”)); 12340000转化为普通数值
String 类型
Var box = ‘yanqi’;
Var box = “yanqi”;
单双引号在JS里头没有任何区别,但必须成对使用,不可以穿插使用。
同样,string()方法用来将任何类型的值转化为字符串
toString 方法用来将数值转化为字符串
Var box = 10;
box.toString(8); //八进制
box.toString(10); //十进制
box.toString(16); //十六进制
记住,最常用的转化主要集中在数字和字符串的转化,数字转化为字符串tostring(),字符串转化为数字parseInt()
Object 类型
Var box = new Object(); //通过new来创建对象
----------------------------------------------------------数据类型结束-----------------------------------------------
运算符
算数运算符
加减乘除模(取余)
如果在算数运算的值不是数值,那么JS将会使用number()转型函数将其转换为数值,属于隐式转换
基本所有的隐式类型转化都发生在运算符中
关系运算符
小于< 大于> 等于== 小于等于<= 大于等于>= 不等于!=全等于=== 全部等于!=== (除了值相等,数据的类型也必须相等)
规则:
[if !supportLists]1. [endif]1.两个操作数都是数值,则数值比较 3>2
[if !supportLists]2. [endif]2.两个操作数,都是字符串,则比较两个字符串对应的ASC编码值 ‘2’> ‘4’
[if !supportLists]3. [endif]3.两个操作数,有一个是数值,则将另外一个转换为数值,再进行数值比较 ‘2’> 4
[if !supportLists]4. [endif]4.两个操作数,有一个是对象,则先调用valueof()或者toString()方法,在用结果比较 1 <对象
特殊类型的比较:
boolean的true转化为1,false转化为0
字符串+数字 = 字符串
如果跟NaN进行运算,始终结果都是NaN,等于或者不等于返回false,true .并且NaN和自身不等
全等和全不等的判断上,值和类型都相等,才会返回true,否则返回flase
如果两个都是对象,则比较它们是否是同一对象,如果指向同一对象,则返回true,否则返回false
Undefined == 0 //结果不成立,false
Null == 0 //结果不成立,false
null会自动转换为0,undefined 自动转化为NaN,但在比较运算中,null和undefined没有自动转换。
逻辑运算符
[if !supportLists]1. [endif]1.逻辑与&&
规则:
①第一个操作数是对象,则返回第二个操作数 var box=对象&&(5>4),true,返回第二个操作数
②第二个操作数是对象,则第一个操作数返回true,才返回第二个操作数,否则返回false
举例:
(5
> 3)&& 对象,因为第一个操作数成立,则返回对象
(5
> 6)&& 对象,因为第一个操作数不成立,则返回false
③有一个操作数是null, 则返回null
④有一个操作数是undefined则返回undefined
⑤它属于短路操作符,如果第一个操作符是false,则不管第二个操作符,始终返回false.
这个情况比较复杂,看实际情况再来看
[if !supportLists]1. [endif]2.逻辑或||
规则:
①第一个是对象,则返回第一个var box = 对象 || (5>3) ,返回对象,无论第二个怎样
②第一个操作数求值结果为false
,则返回第二个操作数 ,否则返回true . Var box = (5>3) || 对象,返回true
③两个操作数都是对象,则返回第一个操作数 var box = 对象1 || 对象2,返回对象1,如果不存在就返回对象2.
④var box = null|| null // null
⑤var box = NaN|| NaN //NaN
⑥var box =undefined || undefined //undefined
⑦同样是短路操作符,当第一操作数的求值结果为true ,就不会对第二操作数求值了
[if !supportLists]1. [endif]3.逻辑非
先将这个值转换为布尔型,然后取反
Var box = !(5>4) false
Var box = !{} false
Var box =!’’ ture
Var box =!’lee’ false
Var box =!0 true
Var box = !8 false
Var box = !null true
Var box = !NaN true
Var box = !undefined ture //这三个本身就是false,如果转换为布尔型,相反就是true
-------------------------------------运算符结束---------------------------------------------------------------------
对象和数组
数组表示有序数据的集合,而对象表示无序数据的集合。如果数据的顺序很重要,就用数组,否则就用对象。
JS中的数组无法使用除了数字作为下标的关联数组的方式,PHP则可以创建关联数组,在JS中,关联数组相当于JS的对象
如何创建对象
[if !supportLists]1. [endif]1.使用NEW运算符
Var box = new Object();
Box.age = 25;
Box name = “闫岐”; //创建对象的属性
[if !supportLists]1. [endif]2.NEW关键字可以省略
Var box = Object ();
[if !supportLists]1. [endif]3.使用字面量的方式
Var box = {
Name : ‘闫岐’,
Age : 25
};
[if !supportLists]1. [endif]4.属性字段也可以用字符串形式
Var box = {
‘name’:’闫岐’,
‘age’:25
};
[if !supportLists]1. [endif]5.使用字面量及传统复制方式
Var box = {};
Box.name = ‘闫岐’;
Box.age = 25;
属性除了.输出之外,也可以用数组形式[]输出,比如box.name 或者box[‘name’]
是不是类似于PHP中的关联数组的方式?就是如此/
[if !supportLists]1. [endif]6.给对象创建方法
Var box = {
Run : function(){
//我的方法
}
}
[if !supportLists]1. [endif]7.删除对象的属性
Delete box.name; //删除了name 属性
数组
[if !supportLists]1. [endif]1.NEW运算符创建数组
Var box = new Array ();
Var box = new Array(10);
Var box = new Array(‘闫岐’,29,’程序员’)
[if !supportLists]1. [endif]2.以上的三种方法可以省略new关键字
Var box = Array ();
Var box = Array(10);
Var box = Array(‘闫岐’,29,’程序员’)
[if !supportLists]1. [endif]3.使用字面量的方式创建数组
Var box = [];
Var box = [‘闫岐’,25,’程序员’];
[if !supportLists]1. [endif]4.数组是使用下标[]来读取每个元素的
Box[1] = ‘’;
Box[2] = ‘’;
[if !supportLists]1. [endif]5.可以使用length来获取数组的长度
Box.length
[if !supportLists]1. [endif]6.创建一个复杂的数组
Var box = {
{
Name:’闫岐’,
Age:25,
},
[‘马云’,29,‘淘宝’],
‘计算机编程’,
25+25,
New Array(1,2,3)
}
证明了数组的每个元素可以是任何类型的元素
感觉JSON和JS的不同在于写法确实不同,JSON必须用双引号,JSON只是数据格式
数组的方法
toString();把数组转换为字符串,并返回结果
valueOf();
toLocaleString();
Join();把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
Push()和pop() 用栈的方式来操作数组,后进先出
Push()和shift()列队的方式来操作数组,跟栈是相反的,先进先出
Unshift()用来在前端添加一个元素,shift用于在前端删除一个元素
Reverse()逆向排序
Sort()正向排序
Concat()方法可以给予当前数组创建一个新数组
Slice()方法可以给予当前数组获取指定区域元素兵创建一个新数组
Splice()主要用途是向数组的中部插入元素
----------------------------------------------------------------对象结束----------------------------------------------
日期和时间
Var box = new Date(); //创建一个日期时间 对象,构造方法可以传参数,来指定时间
//返回的结果根据浏览器的不同而不同,一般不会直接使用
提供了两个方法:
Date.parse()
//必须接受一个表示时间的字符串参数,然后根据这个时间返回相应的毫秒数
格式:
‘月/日/年’6/13/2011
‘英文月名 日,年’May 25,2004
‘英文星期几 英文月名 日 年 时:分:秒 时区’Tue May 25 200400:00:00 GMT-070
Alert(date.parse(‘6/13/2011’)) //1307894400000
如果Date.parse()没有传入或者不是标准的日期格式,将会返回NaN
Alert(Date.parse()); //NaN
如果想输出指定的日期,可以把Date.parse()传入到Date的构造方法中
Var box = new Date(Date.parse(‘6/13/2011’)); //Mon Jun 13 2011 00:00:00 GMT+0800
Var box = new Date(‘6/13/2011’); //默认自动后台调用Date.parse();
PS:如果日期写错了,浏览器会+1或者-1这种形式来保证日期格式的正确,具体怎么做,还是看浏览器的
Date.UTC()
//同样的返回毫秒数
格式:
年份,基于0的月份,月份中的那一天,小时数,分钟,秒以及毫秒
Alert(Date.UTC(2011,11));//13226976000000 只有前两个是必须的
如果Date.UTC()参数错误,同样会返回负值或者NaN
Alert(Date.UTC()); //负值或者NaN
如果要输出指定的日期,就直接放入Date构造方法中去:
Var box = newDate(Date.UTC(2011,11,5,15,13,16));
省略Date.UTC的话,返回的是本地时间,否则返回的是东一区的时间
总结:
Var box = new Date(‘写入parse的格式’|写入UTC的格式),返回自己的时间结果。
得到日期后呢,我们有一些专门用于将日期格式化为字符串的方法
Var box = new Date(‘parse格式’|UTC格式);//已经得到了我们的结果
box.toDateString() 以特定的格式显示星期几、月、日、年
box.toTimeString() 以特定的格式显示时分秒和时区
box.toLocalDateString() 以特定的地区格式显示星期几、月、日、年
box.toLocalTimeString() 以特定的地区格式显示时分秒和时区
box.toUTCString() 以特定的格式显示完整的UTC日期
组件方法
UTC时间,国际统一标准时间 = 当前的时间 +8个小时
box.getTime() //获取日期的毫秒数 //获取当前时间(从1970.1.1开始的毫秒数)
box.setTime() //以毫秒数设置日期
box.getFullYear() 获取四位年份 //获取完整的年份(4位,1970-????)
box.setFullYear() 设置四位年份
box.getMonth() 获取月份 //获取当前月份(0-11,0代表1月)
box.setMonth() 设置月份
box.getDate() 获取日期 //获取当前日(1-31)
box.setDate() 设置日期
box.getDay() 返回星期几 //获取当前星期X(0-6,0代表星期天)
box.setDay() 设置星期几
box.getHours() 返回时 //获取当前小时数(0-23)
box.setHours() 设置时
box.getMinutes() 返回分钟 //获取当前分钟数(0-59)
box.setMinutes() 设置分钟
box.getSeconds() 返回秒数 //获取当前秒数(0-59)
box.setSeconds() 设置秒数
box.getMilliseconds() 返回毫秒数 //获取当前毫秒数(0-999)
box.setMilliseconds() 设置毫秒数
box.getTimezoneOffset() 返回本地时间和UTC时间相差的分钟数
以上方法,除了getTimezoneOffset之外,都有UTC方法,例如setDate 就会有setUTCDate方法
正则表达式
1.new创建
Var box = new RegExp(‘box’,’ig’);
i 忽略大小写
g 全局匹配
m 多行匹配 记住它只对^和$模式起作用
[if !supportLists]2. [endif]2.字面量的方式创建
Var box = /box/ig;
[if !supportLists]2. [endif]3.方法
Test 正则.字符串, 返回true或者false ,看看字符串是否符合正则
Exec 正则.字符串,返回符合匹配的字符串,以()为单位,0返回完整的,从1-N返回括号内的元素——也就是返回字符串的子字符串。
var pattern = /Box/ig;var str = 'This is a
Box!That is a Box ';var box = pattern.exec(str);alert(box[0]);
你可以看到,它box[0]应该是它的完整匹配,如果有括号,那么[1]应该返回它第一个括号内的内容。
你要知道,全局的概念是什么?尽可能多的匹配,如果你给exec 加入了/g,它仍然是返回第一个匹配的结果,但是,你再执行以下,就可以获取第二个匹配结果了。这跟它的下标【】返回子元素并不冲突,记得,它的下标永远返回的是字符串中的子匹配。
var regx=/user\d/;
var str=“user18dsdfuser2dsfsd”;
var
rs=regx.exec(str);//此时rs的值为{user1}
var
rs2=regx.exec(str);//此时rs的值依然为{user1}
如果regx=/user\d/g;则rs的值为{user1},rs2的值为{user2}
[if !supportLists]2. [endif]4.字符串的正则方法
Match(正则)
Var pattern = /Box/ig;
Var str = ‘This is a Box!That is a Box’;
Alert(str.match(pattern));
//获取匹配的数组,如果不开全局,只匹配第一个Box
//注意它跟exec之间的区别,exec即便加了全局,也必须通过再次调用,match一次返回所有的结果。
如果 regexp 没有标志 g,那么 match() 方法就只能在stringObject 中执行一次匹配。如果没有找到任何匹配的文本, match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,input 属性声明的是对 stringObject 的引用。
如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。若没有找到任何匹配的子串,则返回null。如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。
注意:在全局检索模式下,match()即不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果您需要这些全局检索的信息,可以使用RegExp.exec()。
开全局和不开全局差别很大,开了全局只能获得所有的结果,不开全局,能得到子表达式的信息
//个人感觉是统计字符串中某个子串出现的次数
Replace(正则,替换的内容)
Var pattern = /Box/i;
Var str = ‘This is a Box!That is a Box’;
Alert(str.replace(pattern,’Tom’));
//如果不加全局,只替换第一个,加了全局,所有匹配都会被替换
//将字符串某些字符进行替换
Search(正则) 比较简单,正确的话返回它的位置,不正确的话返回-1,只要找到一个就返回了不需要G,全局匹配。
//查找字符串中的某个元素的位置
Split(正则)
Var pattern = /!/i;
Var str = ‘This is a Box!That is a Box’;
Alert(str.split(pattern));
//根据某个字符来拆分,将字符串拆分成数组。
重复的字符串\1引用下即可,例如aa则\w\1中\1匹配到的就是\w匹配的那个字符的重复项
函数
[if !supportLists]1. [endif]1.function box (num1,num2){
Return num1 + num2
}
//普通函数声明
[if !supportLists]1. [endif]2.var box = function(num1,num2){
Return num1 + num2
}
//变量初始化的方法
[if !supportLists]1. [endif]3.函数因为是对象,所以,它可以像传值一样传递到另外一个函数内
Function box (sum,num){
Return sum+num;
}
Function sum(num){
Return num+10;
}
Var result = box(sum(10),10);
//结果为30
这一切之所以这么复杂,或者看起来这么变化莫测,本身就是因为函数也可以作为参数被传递,我们是知道的,在C,C++中,参数可以是简单的数据类型,整数,浮点,字符串,也可以是类(类传递的是一个指针),但我是没见过能传递函数这种参数的。
但在JS中,函数可以拥有方法和属性,因为本身它就是个对象,函数可以作为参数传递,同样是因为它是个对象。
--------------------------------------------------------------函数结束------------------------------------------------
突然明白了正则表达式的惰性模式是个什么意思了? 符号?:是惰性匹配
一般的? * +都是零到一,零到多,一到多,一旦开启了惰性模式,那么,零到一的情况下,尽量匹配零,零到多的情况,尽量匹配零,一到多的情况,尽量匹配一。只要能满足条件就可以停止了。
上头是针对匹配次数的
.
变量以及作用域
之前的变量具备块状作用域,在(){}里头的变量只存在(){}里头,超过了它的作用域,该变量不可见。
JS的变量分为基本类型和引用类型,它们分别放入栈和堆之中,基本类型的在栈中保存实际的数值,引用类型在堆中,该变量实际保存的真是一个指针。(因为它会变大变小)
按我的理解
var num = 10 放在栈中var box = new object() 放在堆中
将一个值赋给变量的时候,解析器必须确定这个值是基本类型,还是引用类型
基本类型:undefinednull boolean number string
///他们的值保存在栈空间,固定大小,不会变大变小
引用类型: 对象object
//他们的值放在堆中
对象保存的是一个地址,先从栈中读取地址,然后从地址找到堆中的值
堆和栈的区别可以用如下的比喻来看出:使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。//总结:栈由系统自动分类配,而堆由程序员来创建
动态属性
基本的类型是无法添加属性的,var
box =’string’; box.age=25;是不可以的
复制变量值
基本类型复制的是值本身,而引用类型复制的是地址
可能它本身储存的就是个地址,我们是从地址中找到值的。
所以,针对对象的复制,可能只是复制一个地址。
Var box =’lee’
Var box2 =box ;
相互独立,互不影响
Var box = new object();
Box.name=’lee’;
Var box2 = box;
引用类型
如果你改变任何一个BOX的name值,它都会变化。
在引用类型中,BOX2其实就是BOX ,因为他们指向同一个对象
这里我们会考虑如何才能完成复制功能呢?
我自己感觉,构造函数已经帮我们完成了复制,批量生产对象的一个功能!
传递参数
分为传递基本型,传递引用类型
参数都是按照值才传递的
这里,我应该很清楚了,函数传参都是复制,默认情况下,它不会修改原本数据的值。
//函数传递参数,都是默认进行复制的,它不会修改原本数据的值var a = 10;var b = 20;function box(num1,num2){ return { a: a + 10, b: b + 10, };}console.log(box(10,20));console.log(a);console.log(b);
JS没有按引用传递,PHP却可以,C++也可以。
//按引用来看看
Fucntion box (obj){
Obj.name=’lee’;
}
Var obj = new object();
Obj.name = ‘kkk’;
Box(obj);
Arlert(obj.name);
//还是lee,它不是按引用传递,只是传递引用类型,跟传递基本类型一样是复制,只不过它传递的是一个地址,基本类型传递的是值,所以,基本类型的操作不会修改本身,而引用类型的操作就会修改本身了。
检测类型
Var box = [1,2,3]; //数组
Var box2 ={}; //对象
Var box3 = /gg/; //正则
上述都是对象,那我们可能想要知道他们是什么类型的对象?
Box instanceof Array
Box instanceof Object
Box instanceof RegExp
Box instanceof Fucntion
Box instanceof 某个构造函数
不能用instanceof来检查基本类型
作用域
所有不在函数内的变量和函数都是window对象下的,window是最大的,也就是全局作用域
JS仅仅具备函数内和函数外两部分的作用域
Var box = ‘blue’;
Function getBox(){
Var box = ‘red’;
Returnbox;
}
Alert(getBox());
基本的包装类型
为了便于操作基本类型,JS提供了三个特殊的引用类型:
[if !supportLists]1. [endif]1.Boolean
[if !supportLists]2. [endif]2.Number
[if !supportLists]3. [endif]3.String
作为基本类型,这三个类型可以调用系统本身自带的属性和方法,而如果你想要为他们单独创建一个属性和方法的时候,是不起作用的。
Var box = ‘Mr.lee’;
Box.name =’lee’;
Box.age = function(){
Renturn 100;
}
Alert(box.name);
Alert(box.age());
那么,你应该怎么办呢?
Var box = new String(‘Mr.lee’);
Box.name =’lee’;
Box.age = function(){
Renturn 100;
}
Alert(box.name);
Alert(box.age());
这样操作,确实有点混淆不清,尽量不这么做。
Number内置的方法
toString() 转换为字符串
toLocaleString() 根据本地数字格式转换为字符串
toFixed() 将数字保留小数点后指定位数并转化为字符串
toExponential() 将数字以指数形式表示,保留小数点后指定位数并转化为字符串
toPrecision() 指数形式或点形式表述数,保留小数点后面指定位数并转化为字符串
String 内置的方法
charAt(位置) 返回指定位置的字符
charCodeAt(位置) 返回指定位置的字符,以Unicode编码的形式
Concat(str1...str2) 将字符串参数串联到调用该方法的字符串中
Slice(n,m) 返回字符串N到M之间位置的字符串
Substring(n,m) 同上
Substr(n.m) 返回字符串N开始的M个字符串
//如果只有一个参数,slice ,substring,substr 功能相同
//他们负数的情况不同
Box.slice(-2) 字符串的总长度-2 ,从这个位置开始,到最后
Box.substring(-2) 返回全部字符串
Box.substr(-2) 字符串的总长度-2 ,从这个位置开始,到最后
Box.slice(2,-1) 从2开始到 字符串的总长度-1
Box.slice(-2,-1) 字符串长度-2 到字符串长度-1
Box.substr(2,-1) 第二个参数为负,直接转0
Indexof(str,n) 从N开始搜索的第一个str,并将搜索的索引值返回
lastIndexOf(str,n) 从N开始搜索的最后一个str,并将搜索的索引值返回 ,从N开始向前搜索
toLowerCase() 全部转化为小写
ToUpperCase() 全部转化为大写
Splice(位置,删除多少,要替换的值) ,返回被删除的元素数组
Splice(2,0,’yanqi’)在数组2下标的位置,插入yanqi这个值
Splice(2,1,’yanqi’)在数组2下标的位置,删除这个值,然后用yanqi来替代
Splice(2,3,’yanqi’)在数组2下标的位置,开始删除三个值,然后用yanqi来填补
-----------------------------------------------------------------包装类型结束----------------------------------------
面对对象与原型
工厂模式
我们都知道引用类型赋值不会产生一个相同的数据,将一个对象赋值给另外一个对象的时候,这两个对象实际指向的是相同的对象地址。
Var box = {};
Box.name =’lee’;
Box.age = 25;
Var box2 = box;
//如果你去修改了box2当中的数据,box也会相应改变
Box.name=’jack’;
Box.age = 100;
Alert(box.name);
Alert(box.age);
为了解决这个问题,我们提供了工厂模式
Function createObject(name,age){
Var obj = new Object();
Obj.name = name;
Obj.age = age;
Obj.run = function (){
Return this.name +this.age +’运行中...’;
}
Return obj;
}
Var box1 = createObject(‘lee’,100);
Var box2 = createObject(‘jack’,200);
Alert(box1.run());
Aleft(box2.run());
构造模式
//这个是个构造函数
Function box(name,age){
This.name = name;
This.age = age;
This.run = fucntion(){
Return this.name + this.age + ‘运行中’;
}
}
Var box1 = new box(‘lee’,100); //new 构造函数
Var box2 = new box(‘jack’,200);
Alert(box1.run());
Alert(box2.run());
感觉构造函数跟普通函数很类似,有两点区别:
[if !supportLists]1. [endif]1.属性和方法用this 关键字
[if !supportLists]2. [endif]2.使用new来实例化
C++中,属性都是各自的,方法可能是公用的,但在JS里头,恐怕属性和方法都是私人的,另外对于this,在C++中,this代表的是对象的地址,它是默认被传递到对象方法中去的,因为方法是公共的,属性是私有的,我必须知道这个方法要处理的数据是哪个?是A,是B,还是C,所以,我会把对象A,对象B,对象C的地址传递到对象的方法中去。
实际上,C++中,对象的大小就是属性的大小,方法是另外存放的。
但在JS当中,你知道了,它的属性和方法都是单独的,也就是说它不需要传递指针给方法
它是怎么做的呢?
这是工厂模式和构造模式想结合理解的一个实例
Function box(name,age){
Var this = {}; //创建一个新的对象,叫this
This.name = name;
This.age = age;
This.run = fucntion(){
Return this.name + this.age + ‘运行中’;
}
//给this 赋值
Return this;
//返回这个this对象
}
创建和返回都是默认进行的,那么在JS中,this就是个新的对象,它就是简化了工厂模式,原理都是一样的。
记住,它们是模仿定义类 —— 实例化类,这种模式的。
实际上,他们是 定义一个构造函数 —— new 这个函数
原型
前面说到过,在JS里头,它的属性和方法都是单独拥有的,那么,它就有一个非常明显的缺点,那就是它无法共享属性和方法。
比如,在DOG对象的构造函数中,设置一个实例对象的共有属性species。
functionDOG(name){
this.name =name;
this.species
= ‘犬科’;
}
然后,生成两个实例对象:
var dogA =
new DOG(‘大毛’);
var dogB =
new DOG(‘二毛’);
这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。
dogA.species
= ‘猫科’;
alert(dogB.species);
// 显示”犬科”,不受dogA的影响
每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。
这个属性包含一个对象(以下简称”prototype对象”),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
还是以DOG构造函数为例,现在用prototype属性进行改写:
functionDOG(name){
this.name =name;
}
DOG.prototype
= { species : ‘犬科’ };var dogA = new DOG(‘大毛’);
var dogB =
new DOG(‘二毛’);alert(dogA.species); // 犬科
alert(dogB.species);
// 犬科
现在,species属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。
DOG.prototype.species
= ‘猫科’; alert(dogA.species); // 猫科
alert(dogB.species);
// 猫科
五、总结
由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像”继承”了prototype对象一样。
实际我是这么理解的,_proto_指向了原型对象,而原型对象中的constructor指向了构造函数,两个家伙联系在了 一起. 构造函数找原型,原型找构造函数
实际的访问测试:
Box1.prototype
Box2.prototype
//访问不到
Box1._proto_
Box2._proto_
//IE下不行
Box1.constructor
Box2.constructor
//也是什么也访问不到
Box.prototype 却可以访问的到,因为你访问的是构造函数的原型属性,同时你也可以修改它
//box.prototype.constructor =box
//这里,一定要看清楚了,原型中的constructor指向的就是构造函数,谁的原型指向谁,毕竟原型是一个共享的对象,单独放置,一个地址共享。
//box1.constructor == boxbox2.constructor == box
//box1 instanceof box box2
instanceof box 也是可以的。
//box1 instanceof object box2
instanceof object 同样是对的
你是直接访问的,而不是通过_proto_或者constructor来带参数访问的,你要明白,只不过是通过这两个属性,我们获得了访问的权限,一定要明白这一点。
原型模式的执行流程:
[if !supportLists]1. [endif]1.先查找构造函数实例的属性方法,如果有立即返回
[if !supportLists]2. [endif]2.如果构造函数实例没有,则去它的原型中找,如果有,就返回
原型的修改方式:
[if !supportLists]1. [endif]1.构造函数.prototype来修改属性和方法
[if !supportLists]2. [endif]2.实例对象.属性或者方法来修改 (这个应该是错误的)
构造函数原型 isPrototypeOf()
这个方法用来判断,某个构造函数原型和某个实例之间的关系。
感觉就是判断它们是否共享了同一原型
alert(Cat.prototype.isPrototypeOf(cat1));
//true
alert(Cat.prototype.isPrototypeOf(cat2));
//true
实例对象hasOwnProperty()
每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
alert(cat1.hasOwnProperty("name"));
// true
alert(cat1.hasOwnProperty("type"));// false
Object.getPrototypeOf()可以获取指定对象的原型
实例对象in运算符
in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
alert("name"
in cat1); // true
alert("type"
in cat1); // true
in运算符还可以用来遍历某个对象的所有属性。
for(var prop in cat1) {
alert("cat1["+prop+"]="+cat1[prop]);
原型的写法——字面量的形式
//字面量的形式
Function box(){};
//创建了一个新的对象赋值给了box.prototype
Box.prototype = {
Constructor :box //强制
Name:’lee’,
Age:100
Run:function(){
Return this.name+this.age+’运行中’;
}
}
//普通的构造方式
Function box(){};
Box.prototype.name=lee;
Box.prototype.age=100;
Box.prototype.run=function(){
Return this.name+tis.age+’运行中’;
}
两种有什么区别呢?
Var box1 = new box();
当你实例化的时候,普通的构造方式,box1.constructor == box
字面量的方式,box1.constructor== object
所以,尽量不要用字面量的形式来创建,也可以强制加上一句话
Constructor :box
原型的缺点
[if !supportLists]1. [endif]1.原型中的属性(也就是数据)不能传参进行初始化,是在写原型数据的时候就已经确定的值
[if !supportLists]2. [endif]2.如果你修改了原型中的属性和方法,那么,第二个实例化对象的时候,属性和方法是修改后的,也就是它们共享这个值的操作结果。
如何正确书写原型
[if !supportLists]1. [endif]1.把不需要改变的属性和方法放入原型中,其它的放入实例中去
Function 构造函数(){
This.name = ‘jack’;
This.age = 100;
}
构造函数.prototype={
Constructor:box
Run:function(){
//代码
}
}
//这种方法叫做构造+原型模式
你可以学习C++,将属性放入实例中,将方法放入原型中,这样就越来越像C++了
多次实例化只有一个地址
[if !supportLists]1. [endif]2.减少原型的初始化次数,并把这一切封装到一起
Function 构造函数(name,age){
This.name = name;
This.age = age;
If(typeof this.run != function){
构造函数.prototype.run = function(){}
}
}
//多次实例化,只执行一次。
继承
子构造函数.prototype = new 父构造函数()不能直接使用
比如,现在有一个"动物"对象的构造函数,
functionAnimal(){
this.species = "动物";
}
还有一个"猫"对象的构造函数,
functionCat(name,color){
this.name = name;
this.color = color;
}
怎样才能使"猫"继承"动物"呢?
更常见的做法,则是使用prototype属性。
如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
Cat.prototype = new
Animal();
Cat.prototype.constructor
= Cat;
var cat1 = new
Cat("大毛","黄色");
alert(cat1.species); // 动物
代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。
Cat.prototype = new
Animal();
它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢?
Cat.prototype.constructor
= Cat;
原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。也就是说,Cat.prototype 这个对象的constructor属性,是指向Cat的。
我们在前一步已经删除了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,所以我们必须手动加上去,否则后面的"继承链"会出问题。这就是第二行的意思。
总之,这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,
o.prototype = {};
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
这种方法只是将基类构造数据 + 原型数据 扔到 派生类的原型中去了,首先,数据不应该在原型中
//比如这里有两个构造函数,第二个构造函数要继承第一个构造函数的内容function animal(){ this.species = '动物';}animal.prototype.run = function(){ alert('所有的动物都会跑');}function cat(name,color){ this.name = name; this.color = color;}//怎么让猫继承动物呢?//引导学生使用prototype属性//将一个基类的实例扔进派生类的原型中去cat.prototype = new animal();cat.prototype.constructor= cat;var cat1 = new cat('大毛','黄色');console.dir(cat1);//对于这个方法,首先,先在cat1自身属性当中去寻找,如果找不到,进去原型链去找,还找不到就去animal中原型链找,最后找到Object对象//cat1.run();//缺点:基类的构造数据和原型数据都放在了派生类的原型链中,基类的构造数据成了公共的了,修改一个等于修改全部.
基类.call(派生类,参数)不能直接使用
///比如这里有两个构造函数,第二个构造函数要继承第一个构造函数的内容function animal(){ this.species = '动物';}animal.prototype.run = function(){ alert('所有的动物都会跑');}function cat(name,color){ //通过call改变了animal中的this指向,变成了cat animal.call(this); this.name = name; this.color = color;}var cat1 = new cat('大毛','黄色');//首先先在自身找,自身找不到,放到原型链中去找,最终找到Object对象console.dir(cat1);//alert(cat1.species);//原型链当中的方法并没有被继承过来//alert(cat1.run());
在派生类中,基类.call(this),就相当于在派生类中添加了this.name=name,this.age=age
它所有的数据和方法都被复制了一遍,只是this的指向是派生类.
但是,基类原型链里面的数据还是没有被继承过来
子构造函数.prototype
= new 父构造函数()
基类.call(派生类,参数) 一起用(不能直接用)
//比如这里有两个构造函数,第二个构造函数要继承第一个构造函数的内容function animal(){ this.species = '动物';}animal.prototype.run = function(){ alert('所有的动物都会跑');}function cat(name,color){ //通过call改变了animal中的this指向,变成了cat animal.call(this); this.name = name; this.color = color;}//基类的原型数据还是没有进去cat.prototype = new animal();cat.prototype.constructor= cat;var cat1 = new cat('大毛','黄色');console.log(cat1);
这种方法的话,原型数据依然是没有进来,并且,派生类的构造函数和原型中都会有基类构造函数里面的数据,造成了重复和浪费.
子类.prototype
= 父类.prototype(不能直接使用)
Function inherit (c,p) {
c.prototype = p.prototype ;
}
共享了同一个原型,先说说最致命的缺点吧
1. 首先,你需要将需要继承的属性放在父类的原型中,自身的属性是不需要被继承的。
2. 然后,既然你要将两个原型放在一起,他们便共享了同一个原型,对任何一方的修改都会改变对方。
由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
现在,我们先将Animal对象改写:
function Animal(){ }
Animal.prototype.species
= "动物";
然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
Cat.prototype =
Animal.prototype;
Cat.prototype.constructor
= Cat;
var cat1 = new
Cat("大毛","黄色");
alert(cat1.species); // 动物
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
所以,上面这一段代码其实是有问题的。请看第二行
Cat.prototype.constructor =
Cat;
这一句实际上把Animal.prototype对象的constructor属性也改掉了!
alert(Animal.prototype.constructor);// Cat
这种方法肯定是不能直接使用的,只是作为理解放在这里。
子类.prototype
= 父类.prototype +空类型中转 + 基类.call(派生类,参数) 一起用完美解决
非构造函数的继承作为了解吧
----------------------------------------------面对对象结束----------------------------------------------------------
匿名函数和闭包
没有名字的函数叫做匿名函数
闭包是可访问一个函数作用域离变量的函数,这么理解,函数里头有一个函数,外层函数和内层函数
Function (){
Varbox = 10;
Return Function (){
Return
box; //BOX的值可以在外部访问到了
}
}
结合变量的作用域,可以总结:作用域从下而上,从内到外可以,但从上往下,从外到内是不可以的。
function wai(){ var box1 = 10; function nei(){ var box2 = 20; } window.alert(box2);//外部函数访问内层的函数是不可以的}wai();alert(box1);//在全局window环境下,访问外部函数内的变量也是不可能的
上头不是个闭包,你要区别函数内有个函数和闭包之间的区别
函数内有个函数,仍然是最基本的变量作用域
但是闭包改变了变量的作用域,让内部变量可以在外部访问,这就是闭包的意义!!
思考:我如何让外层函数访问到内层函数内的box2,然后顺着再让它也能让全局范围内访问的到
function wai(){ var box1 = 10; function nei(){ var box2 = 20; return
function(){ return box2; //外部函数就能访问到box2了 } } alert(nei()()); var box3 = nei()();//将这个box2的值给了box3,box2已经可以在外层函数中访问的到了 return
function(){ return box3;//window下就能访问到box2了 }}alert(wai()());
原来闭包就是这样,打破了原本的变量作用域,你只有理解了变量的基本作用域的同时才能理解为什么需要闭包?就是为了让外层也能访问的到内层的变量。
匿名函数是为了闭包
单一的匿名函数怎么执行?
[if !supportLists]1. [endif]1.赋值给一个变量,通过这个变量名()来执行
[if !supportLists]2. [endif]2.(匿名函数)(传递给匿名函数的参数)来执行
事实上,匿名函数的主要作用还是包含在一个函数内:
//函数里放一个匿名函数
Function box(){
Return function(){
Return ‘lee’;
}
}
Alert(box()());
//如果要执行的话,就需要两个圆括号
//第二个方法调用
Var b= box();
Alert(b());
闭包的作用:你能通过在函数内再创建一个函数,返回它的值,来在函数外访问到函数内的值,默认情况下,函数外是无法访问到函数内的值的。但,函数内是可以访问的到函数外的值的,这个跟我们理解的从内到外访问是可以的,从外到内访问是不行的是一致的。
function box(){ var user = 'lee'; return function(){ return user; }}alert(box()());var b = box();alert(b());
按道理来说,匿名函数是可以访问到user的,因为从内访问外部的是可以的
那么,我们返回这个函数不就能在外部访问到这个值了吗?
它通过了返回一个函数的模式,访问到了函数内部的变量。
初步理解:
因为这个函数的返回值是一个函数,所以,我们将这个返回值函数赋值给b,那么这个函数就有了一个新的名字,不再是匿名函数了,然后,我们再来调用这个b函数
那么box()()这种形式是无法完成累加这种功能的,因为你每次这么调用的话,box里头的所有的内容都会被执行一遍,如果你b=box(),却只会执行这个匿名函数的部分。
function box(){ var age = 100; return function(){ age++; return age; }}var b = box();alert(b());
再看一个循环的例子
function box(){ var arr = []; for(var i=0;i<5;i++){ arr[i]=function(){ return i; } } //如此调用,循环结束后4++=5.然后返回arr.所以结果是5,也就是说return i 的值永远是5 return arr; }var b =box(); //这一步我们获得了return arr的值 for(var i=0;i<5;i++){ alert(b[i]()); }
function box(){ var arr = []; for(var i=0;i<5;i++){ arr[i]=(function(num){ return num; })(i); } return arr; }var b =box(); for(var i=0;i<5;i++){ alert(b[i]); }
这种方法就可以?其实还是不太理解
闭包里头的this
先看下各种情况下,this的指向
var name='闫岐';var age=100;function box(){ alert(this.name);}box();
这里, box()只是个函数,函数内的this指的是windows对象(全局)
var name='王娇娇';var age=100;function box(){ this.name='闫岐'; this.age=200; alert(this.name); alert(this.age);}var box1=new box();box1();
new过之后,box就是个构造函数了,构造函数内的this就是对象本身
var a=0;function box1(){ var a=10; function box2(){ var a=20; alert(this.a); } box2(); alert(this.a);} box1(); alert(this.a);
这种情况,函数发生了嵌套,仍然没有破坏this的指向,仍然是window
也可以这么理解,如果这个函数绑定到某个对象上,那么this就指向这个对象,否则就是window
那么针对对象 ——闭包里头的this会怎么样呢?这也是唯一值得思考的一个问题
var user = 'The
window';var box = { user:'the box', getUser:function(){ return
function(){ return this.user } }}alert(box.getUser()());
闭包里头的this指向了window
第一种方法:对象冒充
var user = 'The
window';var box = { user:'the box', getUser:function(){ return
function(){ return this.user } }}alert(box.getUser().call(box));
第二种方法:
var user = 'The
window';var box = { user:'the box', getUser:function(){ //这里的作用域this是box var that = this; return
function(){ //这里的作用域this是window return that.user } }}alert(box.getUser()());
我们来看一个更加诡异的问题,call,apply的用法让我们去理解下函数内的this
在上面的例子中,函数内的this的指向都是window,这是因为,我们是在最常规的方法下分析得到的,我们在JS中,一切皆为对象,而JS中的函数恰恰也是一个对象。
在Java或者C/C++等语言中,方法(函数)只能依附于对象而存在,不是独立的。而在JavaScript中,函数也是一种对象,并非其他任何对象的一部分,理解这一点尤为重要,特别是对理解函数式的JavaScript非常有用,在函数式编程语言中,函数被认为是一等的。
函数的上下文是可以变化的,因此,函数内的this也是可以变化的,函数可以作为一个对象的方法,也可以同时作为另一个对象的方法,总之,函数本身是独立的。可以通过Function对象上的call或者apply函数来修改函数的上下文:
call和apply通常用来修改函数的上下文,函数中的this指针将被替换为call或者apply的第一个参数
//定义一个人,名字为jack
var jack = {
name :"jack",
age : 26
}
//定义另一个人,名字为abruzzi
var abruzzi
= {
name :"abruzzi",
age : 26
}
//定义一个全局的函数对象
function printName(){
return this.name;
}
//设置printName的上下文为jack, 此时的this为jack
print(printName.call(jack));
//设置printName的上下文为abruzzi,此时的this为abruzzi
print(printName.call(abruzzi));
print(printName.apply(jack));
print(printName.apply(abruzzi));
只有一个参数的时候call和apply的使用方式是一样的,如果有多个参数:
setName.apply(jack, ["Jack Sept."]);
print(printName.apply(jack));
setName.call(abruzzi,"John Abruzzi");
print(printName.call(abruzzi));
改变了this的指向。这是普通函数做不到的。
再看一个例子加深印象
<input type="text" id="myText" value="input text"><script>functionObj(){this.value="对象!";}varvalue="global变量";functionFun1(){alert(this.value);}window.Fun1();//global 变量Fun1.call(window);//global 变量Fun1.call(document.getElementById('myText'));//input textFun1.call(newObj());//对象!</script>
一般的调用是“对象调用函数”,但call aply 是“函数调用对象”。
functionmyFuncOne() {
this.p = "myFuncOne-";
this.A = function(arg) {
alert(this.p + arg);
}
}
functionmyFuncTwo() {
this.p = "myFuncTwo-";
this.B = function(arg) {
alert(this.p + arg);
}
}
function test() {
var obj1 = new myFuncOne();
var obj2 = new myFuncTwo();
obj1.A("testA"); //显示myFuncOne-testA
obj2.B("testB"); //显示myFuncTwo-testB
obj1.A.apply(obj2,["testA"]); //显示myFuncTwo-testA,其中[ testA”]是仅有一个元素的数组
obj2.B.apply(obj1,["testB"]); //显示myFuncOne-testB,其中[ testB”]是仅有一个元素的数组
obj1.A.call(obj2, "testA"); //显示myFuncTwo-testA
obj2.B.call(obj1, "testB"); //显示myFuncOne-testB
}
test();
函数.call(对象),改变函数内的上下文,this的指向发生了变化,指向对象
对象1.call(对象2),对象2继承了对象1中所有的基础方法和属性,不包含原型
对象1.函数.call(对象2),改变对象1中的this指向,指向了对象2
function inner(){
alert(arguments.callee);//指向拥有这个arguments对象的函数,即inner()
alert(arguments.callee.caller);//这个属性保存着调用当前函数的函数的引用,即outer()
}
function outer(){
inner();
}
outer();
在函数内部,arguments.callee该属性是一个指针,指向拥有这个arguments对象的函数;
而函数对象的另一个属性:caller,这个属性保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。