原型 && 原型链
今天我们来聊聊原型和原型链,一个对象可以通过构造函数实例化(也就是常说的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'
什么是原型链
那么什么是原型链呢?话不多说直接上图!!
自己随便画的一个原型链的图,也不知道大家看不看得懂,好的我来解释一下
首先我们记住几个定律
- 每个对象(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
其实这里存在着一个JavaScript多年来的bug。我们先来说说typeof的原理:
不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息
- 000: 对象
- 010: 浮点数
- 100: 字符串
- 110: 布尔值
- 1: 整数
而 null 存储系统内存中对应的2进制全是0,也被 typeof 当成了对象
instanceof 的原理:
判断左边的实例的原型是否是右边构造函数的原型。通俗的来讲就是左边的对象是否是右边构造函数的实例。话不多说直接实现一个instanceof就明白了
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