近大半年来一直做Yahoo!Widget开发,用到了JavaScript,最近离职了总结一下JavaScript的使用,主要是自己的理解和实验结果,有不同见解的欢迎讨论。
一、JavaScript中的条件判断
在JavaScript中既可用条件判断语句也可使用任意数据类型甚至赋值语句作为判断条件
1、条件判断语句就不用说了,就是最终会返回true或false的语句。
2、使用数据作为判断条件时,非零数字与逻辑true等同,数字‘0’与逻辑false等同,空字符串、null、undefined与逻辑false等同。
3、使用赋值语句作为判断条件时,如a = b,其效果等同于 a | b
示例:
if(0){//此处是使用数字‘0’作为判断条件,所以其逻辑效果就等同于false return 0} else return 1; if(a = 0){//此处是使用赋值语句作为判断条件,所以其逻辑效果就等同于 a | 0 return 0} else return 1;
二、对象的实例化及使用
1、获取对象大致有两种方式,一是直接获得对象实例,二是通过构造函数构造一个对象实例。(构造函数稍后详讲)
//直接获得对象实例 var a = { this.name = “name” this.getName = function(){ return this.name}; } //直接获得空对象 var a = {}; var a = new Object();//var a = new Object(){};这种定义是不被允许的 //JSON方式获得对象实例 var a = { “name”:”name”, “getName”:function(){return this.name} } //JSON方式获得对象实例也可用下面这种 var a = { name:”name”, getName:function(){return this.name} } //先声明一个构造函数,然后通过该构造函数构造一个此对象 function A(name){ this.name = name; this.getName = function(){ return this.name} } Var a = new A("han");
2、对象属性
访问一个对象的属性,可以简单的用对象名加"."后加属性的名字,也可以用"[]"操作符来获取,此时在[]里面的属性名字要加引号,这是因为对象中的索引都是字符串类型的.如a[“name”]可以,但a[name]不可以。
因为JS可以将数字自动包装成string,所以这种写法(a[“name” + 1])是正确的。a[“name” + 1]就相当于a[“name1”]
每一个javascript对象都有一个constructor属性.这个属性对应了对象初始化时的构造函数.试着打印出无构造器对象的constructor,会发现它和new Object()的constructor的是一样的。
对象实例化后可以给对象动态添加属性或方法
function A(name){ this.name = name; this.getName = function(){ return this.name} } var a = new A("han"); //对象实例化后给对象动态添加属性或方法 a.age = 20;//或 a["age"] = 20; a.setName = function(name){this.name = name};//或a["setName"] = ...
Array也是对象所以可以给Array对象添加属性或方法
var a = new Array(); a.name = "Array";//或a["name"] = "Array"; a.getName = function(){return this.name} //或a["getName"] = ...
Array对象的长度是可以动态改变的
var a = new Array();//声明一个长度为0的数组 var b = new Array(10);//声明一个长度为10的数组,未被赋值的元素的值与null,undefined等价的 var c = new Array(1,2);//声明一个长度为2的数组,包含1和2两个元素 //Array对象声明完后可以动态指定它的长度 a.length = 10;//将数组a的长度指定为10 b.length = 5;//将数组b的长度重新指定为5,则b只保留前5个元素,后5个将被舍弃 c[10] = 11;//c原先的长度为2,没有索引为10的元素,此赋值语句等于变相指定c的长度为11,且索引为10的元素的值为11 //也可以通过下面这种方式指定c中索引为10的元素的值 c["10"] = 11;//这与c[10] = 11的效果等同,虽然这与给对象添加属性很类似,但使用时只能通过c["10"]或c[10]引用,而不能通过a.10使用
//注意下面的声明方式的不同 var a = new Array(1);//声明一个长度为1的空数组 var b = new Array("1");//声明一个长度为1的数组,且b[0] = 1 var c = new Array("a");//声明一个长度为1的数组,且c[0] = "a"
通过以上可以看出,我们在使用Array对象时一般没有必要声明它的长度。如果要保存固定长度和名称的属性建议使用对象,如果是不定长或临时数据可以使用数组或闭包。
三、对象、函数、原型
1、函数的arguments属性
函数可以接收可变长度参数,且在定义函数时不必指明参数的名称,我们可以使用函数的arguments属性(每个函数都默认含有arguments属性,所有 参数会被顺序放到一个类似数组的对象中,函数的arguments拥有对它的引用)来引用这些参数。
function f(){ if(arguments.length){ for(var i = 0;i < arguments.length; i++){ alert(arguments[i]); } } } //调用函数f f(1,2,3);//会依次打印出1,2,3 f(1);//会打印出1 f();//不打印
2、函数的prototype属性
每一个函数都包含了一个prototype属性(对象没有此属性),这个属性指向了一个prototype对象,我们可以指定函数对应的prototype对象,如果不指定,则函数的prototype属性指向一个默认的prototype对象,并且此默认prototype对象的constructor属性又指向该函数。
若给函数的prototype属性指定一个prototype对象,则此prototype对象的constructor属性指向该prototype对象的构造函数。
函数在指定一个prototype对象时,此prototype对象要保证已经被实例化,否则会出错。
3、使用构造函数构造对象及原型的使用
构造函数就是JavaScript中的函数,只不过这个函数被用来构造对象,像是一个构造器,所以称这种特殊用途的函数为构造函数。
使用构造函数构造对象的过程:
当我们使用new操作符时,javascript会先创建一个空的对象,然后这个对象被new后面的函数的this关键字引用,然后在函数中,通过操作this,就给这个新创建的对象相应的赋予了属性或方法,最后返回这个经过处理的对象。
function A(){ this.name = "handl"; this.getName = function(){ return this.name} } var a = new A();//先创建一个空对象,然后调用A方法给空对象添加属性和方法,最后返回该对象,我们就得到了一个a对象。
摘抄官方解释:
当用构造函数创建一个新的对象时,它会获取构造函数的prototype属性所指向的prototype对象的所有属性和方法,对构造函数对应的prototype对象所做的任何操作都会反映到它所生成的对象身上 ,所有的这些对象共享构造函数对应的prototype对象的属性和方法。
对象可以使用其创建时它的构造函数指向的prototype对象 ,但不能像构造函数那样直接调用prototype对象(对象没有prototype属性)。
function A(){ this.name = "A"; } function B(){ this.name = "B"; } var b = new B(); var a1 = new A();//a1创建时,A的prototype属性所指向的是默认prototype对象 A.prototype.getName = function(){//给A的prototype属性所指向的是默认prototype对象添加方法 return "Test_1"} A.prototype = b;//指定A的prototype属性所指向prototype对象为b var a2 = new A();//a2可以使用A指向的prototype对象b A.prototype.getName = function(){//给A的prototype属性所指向的prototype对象b添加方法 return "Test_2"} a1.getName();//因为a1可以使用A原先指向的默认prototype对象,所以执行此语句会返回"Test_1" a2.getName();//因为a2可以使用A指向的prototype对象b,所以执行此语句会返回"Test_2"
对象没有prototype属性,所以也就不能像构造函数那样通过"."操作符后跟prototype的方式来引用prototype对象。对象只能共享其构造函数对应的prototype对象的属性和方法,并且对象一旦创建,它所指向的prototype对象就不能改变为其它对象,但这个prototype对象的属性和方法可以更改。
4、this的使用
用this来取得对方法调用者(对象)的引用。 说的通俗一些就是,某对象调用方法,那方法中的this就是该对象。
function A(name){ this.name = name; } function B(name,object){ this.name = name; this.object = object; this.object.rename = function(name){ //此函数的参数中不能使用this关键字 object.name = this.name;//此函数内的this是对object的引用而不是对B的对象的引用 } }
4、对象属性,类属性,私有属性
对象属性:通过实例对象加"."操作符然后跟上属性名(或方法名)来访问的属性。
类属性:通过构造函数或实例对象加"."操作符然后跟上属性名(或方法名)来访问的属性。
function A(){ this.name = "A";//声明一个对象属性 } A.getName = function(){//声明一个类方法 return "Test"} var a = new A(); a.setName = function(name){//给对象a添加一个对象方法 this.name = name}
私有对象属性:通过构造函数的参数或局部变量指定(闭包),不能直接访问,只能通过对象内的方法来访问。
function A(){ var name;//函数内的私有变量,因为有别的方法对它有引用所以可以作为局部变量使用 this.getName = function(){ return name;} this.setName = function(n){ name = n;} }
四、作用域、局部变量、全局变量、闭包
以下部分是摘抄别人的总结,自己还没有从中有新的发现
1、作用域
在javascript中没有块级别的作用域,也就是说在java或c/c++中我们可以
用"{}"来包围一个块,从而在其中定义块内的局部变量,在"{}"块外部,这些变量不再起作用,
同时,也可以在for循环等控制语句中定义局部的变量,但在javascript中没有此项特性:
function f(props) { for(var i=0; i<10; i++) {} alert(i); //10 虽然i定义在for循环的控制语句中,但在函数 //的其他位置仍旧可以访问该变量. if(props == "local") { var sco = "local"; alert(sco); } alert(sco); //同样,函数仍可引用if语句内定义的变量 } f("local"); //10 local local
2、局部变量与全局变量
var sco = "global"; function print1() { alert(sco); //global } function print2() { var sco = "local"; alert(sco); //local } function print3() { alert(sco); //undefined var sco = "local"; alert(sco); local } print1(); //global print2(); //local print3(); //undefined local
前面两个函数都很容易理解,关键是第三个:第一个alert语句并没有把全局变量"global"显示出来,
而是undefined,这是因为在print3函数中,我们定义了sco局部变量(不管位置在何处),那么全局的
sco属性在函数内部将不起作用,所以第一个alert中sco其实是局部sco变量,相当于:
function print3() { var sco; alert(sco); sco = "local"; alert(sco); }
从这个例子我们得出,在函数内部定义局部变量时,最好是在开头就把所需的变量定义好,以免出错。
var scope = "global" //定义全局变量 function print() { alert(scope); } function change() { var scope = "local"; //定义局部变量 print(); //虽然是在change函数的作用域内调用print函数, //但是print函数执行时仍旧按照它定义时的作用域起作用 } change(); //golbal
3、闭包
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
function a(){ var i=0; function b(){ alert(++i); } return b; } var c = a(); c();
闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。
在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。(Javascript的垃圾回收机制)
闭包内的微观世界
如 果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境 (excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。
1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
2、当函数a执行的时候,a会进入相应的执行环境(excution context)。
3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。
当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依 次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。
闭包的应用场景
1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
以上两点是闭包最基本的应用场景,很多经典案例都源于此。
JavaScript的总结大概就先写到这里,如果以后工作中有什么新的发现还会继续。