[TOC]

四、原型与原型链

1. 原型

(一)原型的定义

在JavaScript中是使用构造函数(js是基于原型的语言)来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。
当使用构造函数新建一个对象后,在这个实例对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
一般来说不应该能够获取到原型的,但是现在浏览器中都实现了 proto 属性来访问原型,但是最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。

一个实例的__proto__ // 隐式原型
一个实例的构造函数的prototype // 显式原型

(二)原型的特性

  1. 所有的引用类型(数组、对象、函数)都具有对象特性,即可自由扩展属性( null 除外); 都有一个 proto 属性,属性值是一个普通的对象, proto 属性值指向它的构造函数的 prototype 属性值(实例. proto === 构造函数.prototype)
  2. 所有的函数都有一个 prototype 属性,属性值也是一个普通的对象(这里要注意,一般实例的构造函数上才有prototype 属性,但函数本身就有prototype 属性)

(三)hasOwnProperty

使用 hasOwnProperty 判断这个属性是不是对象本身的属性:
高级浏览器已经在 for in 中屏蔽了来自原型的属性,但有时候加上原型的判断,可以保证程序的健壮性:

f.hasOwnProperty(item) => 判断item是否是f对象本身的属性

2. 原型链

(一)原型链的定义/什么是原型链

每个构造函数内部都有显式原型prototype,构造函数的实例对象具有隐式原型proto属性指向它构造函数的显示原型prototype,如果一个构造函数是另一个构造函数的的实例对象的话,那这个构造函数也有proto。当我们查找一个对象的属性时,会先从自身属性上去找,找不到的话再从proto上去找,还没有就从protoproto上去找,这样一直往上查就会查到原型的尽头,这么一个链式结构是原型链,原型链尽头是null。

(二)原型链终端

当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 proto (即它的构造函数的 prototype )中寻找 ;顺着原型链找,最后没找到的话最终遇到null,null是一个中断机制,遇到null就停了返回undefined。Object.prototype. proto === null

为什么Object.prototype.proto指向null?

因为原型链的终端是null,就和链表一样,终端是null,而原型链的终端是Object这个构造函数的原型的proto指向的值,设计就是这样

(1)原型链上查找一个不存在的属性为什么返回undefined

image.png

(2)Object.prototype.proto为什么设计指向null ?

查找属性是要沿着原型链递归查找,所以原型链是要有长度的,null作为一个为空的对象刚好满足要求,是个相对合理的设定;原型链查找就是递归调用,那肯定要有出口的,递归出口就是当前原型对象的原型是不是null

(3) 原型链的终点是什么?如何打印出原型链的终点?

由于Object是构造函数,原型链终点是Object.prototype.proto
Object.prototype.proto=== null // true,
所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.proto

三(2)【JavaScript】 - 图2

(三)新建的对象为什么能够使用 toString() 等方法?

新建对象原型链上有Object,就会继承 Object.prototype属性 (Object.prototypeproto === null),所有的对象都继承了Object的方法,所以这就是新建的对象为什么能够使用 toString() 等方法的原因。

(四)原型链的特点

JavaScript 对象是通过引用来传递的,创建的每个新对象实体中并没有一份属于自己的原型副本。当修改原型时,与之相关的对象也会继承这一改变

(五)原型链图

image.png
image.png

(六)Function.proto === Function.prototype

为什么 Function.prototype === Function.proto 为true?
因为Function.prototype.proto指向的是Object.prototype,Function.proto.proto === Object.prototype所以Function.prototype === Function.proto;所以Object.proto === Function.prototype

https://github.com/mqyqingfeng/Blog/issues/2

image.png

3. 原型修改、重写

function Person(name) {
    this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // false

可以看到修改原型的时候p的构造函数不是指向Person了,因为直接给Person的原型对象直接用对象赋值时,它的构造函数指向的了根构造函数Object,所以这时候p.constructor === Object ,而不是p.constructor === Person。要想成立,就要用constructor指回来

Person.prototype = {
    getName: function() {}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // true

4. 原型链指向

p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor  // Person
p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor  // Person

image.png

Object.prototype.__proto__     原型链的尽头是null

Function.prototype.__proto__   Object.prototype,Function是Object实例化的对象

构造函数自身的__proto__是什么    任何函数包括Function的隐式原型都指向Function.prototype;

Object.__proto__ 答案是什么  可通过new Object的方式去new实例,说明Object是构造函数,任何函数的隐式原型是Function.prototype;

Object instanceof Function     true Object的原型链上有Function

Function instanceof Object     true Function的原型链上有Object

Function.prototype === Function.__proto__  true

通常,proto指向prototype;constructor指向构造函数;constructor和prototype是相反方向的箭头

7. 如何获得对象非原型链上的属性?

使用后hasOwnProperty()方法来判断属性是否属于原型链的属性:

function iterate(obj){
   var res=[];
   for(var key in obj){
        if(obj.hasOwnProperty(key))
           res.push(key+': '+obj[key]);
   }
   return res;
}

五、执行上下文/作用域链/闭包

1. 对执行上下文的理解

(一)执行上下文的定义

在一段 JS 脚本(即一个