js中的原型继承

作者 likaiqiang 日期 2017-12-05
js中的原型继承

前言

js里面几乎一切都是对象

什么是对象

ECMA-262把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数

为什么说js里面几乎一切都是对象

这里为什么说几乎,因为不包括null和undefined。其他的,包括字符串、数字、布尔值、数组甚至是函数都有属性和方法,所以他们都是对象。

js里面的数据类型

基础类型

var a = 123
var b = "123"
var c = false

a,b,c属于基础类型

包装类型

var d = []
var o = {}
var f = function(){}

d,o,f属于包装类型

基础包装类型

var x = new Number(123)
var y = new String("123")
var z = new Boolean(false)

x,y,z属于包装类型

上面说js里面字符串、数字、布尔值都是对象,都拥有属性和方法,我们尝试给基本类型的数据添加属性

var a = 123
a.f = function(){}
console.log(a.f) //undefined

这是怎么回事,不是都是对象吗,为什么不能动态添加属性和方法。

其实我们之所以能访问到基本类型数据的属性和方法

var a = 123
console.log(a.toString) //ƒ toString() { [native code] }

是因为在我们访问的一瞬间,js帮我们把基础类型转化成它对应的基础包装类型,访问结束会销毁掉这个基础包装类型对象,这也是为什么不能给基础类型的数据动态添加属性和方法的原因。

类似下面这个过程

var a = 123
var b = new Number(a)
console.log(b.toString)
b = null

js里的函数

js里面函数是第一等公民,为什么这么说?

因为函数也是对象,自然有属性和方法。函数还可以赋值给一个变量,可以塞到一个数组中,可以作为其他对象的属性,可以作为参数传递给另一个函数。其他数据能做的它能做,其他数据不能做的它也能做,简直无所不能,所以说函数是第一等公民。

js里的继承

new操作符

js里每个引用类型的对象都有一个proto属性,每个函数都有一个prototype属性,所以函数这种既是函数又是引用类型的数据类型就同时拥有proto与prototype。

假如有这样一个函数

function f(a,b){
this.a = a
this.b = b
}
var f1 = new f(1,2)
console.log(f1)

对f函数使用new操作符后,到底发生了什么。我们先不用new 操作符,直接调f函数

function f(a,b){
this.a = a
this.b = b
}
f(1,2)
console.log(window.a)//1
console.log(window.b)//2

可见f函数里面的this默认指的是window,对f函数使用new操作符后,它先生成了一个空对象o,然后让this指向o,刚才说了javascript里面每个对象都有一个proto属性,o也不例外,再把f函数的prototype属性指向的引用赋值给o对象的proto,然后执行this.a = 1;this.b = 2,最后返回这个对象。

这个过程类似于下面的代码

function f(a,b){
this.a = a
this.b = b
}
var f1 = new f(1,2)
==>
/*伪代码*/
new function f (a,b){ //a=1;b=2
var o = {} //生成空对象o
this = {} //让this指向这个空对象
o.__proto__ = f.prototype
this.a = 1 //o.a = 1
this.b = 2 //o.b = 2
return o
}

原型链

我们定义一个数组[],我们并没有赋给它push、pop等方法,为什么它可以调用这些方法,秘密就在proto属性上,proto属性指向该对象的原型,说白了,就是一个对象,这个对象中存有生成这个数组的构造函数和一些公用方法。这就是为什么我们没有给[]任何属性和方法,它却可以调用push、pop的原因。一个对象如果本身不含有某个方法或者属性,就会查找它的原型,找到了就执行方法,找不到就继续查找原型。对象的原型也是一个对象,是对象就会有原型,所以我们的原型链最终会指向Object的prototype。

说了这么多,用代码表示这一过程

var arr = []
arr.__proto__ == Array.prototype // true
arr.__proto__.__proto__ == Object.prototype // true

instanceof

a instanceof b 表示a是否是b的实例

[] instanceof Array // true

它的实现原理:判断a的原型链上是否有b的prototype

图太长,截不下

[] instanceof Array // true
[] instanceof Object // true

因为[]的原型链上有Array和Object的prototype

写到这里,突然想到用[] instanceof Array判断一个对象是否是数组有点不靠谱。假如有这样一个对象

var o = {
__proto__:Array.prototype
}
o instanceof Array // true

假如有人这样造一个假数组,instanceof就不准了,所以判断对象是否是数组还是用Array.isArray([])比较靠谱。

前两天在知乎上看到这样一道题:

js instanceof 疑惑?

有兴趣的话可以去围观

如果让我来解释的话:

[] instanceof Array // true,[].__proto__==Array.prototype
Array instanceof Function // true,Array.__proto__==Function.prototype
[] instanceof Function // false

[]的原型链

Function的prototype

在[]的原型链上找不到Function的prototype,所以说[] instanceof Function 的值为false

参考资料

javascript面向对象编程指南