原型与原型链
文章结构
- 原型与原型链
- 原型
- 原型链
- 原型链中的 this
- 作用域与闭包
- 执行上下文
- 作用域
- 作用域链
- 闭包
- 面试题
原型与原型链
下面几条必须记住:
- 所有的引用类型(数组,函数,对象),都具有对象特性,即可以自由扩展属性。
- 所有的引用类型(数组,函数,对象),都具有proto,其值是一个对象。
- 所有的函数,都具有 prototype,其值是一个对象。
- 所有的引用类型(数组,函数,对象),其proto属性都指向它的构造函数的 prototype 属性值。即:
obj.__proto__ === Object.prototype
1 | // 要点一:自由扩展属性 |
原型
当试图找到一个对象的某个属性时,如果该对象没有这个属性,那么就会到它的proto(即它的构造函数的 prototype)去寻找。如果它的proto还是没有则继续在 f.proto.proto(也即 F.prototype.proto)上寻找。如果直到最上层也没有找到,则返回undefined。
1 | // 构造函数 |
执行 f.alertName()时,因为该对象没有 alertName()方法,所以就会到它的proto(即 Foo.prototype)上去寻找。此时找到了,调用该原型链上的 alertName()方法。没有找到则继续查找。
原型链
上述找的过程,是一个链式的结构,叫做“原型链”。所有原型链的最上层都是 null(即 Object.prototype.proto === null)。
原型链中的 this
所有从原型链中得到的方法,在 this 执行时,都指向了当前触发这个方法的对象。
作用域与闭包
执行上下文
在一段 javsscript 脚本执行之前,要先解析代码,解析的时候,会创建一个全局执行上下文环境,先把即将执行代码中的变量和函数的声明拿出来存放在这个环境中:变量暂时赋值为 undefined,函数则声明好可以使用。这一步完成之后,才正式开始执行代码。
一个函数在调用时,会创建函数执行上下文环境,相对于全局执行上下文环境,在函数执行上下文环境中,增加了 this,arguments 和函数的参数。
this 在执行时才能确定其值,定义的时候不能确认。
1 | console.log(a) |
上述代码执行过程:
- 创建全局执行上下文
- 申明变量 a(此时变量 a 赋值为 undefined)、函数 fn
- 开始执行代码:
console.log(a) // undefined
,fn('zhangsan') // 100 zhangsan 20
,console.log(b) // 报错,因为全局执行环境中没有找到关于b的申明
fn('zhangsan')
调用时,其执行过程如下:
- 创建一个函数执行上下文
- 声明变量 age(此时变量 age 赋值为 undefined)
- 沿着作用域链查找变量 a,若在全局作用域中还没有找到,才会报错:
a is not defined
并停止执行程序 - 在 arguments(arguments 是类数组)中找到 name
- 沿着作用域链查找变量 a,若在全局作用域中还没有找到,才会报错:
- 开始执行代码
作用域
在 es6 之前,javascript 中没有块级作用域的概念。例如:
1 | if (true) { |
作用域就是一个独立的地盘,让变量不会泄露、暴露出去。JS 没有块级作用域,只有全局作用域和函数作用域。
jQuery、Zepto 等库的源码,所有的代码都会放在(function(){….})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。
1 | var a = 100 |
ES6 中开始加入了块级作用域,使用let
定义变量即可。
1 | if (true) { |
作用域链
执行某个函数作用域中的一段代码时,当一个变量的申明没有在当前作用域中找到,会一层一层向上级查找,这一层一层的关系,就是作用域链。若在全局作用域中还没有找到,才会报错。
1 | var a = 100 |
上述代码中,console.log(a)要得到 a 变量,但是在当前的作用域中没有定义 a 变量。像这种在当前作用域中没有定义但在作用域链中定义的变量,就是自由变量。
自由变量将在作用域链中寻找,但是依据的是函数定义时候的作用域链,而不是执行时候的。
闭包
闭包主要有两个执行场景:
- 函数作为返回值返回
- 函数作为参数传递
1 | function F1() { |
1 | function F1() { |