彻底贯通原型链

文章目录

原型链的串联及扩展

原型

原型:每一个对象都从原型继承属性。每个对象都有 proto 属性 , 每个对象都有原对象,但只有函数对象才有 prototype 属性, 但是除却 function.prototype,function.prototype 也是函数对象,但是没有 prototype。可以使用 p.isPrototype(o) 来检查 p 是否是 o 的原型。

原型模式

  1. proto、 constructor 属性是对象所独有的;
  2. prototype 属性是函数独有的;
  3. 上面说过 js 中函数也是对象的一种,那么函数同样也有属性proto、 constructor;
1
2
3
4
5
function a() {}
var b = new a()
console.log(b.prototype) //undefined
console.log(b instanceof Function) //false
console.log(b instanceof Object) //true

原型模式是 js 对继承的一种实现

  • prototype:构造函数中的属性,指向该构造函数的原型对象。

  • constructor :原型对象中的属性,指向该原型对象的构造函数

  • _proto_:实例中的属性,指向 new 这个实例的构造函数的原型对象,对象可以通过_proto_来寻找不属于该对象的属性,_proto_将对象连接起来组成原型链。所有引用类型的proto属性值均指向它的构造函数的 prototype 的属性值。当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去他的proto(即它的构造函数的 prototype)中寻找

prototype 属性的引入

prototype 是一个对象

每一个 new 出的实例都有自己的属性和方法的副本,无法做到属性、方法共享,因此 Brendan Eich 决定为构造函数设置一个 prototype 属性。

这个对象包含一个对象(以下简称 “prototype 对象 ”),所有实例对象需要共享的属性及方法,都放在这个对象里面,那些不需要共享的属性及方法,就放在构造函数里面。

实例对象一旦创建,就自动引用 prototype 对象的属性和方法。也就是说。实例对象的属性和方法,分成两种,一种是本地的,一种是引用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function DOG(name) {
this.name = name
}
DOG.prototype = { species: '犬科' }

var dogA = new DOG('大毛')
var dogB = new DOG('二毛')

alert(dogA.species)
// 其实是通过dogA._proto_.species 来访问DOG.prototype.species
alert(dogB.species)
// 犬科
DOG.prototype
//{species:''犬科',constructor:fDOG(name),_proto_:Object}
DOG.prototype.constructor === DOG
//true

现在,species 属性放在 prototype 对象里,是两个实例对象共享的。只要修改了 prototype 对象,就会同时影响到两个实例对象。

network

一张图读懂完整的原型链关系**

network

原型链的作用:对象属性的访问修改和删除。

  • 访问。优先在对象本身查找,没有则顺着原型链向上查找
  • 修改。只能修改跟删除自身属性,不会影响到原型链上的其他对象。

constructor

constructor 属性获取创造自己的构造函数

1
2
3
4
5
var Parent = function () {}
//定义一个函数,那它只是一个普通的函数,下面我们让这个函数变得不普通
var p1 = new Parent()
//这时这个Parent就不是普通的函数了,它现在是一个构造函数。因为通过new关键字调用了它
//创建了一个Parent构造函数的实例 p1

network

proto (原型指针)

_proto** 和 prototype 的关系是 **proto__ 是 prototype 的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
alert(this.name)
}
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer')
var person2 = new Person('Mick', 23, 'Doctor')
person1.__proto__ == Person.prototype
//person1.__proto__ = person1.constructor.prototype , person1.constructor = Person
Person.__proto__
//Person.constructor = Function => Person.__proto__ = Function.prototype
Person.prototype.__proto__
// Person.prototype 是一个普通对象(原型对象),普通函数的构造函数是Object => Person.prototype.__proto__ = Object.prototype
Object.__proto__
//普通对象 同上
Object.prototype.__proto__
//对象也有proto属性,但它比较特殊,为null,因为null处于原型链的顶端

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做 proto 的内置属性,用于指向创建它的构造函数的原型对象 ,原型链继承是通过 proto 这个原型指针来完成的

原型链图

褐色的线为原型链 network

够造函数、原型和实例的关系:

  1. 每个构造函数都有一个原型对象(x.prototype)
  2. 原型对象都包含一个指向构造函数的指针(x.prototype.constructor === x)
  3. 实例都包含一个指向原型对象的内部指针(a.proto

** 所有函数的默认原型都是 Object 的实例**

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上——MDN

instanceof 表示某个变量是否是某个对象的实例

1
2
3
4
5
6
7
8
9
10
11
12
function Car(make, model, year) {
this.make = make
this.model = model
this.year = year
}
const auto = new Car('Honda', 'Accord', 1998)

console.log(auto instanceof Car)
// expected output: true

console.log(auto instanceof Object)
// expected output: true

instanceof 可以正确的判断对象类型,因为内部机制是通过对象的原型链中是不是能找到类型的 prototype

1
2
3
4
console.log([] instanceof Arrage) //true
console.log({} instanceof Object) //true
console.log(function () {} instanceof Function) //true
console.log(1 instanceof NUmber) //true

network

null instanceof Object 为 false

1
2
console.log(typeof null) //Object
console.log(null instanceof Object) //false

这是历史遗留的问题,对于 null 值,typeof null 是出了 BUG,但是本质上 null 和 Object 不是同一个类型,null 值并不是以 Object 为原型创建的,恰恰相反Object.prototype.__proto__ = null

instanceof 的底层实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
function instanceof(l, r) {
let o = r.prototype
l = l._proto_
while (true) {
if (l === Null) {
return false
}
if (l === o) {
return false
}
l = l._proto_
}
}

instanceof 与 constructor 的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log((1).constructor === Number) //true
console.log('1'.constructor === String) //true
console.log(function () {}.constructor === Function) //true
console.log([].constructor === Array) //true
console.log({}.constructor === Object) //true
function Func() {}

Func.prototype = new Array()

const f = new Func()

console.log(f.constructor === Function) //false
console.log(f.constructor === Array) //true
console.log(f instanceof Function) //true
console.log(f instanceof Array) //false

Object.prototype.toString.call

使用 Object 对象的原型方法 toString,返回值是[object 类型]字符串,该方法基本上能判断所有的数据类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var toString = Object.prototype.toString

toString.call({})
//"[object Object]"
toString.call([])
//"[object Array]"
toString.call(null)
//"[object Null]"
toString.call(undefined)
//"[object Undefined]"
toString.call(function () {})
//"[object Function]"
toString.call(NaN)
//"[object Number]"

特殊的箭头函数

箭头函数没有自己的 this,arguments,super 或 new.target。箭头函数表达式更实用于那些需要匿名函数的地方,并且它不能用作构造函数,和 new 一起用会抛出错误,箭头函数没有 prototype 属性。

箭头函数是有-proto-属性的,所以箭头函数本身是存在原型链的,他也是有自己的构造函数的,但是因为没有 prototype 属性,他的实例-proto-没法指向,所以箭头函数也就无法作为构造函数。

同时箭头函数由于没有 this 指针,通过 call()和 apply 方法调用一个函数时,只能传递参数,不能绑定 this。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = () => {
this.name = 'a'
}
var b = function () {
this.name = 'b'
}
var c = new a() //TypeError: a is not a constructor
var d = new b()
typeof a //function
console.log(a.prototype) //undefined
console.log(d._proto_) //{constructor: ƒ}
console.log(a._proto_) //ƒ () { [native code] }
a instanceof Object //true
a instance of Function //true

文章扩展

分享到:
network