原型与原型链

200 阅读5分钟

原型

什么是原型?

原型是 function 对象的一个属性,函数.prototype,原型定义了构造函数制造出的对象的公共属性。通过该构造函数产生的对象,可以继承该原型的属性方法,原型也是对象。

展开说下:

  1. 每个 函数(包括构造函数) 都有一个 prototype 属性,指向函数的原型对象。

这个 原型对象constructor 属性,指向函数本身

function Person(name) {
  this.name = name
}
var person = new Person('Steve') // 自定义构造函数
var string = new String('Hello') // 原生的构造函数

console.log(Person.prototype.constructor === Person)
console.log(String.prototype.constructor === String)

/*
 * 执行结果:
 * true
 * true
**/
  1. 在浏览器中每个 实例对象 会提供__proto__ 属性,指向它的构造函数的原型对象。

    所有函数(内置或自定义)的__proto__都指向Function.prototype(Function.prototype是一个空函数)。

    实例的__proto__总是指向其构造函数的prototype,__proto__是实例与构造函数的原型之间的关系,而不是实例与构造函数之间的关系

注意:_proto__目前在IE6/7/8/9中都不支持,IE9中可以使用Object.getPrototypeOf获取对象的内部原型。

function Person(name) {
    this.name = name
}
var person = new Person('Steve') // 自定义构造函数
var string = new String('Hello') // 原生的构造函数

console.log(person.__proto__ === Person.prototype)
console.log(string.__proto__ === String.prototype)
console.log(Object.getPrototypeOf(string) === String.prototype)
console.log(Person.__proto__ === Function.prototype)
console.log(String.__proto__ === Function.prototype)
/*
 * 执行结果:
 * true
 * true
 * true
 * true
 * true
**/
  1. 每个 实例对象 都有一个 constructor 属性,指向它的构造函数
function Person(name) {
    this.name = name
}
var person = new Person('Steve') // 自定义构造函数
var string = new String('Hello') // 原生的构造函数

console.log(person.constructor === Person)
console.log(string.constructor === String)

/*
 * 执行结果:
 * true
 * true
**/

构造函数、实例对象、原型对象 关系图如下:

注意:

当我们在查找对象的属性或方法时会首先在自己里面找,如果找不到会找 __proto__ 指向的原型,这样就把对象和原型连接到了一起。 __proto__存的是对象的原型,或者换句话说每个对象都有一个__proto__指向构造它的原型, 由此我们可以发现 __proto__指向的构造函数是可以修改的,因此person的构造函数也就未必是Person()了,也就是说Person()这个构造函数构造出来的对象的原型不一定是Person.prototype。

```javascript
function Person() {}
Person.prototype.name = "script"
var obj = {
	name:"script"
}
var person = new Person()
person.__proto__ = obj
person.__proto__ != Person.prototype
```

原型链

  1. 上面我们说过,所有对象都有 __proto__ 属性,并且这个 __proto__ 属性指向一个原型对象

因为原型对象也是对象,这个原型对象也有 __proto__ 属性,我们把这种关系称为原型链。

s=new String('a')
s.__proto__ === String.prototype
s.__proto__.__proto__ === Object.prototype
s.__proto__.__proto__.__proto__  === null
  1. 当需要访问一个对象的属性时,首先从该对象开始查找,如果能够找到,那么到此返回

如果没有找到,就在该对象的 __proto__ 属性指向的原型对象中继续查找,如果能够找到,那么到此返回

如果没有找到,那么一直往上查找原型对象,直至 __proto__ 属性指向 null,也就是原型链的顶端

若原型链上的所有原型对象都没有该属性,则返回 undefined

function Person(name) { this.name = name }
Person.prototype.getName = function() { return this.name }
var person = new Person('Steve')

var name = person.getName() // 在 Person.prototype 中找到
var description = person.toString() // 在 Object.prototype 中找到
var age = person.age // 在原型链中无法找到

console.log(name)
console.log(description)
console.log(age)

/*
 * 执行结果:
 * Steven
 * [object Object]
 * undefined
**/
  1. 属性检测

in 操作符:检测属性是否在对象的原型链上

hasOwnProperty 方法:检测属性是否来自对象本身

function Person(name) { this.name = name }
Person.prototype.getName = function() { return this.name }
var person = new Person('Steve')

console.log('age' in person) 			//false
console.log('name' in person) 		//true
console.log('getName' in person)		//true
console.log('toString' in person)		//true

console.log(person.hasOwnProperty('age'))			//false
console.log(person.hasOwnProperty('name'))		//true
console.log(person.hasOwnProperty('getName'))		//false
console.log(person.hasOwnProperty('toString'))	//false

  1. 应用:

    1. 为什么要使用原型?

    JavaScript 中的继承是基于原型的,原型的作用在于不同实例对象之间可以共享数据,节省内存。用__proto__来连接多个原型就是原型链了。

       function Grand() {
         this.word = "爷";
        }
        var grand = new Grand();
        Father.prototype = grand;
        //grand是用Grand()创建的一个对象实例,它是个对象
        function Father() {
           this.name = "父";
        }
        var father = new Father();
        Son.prototype = father;
        // 同上,直接给原型赋值一个对象
        function Son() {
           this.hobbit = "子";
        }
        var son = new Son();
    

    son实例对象追溯__proto__一直到Object发现已经没有__proto__属性

    2.还需注意原型是隐式的内部属性,只有系统给我们的才能用,假如我们自己在一个没有原型的对象中添加了__proto__属性,系统是不能识别的

     var obj = Object.create(null);
      obj.__proto__ = {
         name: "sunny"
      }
      obj.name === undefined
    

    绝大多数对象最终都会继承自Object.prototype,但不是全部!

    1. instanceof 原理正是应用的原型链查询。 注意,instanceof运算符只能用于对象,不适用原始类型的值。
    function instanceof(L, R) { //L是表达式左边,R是表达式右边
      const O = R.prototype;
      L = L.__proto__;
      while(true) {
          if (L === null)
              return false;
          if (L === O) // 这里重点:当 L 严格等于 0 时,返回 true 
              return true;
          L = L.__proto__;
      }
    }
    

    (a== 1 && a ==2 && a==3)可能为true吗?

      let a = {
      i: 1,
      toString () {
        return a.i++
      }
      }
    
      if(a == 1 && a == 2 && a == 3) {
        console.log('Hello World!');
      }
      // 输出Hello World!
    

这里说下具体的过程:执行a==1时,js引擎会尝试把对象类型a转化为数字类型,首先调用a的valueOf方法来判断,不行则继续调用toString方法,然后再把toString返回的字符串转化为数字类型再去和a作比较(这里我重写了toString就直接返回的数字类型的结果,正常情况toString返回的字符串)。 所以每一次使用==判断都会调用一次a的toString方法,返回i属性的值,然后使a的i属性加1,这样最后的判断结果自然就为true了。 其实重写valueOf方法也可以实现,而且转化时会优先调用valueOf方法: