原型和原型链

145 阅读4分钟

原型 && 原型链

今天我们来聊聊原型和原型链,一个对象可以通过构造函数实例化(也就是常说的new 一个对象)出来。这个是实例化(new)出来的对象上我们可以看到__proto__这么个属性,他就是对象的原型,也可以说是对象构造函数的原型。 原型有什么用呢?简单的来说就是可以复用一部分的属性和方法。不用在每个创建的方法中都定义相同的方法。如下

function Mao(opt){
    this.name = opt.name
}
Mao.prototype.sayHello = function(){
    console.log('hello! my name is ' + this.name)
}

var a = new Mao({name: 'a'})
var b = new Mao({name: 'b'})
a.sayHello() // 'hello! my name is a'
b.sayHello() // 'hello! my name is b'

什么是原型链

那么什么是原型链呢?话不多说直接上图!! 原型链结构.png

自己随便画的一个原型链的图,也不知道大家看不看得懂,好的我来解释一下 src=http___www.xiaocile.com_d_file_tic_202006_ntdr5o21zuy.jpg&refer=http___www.xiaocile.jpeg 首先我们记住几个定律

  • 每个对象(null除外)都有__proto__属性,且函数对象都有prototype属性
  • 每个__proto__指向构造函数的 prototype (Object.prototype._proto_ 指向 null )
  • 每个构造函数的原型(prototype)都有一个constructor,constructor 指向的是构造函数本身

上图中,黄色的部分代表函数对象的 prototype 属性,实例是没有 prototype 属性,它的__proto__指向的是它的构造函数的prototype属性。

  • var mao = new Mao(); 定义的 实例 mao 的 __proto__ 属性是指向 它的构造函数 Mao.prototype => mao.__proto__ === Mao.prototype
  • Mao.prototype 下的 constructor 属性指向的是构造函数本身 => Mao.prototype.constructor === Mao
  • prototype 也是一个对象 也有自己的__proto__,它指向的是它构造函数的prototype 也就是Object.prototype => Mao.prototype.\_\_proto__ === Object.prototype
  • 构造函数Mao 也是函数对象,也有自己的__proto__,指向自己的构造函数的prototype 也就是Function.prototype => Mao.\_\_proto__ === Function.prototype
  • Object.prototype.__proto__ 怎么办呢?按定律是应该等于自己构造函数的prototype,难道Object.prototype.__proto__ === Object.prototype ?? 看着都怪怪的。真实打印出来Object.prototype.__proto__ 是为null的 所以原型链的顶部是 null

原型链的作用

原型链有什么用呢?先解释下原型链访问属性的原理
当你访问一个对象的属性时,如果对象本身就有这个属性,那么就可以直接访问到。如果没有该属性,就会访问对象原型,查找原型上是否存在该属性

function Mao(){
    
}
Mao.prototype.a = 'prototype value a'
var m = new Mao()
m.a // 'prototype value a'
m.b // undefined
Object.prototype.a = 'Object prototype value a'
Object.prototype.b = 'Object prototype value b'
m.a // 'prototype value a'
m.b // Object prototype value b

上面代码的例子可以看出,a属性在Mao构造函数的原型上,实例m本身并没有a属性,所以会找自己的原型上是否有a属性,有就取到值为 'prototype value a',查找b属性,发现对象本身和原型上都没有,那么就会顺着构造函数的原型链向它的上一级也就是 Mao.prototype.\_\_proto__ => Object.prototype上查找,在我赋值前是没有的,所以返回的是undefined,赋值后发现在Object.prototype上可以找到b属性就取b属性的值,同时我在Object.prototype上赋值了a属性。但是仍然取值是构造函数的原型上的a,所以就验证了原型链取值的就近原则

通过原理不难看出,原型链的作用是可以继承原型链上的方法。只要原型链向上的方法在原型链底部的对象都可以直接调用,不用反复声明相同的方法。

instanceof 和 typeof

再来跟大家探讨个和原型链相关的 js 内置方法 instanceof。提到instanceof 自然就会想到typeof,他们都是用来判断一个变量的类型的,先来看看typeof

  console.log(typeof 1) // number
  console.log(typeof '1') // string
  console.log(typeof true) // boolean
  console.log(typeof undefined) // undefined
  console.log(typeof null) // object
  console.log(typeof {}) // object
  console.log(typeof []) // object

typeof 只能判断基础类型的变量,null 、{}、[]都输出 object 那么我就没办法精准的判断变量类型 再来看看 instanceof

    console.log(1 instanceof Number) // false
    console.log('1' instanceof String) // false
    console.log(true instanceof Boolean) // false
    console.log(undefinded instanceof Object) // false
    console.log(null instanceof Object) // false
    console.log({} instanceof Object) // true
    console.log([] instanceof Array) // true

上面的结果,我们可以看出 instanceof 不能判断基础类型的变量,但是可以判断引用类型的变量。

typeof 和 instanceof 实现原理

通过上面的两个代码的输出,这里可以看到一个有趣的地方。

typeof null // object

null instanceof Object // false

这是啥情况,不是说好的null是object类型么,查看null的instanceof是不是object 又是false

玩呢.jpeg 其实这里存在着一个JavaScript多年来的bug。我们先来说说typeof的原理:
不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息

  • 000: 对象
  • 010: 浮点数
  • 100: 字符串
  • 110: 布尔值
  • 1: 整数 而 null 存储系统内存中对应的2进制全是0,也被 typeof 当成了对象
    instanceof 的原理:
    判断左边的实例的原型是否是右边构造函数的原型。通俗的来讲就是左边的对象是否是右边构造函数的实例。话不多说直接实现一个instanceof就明白了

src=http___pic3.zhimg.com_v2-75629bc442323fddd3455cab4a4eec16_b.jpg&refer=http___pic3.zhimg.jpeg

function instanceof(leftVal, rightVal) {
    if(leftVal === null) return false;
    let leftValuePro = leftVal.__proto__
    if(leftValuePro === rightVal.prototype) {
        return true;
    } else {
        return instanceof(leftValuePro.__proto__, rightVal)
    }
}

所以就可以解释JavaScript的这个bug null 没有办法取到 .__proto__ leftVal 等于null直接返回false