@chris-ren
2016-10-14T03:39:29.000000Z
字数 3779
阅读 864
JavaScript
随着JavaScript的日渐成熟,越来越多前端框架的出现以及越来越高的用户体验要求,对于JavaScript的要求不再是简单的使用,需要更深层次的理解。
JavaScript本身比较简单、易学,而且灵活、轻便。
javascript是一门解释型的语言,浏览器充当解释器。js执行时,在同一个作用域内是先解释再执行。解释的时候会编译function和var这两个关键词定义的变量,编译完成后从上往下执行并向变量赋值。
JS包含基本数据类型(字符串、数值、布尔)、复合数据类型(对象、数组)、特殊数据类型(null、'Undefined')
this是Javascript的一个关键字,它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。
随着函数使用场合的不同,this的值会发生变化。但有一个总的原则,this指的是调用函数的那个对象。下面分情况进行讨论:
这是函数的通常用法,属于全局性调用,因此this就代表全局对象。
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
为了证明this就是全局对象,对代码做一个修改:
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x); //0
结果变成了0
2. 作为对象方法调用
函数还可以作为某个对象的方法调用,这时this就指这个上级对象。
function test(){
alert(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m(); // 1
.
3. 作为构造函数调用
所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象。
var x = 2;
function test(){
this.x = 1;
}
var o = new test();
alert(o.x); // 1
alert(x); //2
如上,证明了this不是全局对象,全局对象的x值并没有改变。
4. apply调用
apply()函数中,第一个参数是上下文,第二个参数是参数组成的数组。如果上下文是null,则使用全局对象代替。例如:
function.apply(this,[1,2,3])
因此,this指的就是这第一个参数。
var x = 0;
function test(){
alert(this.x);
}
var o={};
o.x = 1;
o.m = test;
o.m.apply(); //0
apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。
如果把最后一行代码修改为
o.m.apply(o); //1
运行结果就变成了1,证明了这时this代表的是对象o。这里也证实了apply()函数的作用就是改变函数的调用对象。
call()方法与apply()方法类似,第一个参数也是上下文,后续驶传入的参数序列,例如:
function.call(this,1,2,3);
.
5. 箭头函数
ES6中的箭头函数有个不同的地方:函数体内的this对象,是定义时所在的对象,而不是使用时所在的对象。this对象的指向是可变的,但在箭头函数中,它是固定的。
function test() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
test.call({ id: 42 });
// id: 42
上面的代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义是在test函数生成时生效的,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。
闭包的三个特性:
1.函数嵌套函数
2.函数内部可以引用外部的参数和变量(创建闭包最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量)
3.参数和变量不会被垃圾回收机制回收(会常驻内存,使用不当可能会造成内存泄漏)
函数内部可以访问全局变量:
var n=1;
function f1(){
alert(n);
}
f1(); // 1
函数外部自然无法访问内部局部变量:
function f1(){
var n=1;//这里如果不用var声明,实际上是全局变量
}
alert(n); // error
那有时我们需要在函数外部得到函数内部的局部变量,要怎么做呢?
我们可以在函数的内部再定义一个函数(那内部的函数是可以访问函数的变量的),并将其作为返回值。
function f1(){
var n=1;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 1 函数外部访问内部变量
.
2. 闭包概念
上面的f2就是一个闭包函数,闭包是指有权访问另一个函数作用域内部变量的函数,创建闭包最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。
3. 闭包的用途
(1) 读取函数内部的变量。(如上面的例子)
(2)让变量始终保持在内存中。(JavaSript中一般函数执行完毕,局部变量就会被销毁,内存中仅保存全局作用域,但闭包不是这样的)
function test() {
var a = 1;
function f2(){
alert(a++)
};
return f2;
}
var fun = test();
fun();// 1 执行后 a++,a依然保存在内存中
fun();// 2 再执行时,会在原来的值上加1
fun = null;//a被回收!!
为什么是这样的?原因就在于test是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于test,因此test也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个” [[Prototype]]”内部属性,这个属性所对应的就是该对象的原型。
2. 设计思想
JavaScript里所有数据类型都是对象,这一点和Java很像,但是在设计"继承"机制时,Brendan Eich不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这在当时感觉有点太正式了,而且增加了初学者的入门难度。
考虑到Java和C++都是通过new生成实例,所以在JavaScript中引入了new的命令,举个例子:
function DOG(name){
this.name = name;
}
这里有一个表示dog的对象,通过new就可以创建该对象的实例:
var dogA = new DOG('大毛');
alert(dogA.name); // 大毛
但是通过这种方法生成的实例有一个缺点:无法共享属性和方法。
比如,在DOG对象中有一个共有的属性species:
function DOG(name){
this.name = name;
this.species = '犬科';
}
然后,生成两个实例对象:
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。
dogA.species = '猫科';
alert(dogB.species); // 显示"犬科",不受dogA的影响
每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
3. prototype属性的引入
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。
这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。
现在用prototype属性进行改写上面的方法:
function DOG(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); // 猫科