在 JavaScript 中使用构造函数来新建一个对象的,每一个构造函数内部都有一个 prototype
属性,属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,指向构造函数的 prototype
属性对应的值,在 ES5 中这个指针称为对象的原型,可以通过 __proto__
属性来访问,但最好不要在实践中使用,因为他不是规范中规定的。ES5 中新增了 Object.getPrototypeOf()
方法,可以通过这个方法来获取对象的原型。
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里去找这个属性,这个原型对象又会有自己的原型,于是就一直找下去,即原型链。原型链的尽头一般来说都是 Object.prototype
所以这就是新建的对象为什么能使用 toString
方法的原因。
特点:JavaScript 对象是通过引用来传递的,创建的每个新对象实例中并没有一份属于自己的原型副本。当修改原型时,与之相关的对象也会继承这一改变。
原型链的终点是什么?如何打印出原型链的终点?
由于 Object
是构造函数,原型链终点 Object.prototype.__proto__
,而 Object.prototype.__proto__ === null
,所以,原型链的终点是 null
。
原型链上的所有原型都是对象,所有的对象最终都是由 Object
构造的,而 Object.prototype
的再上一层是 Object.prototype.__proto__
。
# 作用域 和 作用域链
- 全局作用域
- 最外层函数和最外层函数外面定义的变量拥有全局作用域
- 所有未定义直接赋值的变量自动声明为全局作用域
- 所有
window
对象的属性拥有全局作用域 - 全局作用域由很大的弊端,过多的全局作用域变量会污染全局命名空间,引起命名冲突
- 函数作用域
- 声明在函数内部的变量,一般只有固定的代码片段可以访问到
作用域是分层的,内层作用域可以访问外层,反之不行
- 块作用域
- ES6 中新增
let
和const
指令可以声明块级作用域 - 块级作用域可以在函数中创建,也可以在一个代码块(
{}
)中创建 let
和const
声明的变量不会有变量提升,也不可以重复声明- 在循环中比较适合绑定块级作用域,可以将声明的计数器变量限制在循环内
- ES6 中新增
- 作用域链
- 在自己作用域中找不到变量就去父级作用域查找,依次向上级作用域查找,直到访问到全局作用域就终止,这一层层关系就是作用域链
- 作用域链保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数
- 本质上是一个指向变量对象的指针列表,变量对象是一个包含了执行环境中所有变量和函数的对象
- 作用域链的前端始终都是当前执行上下文的变量对象,全局执行上下文的变量对象始终是作用域链的最后一个对象
- 当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找