[关闭]
@chris-ren 2016-10-14T03:39:29.000000Z 字数 3779 阅读 864

JavaScript浅谈

JavaScript


Why

  1. 随着JavaScript的日渐成熟,越来越多前端框架的出现以及越来越高的用户体验要求,对于JavaScript的要求不再是简单的使用,需要更深层次的理解。

  2. JavaScript本身比较简单、易学,而且灵活、轻便。

  3. 虽然JavaScript的使用非常普遍,但是对于它的理解远不如后台语言。

关键点


javascript是一门解释型的语言,浏览器充当解释器。js执行时,在同一个作用域内是先解释再执行。解释的时候会编译function和var这两个关键词定义的变量,编译完成后从上往下执行并向变量赋值。

JS包含基本数据类型(字符串、数值、布尔)、复合数据类型(对象、数组)、特殊数据类型(null、'Undefined')

一、Javascript this用法

this是Javascript的一个关键字,它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。

随着函数使用场合的不同,this的值会发生变化。但有一个总的原则,this指的是调用函数的那个对象。下面分情况进行讨论:

  1. 纯粹的函数调用

这是函数的通常用法,属于全局性调用,因此this就代表全局对象。

  1. function test(){
  2.     this.x = 1;
  3.     alert(this.x);
  4. }
  5.   test(); // 1

为了证明this就是全局对象,对代码做一个修改:

  1. var x = 1;
  2.   function test(){
  3.     this.x = 0;
  4.   }
  5.   test();
  6.   alert(x); //0

结果变成了0
2. 作为对象方法调用

函数还可以作为某个对象的方法调用,这时this就指这个上级对象。

  1. function test(){
  2.     alert(this.x);
  3.   }
  4.   var o = {};
  5.   o.x = 1;
  6.   o.m = test;
  7.   o.m(); // 1

.
3. 作为构造函数调用

所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象。

  1. var x = 2;
  2. function test(){
  3.     this.x = 1;
  4.   }
  5.   var o = new test();
  6.   alert(o.x); // 1
  7.   alert(x); //2

如上,证明了this不是全局对象,全局对象的x值并没有改变。
4. apply调用

apply()函数中,第一个参数是上下文,第二个参数是参数组成的数组。如果上下文是null,则使用全局对象代替。例如:

  1. function.apply(this,[1,2,3])

因此,this指的就是这第一个参数。

  1. var x = 0;
  2.   function test(){
  3.     alert(this.x);
  4.   }
  5.   var o={};
  6.   o.x = 1;
  7.   o.m = test;
  8.   o.m.apply(); //0

apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。

如果把最后一行代码修改为

  1.  o.m.apply(o); //1

运行结果就变成了1,证明了这时this代表的是对象o。这里也证实了apply()函数的作用就是改变函数的调用对象。

call()方法与apply()方法类似,第一个参数也是上下文,后续驶传入的参数序列,例如:

  1. function.call(this,1,2,3);

.
5. 箭头函数
ES6中的箭头函数有个不同的地方:函数体内的this对象,是定义时所在的对象,而不是使用时所在的对象。this对象的指向是可变的,但在箭头函数中,它是固定的。

  1. function test() {
  2. setTimeout(() => {
  3. console.log('id:', this.id);
  4. }, 100);
  5. }
  6. var id = 21;
  7. test.call({ id: 42 });
  8. // id: 42

上面的代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义是在test函数生成时生效的,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。

二、Javascript闭包

闭包的三个特性:

1.函数嵌套函数
2.函数内部可以引用外部的参数和变量(创建闭包最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量)
3.参数和变量不会被垃圾回收机制回收(会常驻内存,使用不当可能会造成内存泄漏)

  1. 变量的作用域
    要理解闭包,先从变量作用域说起,Javascript变量作用域无非就是:全局变量和局部变量。

函数内部可以访问全局变量:

  1. var n=1;
  2. function f1(){
  3.  alert(n);
  4. }
  5. f1(); // 1

函数外部自然无法访问内部局部变量:

  1. function f1(){
  2.     var n=1;//这里如果不用var声明,实际上是全局变量
  3. }
  4. alert(n); // error

那有时我们需要在函数外部得到函数内部的局部变量,要怎么做呢?

我们可以在函数的内部再定义一个函数(那内部的函数是可以访问函数的变量的),并将其作为返回值。

  1. function f1(){
  2.     var n=1;
  3.     function f2(){
  4.       alert(n);
  5. }
  6.    return f2;
  7.   }
  8.   var result=f1();
  9.   result(); // 1 函数外部访问内部变量

.
2. 闭包概念
上面的f2就是一个闭包函数,闭包是指有权访问另一个函数作用域内部变量的函数,创建闭包最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。
3. 闭包的用途
(1) 读取函数内部的变量。(如上面的例子)
(2)让变量始终保持在内存中。(JavaSript中一般函数执行完毕,局部变量就会被销毁,内存中仅保存全局作用域,但闭包不是这样的)

  1. function test() {
  2. var a = 1;
  3. function f2(){
  4. alert(a++)
  5. };
  6. return f2;
  7. }
  8. var fun = test();
  9. fun();// 1 执行后 a++,a依然保存在内存中
  10. fun();// 2 再执行时,会在原来的值上加1
  11. fun = null;//a被回收!!

为什么是这样的?原因就在于test是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于test,因此test也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

三、JavaScript原型

  1. 原型的概念

在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个” [[Prototype]]”内部属性,这个属性所对应的就是该对象的原型。
2. 设计思想
JavaScript里所有数据类型都是对象,这一点和Java很像,但是在设计"继承"机制时,Brendan Eich不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这在当时感觉有点太正式了,而且增加了初学者的入门难度。

考虑到Java和C++都是通过new生成实例,所以在JavaScript中引入了new的命令,举个例子:

  1. function DOG(name){
  2.     this.name = name;
  3. }

这里有一个表示dog的对象,通过new就可以创建该对象的实例:

  1. var dogA = new DOG('大毛');
  2. alert(dogA.name); // 大毛

但是通过这种方法生成的实例有一个缺点:无法共享属性和方法。

比如,在DOG对象中有一个共有的属性species:

  1.  function DOG(name){
  2.     this.name = name;
  3.     this.species = '犬科';
  4.   }

然后,生成两个实例对象:

  1.   var dogA = new DOG('大毛');
  2.   var dogB = new DOG('二毛');

这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。

  1.   dogA.species = '猫科';
  2.   alert(dogB.species); // 显示"犬科",不受dogA的影响

每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
3. prototype属性的引入
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。

这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

实例对象一旦创建,将自动引用prototype对象的属性和方法。

现在用prototype属性进行改写上面的方法:

  1.   function DOG(name){
  2.     this.name = name;
  3.   }
  4.   DOG.prototype = { species : '犬科' };
  5.  var dogA = new DOG('大毛');
  6.   var dogB = new DOG('二毛');
  7.  alert(dogA.species); // 犬科
  8.   alert(dogB.species); // 犬科

现在,species属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。

  1.   DOG.prototype.species = '猫科';
  2.   
  3.   alert(dogA.species); // 猫科
  4.   alert(dogB.species); // 猫科
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注