1、执行环境及作用域
(1).执行环境:
定义了变量或函数有权访问的其它数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象(variable object, VO),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。
全局执行环境是最外层的一个执行环境,在web浏览器中全局执行环境是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。每个函数都有自己的执行环境,当执行流进入一个函数的时候,函数的环境会被推入一个函数栈中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的所有变量和函数定义随之销毁,控制权返回到之前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。
(2).作用域:
作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域。
全局作用域:任何地方都可以定义拥有全局作用域的变量。
a.没有用var声明的变量(除去函数的参数)都具有全局作用域,成为全局 变量,所以声明局部变量必须要用var。
b.window的所有属性都具有全局作用域
c.最外层函数体外声明的变量也具有全局作用域
局部作用域:局部变量的优先级高于全局变量。
a.函数体内用var声明的变量具有局部作用域,成为局部变量
b.函数的参数也具有局部作用域
JavaScript是函数作用域(function scope),没有块级作用域。无论函数体内的变量在什么地方声明,对整个函数都是可见的,即JavaScript函数里声明的所有变量都被提前到函数体的顶部,只是提前变量声明,变量的赋值还是保留在原位置
(3).作用域链:
JavaScript的变量都是对象的属性,而该对象可能又是其它对象的属性,而所有的对象都是全局对象的属性,所以这些对象的关系可以看作是一条链,由于每个对象都有一个作用域,所以形成了一个作用域链。链头就是变量所处的对象,链尾就是全局对象。
当代码在一个环境中执行时,会创建变量对象的一个作用域链来保证对执行环境有权访问的变量和函数的有序访问。
2、原型链、继承
(1)理解原型对象
1).只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象;
2).所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针;
3).当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
(2)原型链
综上所述,假如我们让原型对象等于另一个类型的实例,则此时的原型对象将包含一个指向另一个原型的实例,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,这就是原型链。
【注】:所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因。
(3)继承
1).通过原型链实现。不能使用对象字面量创建原型方法,因为这样会重写原型链。
缺点:最主要的问题是包含引用类型值的原型;二是在创建子类型的实例时,不能向超类型的构造函数中传递参数。
2)借用构造函数。在子类型构造函数的内部调用超类型构造函数。使用apply()和call()方法,可以向超类型的构造函数中传递参数。apply()和call()的作用一样,只是专递的参数形式不同,apply()的参数是以数组的形式传递,call()中是展开的形式。
缺点:函数复用无从谈起,而且在超类型的原型中定义的方法,对子类型而言是不可见的。
3).组合继承(原型链+借用构造函数)。避免了1)、2)的缺陷,融合了他们的优点,成为javascript最常用的继承模式,而且,instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。
3、闭包
(1).概念:有权访问另一个函数作用域中的变量的函数。简单理解为“定义在一个函数内部的函数”。
(2).好处:保护函数内的变量安全,加强了封装性;在内存中维持一个变量(缓存);匿名自执行函数;模拟面向对象编程。
(3).应用场景:使用闭包代替全局变量;函数外或在其他函数中访问某一函数内部的参数;包装相关功能;为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点;
(4).缺点:常驻内存,会增大内存使用量,使用不当很容易造成内存泄露,更重要的是,对闭包的使用不当会造成无效内存的产生。