学不动系列之JS原型链

194 阅读5分钟

JS原型链

含义

​ JS是面向对象的,每个实例对象都有一个__proto__属性,该属性指向它的原型对象,这个实例对象的构造函数有一个原型属性prototype,与实例的__proto__属性指向同一个对象。当一个对象在查找一个属性的时候,自身没有就会根据__proto__向它的原型进行查找,如果还没有,则向它的原型的原型继续查找,直到查到Object.prototype.__proto__null,这样也就形成了原型链

重要属性

  • prototype :构造函数的原型对象。当创建一个构造函数后,这个函数会自动创建一个prototype属性,这个属性是个指针,指向一个对象。这个对象称为自己创建的构造函数的原型对象

  • __ proto __ :对象的原型。对于已经创建好了的构造函数,当用户创建了这个构造函数的实例后(new出来的),这个新实例下也会自动添加一个属性__proto__,这称为内部属性。

    ES5中用[[prototype]]表示,一般用户是没有办法访问的。但Firefox,Safari,Chrome,以及IE9以上可以用Object.getPrototypeOf(实例)来访问这个属性。其实这个属性指向的也是这个实例的构造函数的原型对象。

  • constructor :构造器。原型上的构造器属性,指向该原型的构造函数。

特别声明

  • Function是一个构造函数,也可以说是一个已经由function声明好的函数对象。function是一个关键字,用来声明一个函数对象。
  • Function这个函数对象和其他函数对象不同,他的prototype__proto__都指向Function的原型,即Function.prototype。这是因为函数Function是由它本身创造出来的,所以它的__proto__指向它自己的原型。
  • prototype开发者可以用方法直接访问,__ proto __代表的是实例对象的内部属性。
  • __ proto __属性里不会有prototype属性的,prototype里也不会嵌套prototype属性。但是constructor属性中会同时存在__proto__prototype属性。

概念

  1. JS分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性;

  2. ObjectFunction都是JS内置的函数,类似的还有我们经常用到的ArrayRegExpDateBooleanNumberString

  3. 属性__proto__是一个对象,它有两个属性,constructor__proto__

  4. 原型对象prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建的。

分析

  • 函数对象

    // 在JS中,任何事物皆为对象,所以Person既是一个函数,也是一个对象
    function Person(){};
    // 此时,Person就是一个函数对象
    
    let person = new Person();
    // 此时,person就是一个普通对象,也是Person的一个实例
    

    ObjectFunctionArrayRegExpDateBooleanNumberString这些JS内置的函数也都是函数对象,都有prototype属性。同样,我们通过function声明的函数,类如上述代码中的Person也是一个函数对象。

  • 原型对象

    原型也是一个对象,而所谓原型,就是函数对象的prototype属性。拿上述函数对象Person来举例,就是原型对象=Person.prototype

  • 普通对象

    let person = {};
    // 等价于
    let person = new Object();
    // 等价于
    let person = Object.create(null)
    
    // 此时,person就是一个普通对象
    
  • 构造函数(通过new关键字来实例化对象的函数

    // 声明一个函数,名为Person
    function Person(){};
    
    // 实现1:
    let person = new Person();// 此时,Person为构造函数
    
    // 实现2:
    let person = Person();// 此时,Person为一个普通函数
    

    任何函数都可以称之为构造函数。之所以有构造函数普通函数之分,主要是从功能上进行区别。构造函数的主要功能为初始化对象,为其添加属性和方法,特点是和new一起使用。

准则

  1. 原型对象prototypeconstructor指向构造函数本身;
  2. 实例的__proto__和原型对象指向同一个地方。

分析

  • function Person(){};
    console.log(Person.prototype.constructor === Person); 
    // 打印结果为 true
    
  • function Person(){};
    let person = new Person();// 此时person是Person的一个实例
    console.log(person.__proto__ === Person.prototype);
    // 打印结果为 true
    

原型链指向

function Person(){};
let person = new Person();

console.log(Person.prototype.constructor === Person);// true

console.log(person.__proto__ === Person.prototype);// true

console.log(Person.__proto__ === Function.prototype);// true

console.log(Function.prototype.__proto__ === Object.prototype);// true

console.log(Person.__proto__.__proto__ === Object.prototype);// true

console.log(Object.prototype.__proto__ === null);// true

console.log(Person.__proto__.__proto__.__proto__ === null);// true

可以看到,原型链的最终指向是null

实际使用

  • 读取对象的hasOwnProperty方法:

    function Person(){
      this.name = "一路星河";
    };
    let person = new Person();
    
    console.log(person.name);// "一路星河"
    
    // Object的hasOwnProperty方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性
    console.log(person.hasOwnProperty("age"));// false
    console.log(person.hasOwnProperty("name"));// true
    

    我们可以看到,在构造函数内部,我们并没有声明hasOwnProperty方法,但是它执行了,并且根据传入的参数不同,返回了不同的结果!我们来按步骤分析一下它的执行过程:

    1. person本身查找hasOwnProperty方法,没找到;

    2. person.__proto__(同Person.prototype)上查找hasOwnProperty方法,没找到;

    3. person.__proto__.__proto__(同Object.prototype)上查找hasOwnProperty方法,找到了;

    4. 执行person.hasOwnProperty(key)

    5. 至此,整个原型链的查找过程结束。

    6. 假设,在第3步的时候依然没有找到,那么再深入去查找person.__proto__.__proto__的原型对象的结果就为null了,因为Object.prototype.__proto__=null已经到头了。此时,在整个原型链中都没有找到hasOwnProperty方法,所以不会正常打印结果(结果为undefined或报错)。

    当然,我们可以重写hasOwnProperty方法。比如我们对上面的代码进行一番改造:

    • function Person(){
        this.name = "一路星河";
      };
      let person = new Person();
      
      console.log(person.name);// "一路星河"
      
      // 此时,hasOwnProperty方法会在第1步被找到
      person.hasOwnProperty = function(key){
        return key;
      };
      
      // Object的hasOwnProperty方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性
      console.log(person.hasOwnProperty("age"));// "age"
      console.log(person.hasOwnProperty("name"));// "name"
      
    • function Person(){
        this.name = "一路星河";
        
        // 声明方式一:此时,hasOwnProperty方法会在第2步被找到
        this.hasOwnProperty = function(key){
        	return key;
      	};
        
      };
      
      // 声明方式二:此时,hasOwnProperty方法会在第2步被找到
      Person.prototype.hasOwnProperty = function(key){
        	return key;
      };
      
      let person = new Person();
      
      console.log(person.name);// "一路星河"
      
      // Object的hasOwnProperty方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性
      console.log(person.hasOwnProperty("age"));// "age"
      console.log(person.hasOwnProperty("name"));// "name"