六、JavaScript原型及原型链和ES5中实现继承的方案
Lyk 2022/8/1 JS高级对象的原型函数的原型constructor属性原型链原型链实现继承借用构造函数继承原型式继承函数寄生式继承函数寄生组合式继承对象的方法补充构造函数的实例方法构造函数的类方法Object.create()
# 1、认识对象的原型
- JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。
- 那么这个对象有什么用呢?
- 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
- 这个操作会首先检查该对象是否有对应的属性,如果有的话就使用它;
- 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;
- 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
- 答案是有的,只要是对象都会有这样的一个内置属性;
- 获取的方式有两种:
- 方式一:通过对象的
__proto__
属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题); - 方式二:通过 Object.getPrototypeOf 方法可以获取到;
- 方式一:通过对象的
# 2、函数的原型 prototype
- 那么我们知道上面的东西对于我们的构造函数创建对象来说有什么用呢?
- 它的意义是非常重大的,接下来我们继续来探讨;
- 这里我们又要引入一个新的概念:所有的函数都有一个prototype的属性(注意:不是
__proto__
) - 你可能会问:是不是因为函数是一个对象,所以它有prototype的属性呢?
- 不是的,而是因为它是一个函数,所以才有这个特殊的属性;(显式原型属性prototype)
- 而对象是没有这个特殊属性:prototype 的;
- 补充:因为函数也是对象,所以函数也有隐式原型属性
__proto__
var obj = {}
console.log(obj.prototype)//undefined 对象没有这个prototype属性
function foo() {
}
console.log(foo.__proto__)//ƒ () { [native code] }
console.log(foo.prototype)//{constructor: ƒ}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3、再看new操作符
- 我们前面讲过new关键字的步骤如下:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;
- 那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype:
function Person(name,age) {
this.name = name,
this.age = age
}
var p1 = new Person('lyk',26)
var p2 = new Person('kobe',38)
console.log(p1.__proto__ === Person.prototype)//true
console.log(p2.__proto__ === Person.prototype)//true
console.log(p1.__proto__ === p2.__proto__)//true
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4、new创建对象后,新增属性的内存表现
function Person(name,age) {
this.name = name,
this.age = age
}
Person.prototype.address = "中国"
Person.prototype.info = "中国很美丽"
Person.prototype.running = function() {}
var p1 = new Person('why',18)
var p2 = new Person('kobe',30)
console.log(p1.address)//中国
// 新增属性
p1.height = 1.88
p1.address = "广州市"
p2.isAdmin = true
console.log(p1.address)//广州市
console.log(p2.address)//中国
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5、constructor属性
- 事实上原型对象上面是有一个属性的:constructor
- 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象;
function Person() {}
var p1 = new Person()
console.log(Person.prototype.constructor)//ƒ Person() {}
console.log(p1.__proto__.constructor)//ƒ Person() {}
console.log(p1.__proto__.constructor.name)//Person
1
2
3
4
5
2
3
4
5
# 6、重写原型对象
- 如果我们需要在原型上添加过多的属性,通常我们会重写整个原型对象:
function Person() {}
Person.prototype = {
name:'kobe',
age:38,
running() {
console.log(this.name,'在跑步呢')
}
}
console.log(Person.prototype.constructor.name)//Object
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 前面我们说过, 每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性;
- 而我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函数, 而不是Person构造函数了
# 7、原型对象的constructor
- 如果希望constructor指向Person,那么可以手动添加:
function Person() { }
Person.prototype = {
constructor: Person,
name: 'kobe',
age: 38,
running() {
console.log(this.name, '在跑步呢')
}
}
console.log(Person.prototype.constructor.name)//Person
console.log(Object.getOwnPropertyDescriptor(Person.prototype,'constructor'))//{writable: true, enumerable: true, configurable: true, value: ƒ}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 上面的方式虽然可以, 但是也会造成constructor的[[Enumerable]]特性被设置了true.
- 默认情况下, 原生的constructor属性是不可枚举的.
- 如果希望解决这个问题, 就可以使用我们前面介绍的Object.defineProperty()函数了.
function Person() { }
Person.prototype = {
name: 'kobe',
age: 38,
running() {
console.log(this.name, '在跑步呢')
}
}
Object.defineProperty(Person.prototype,'constructor',{
enumerable:false,
configurable:true,
writable:true,
value:Person
})
console.log(Person.prototype.constructor.name)//Person
console.log(Object.getOwnPropertyDescriptor(Person.prototype,'constructor'))//{writable: true, enumerable: false, configurable: true, value: ƒ}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 8、创建对象 – 构造函数和原型组合
- 我们在上一个构造函数的方式创建对象时,有一个弊端:会创建出重复的函数,比如running、eating这些函数
- 那么有没有办法让所有的对象去共享这些函数呢?
- 可以,将这些函数放到Person.prototype的对象上即可;
function Person(name,age,address) {
this.name = name,
this.age = age,
this.address = address
}
Person.prototype.running = function() {
console.log(this.name,'在跑步')
}
Person.prototype.eating = function () {
console.log(this.name,'在吃东西')
}
var p1 = new Person('kobe',38,'洛杉矶')
var p2 = new Person('james',37,'克城')
p1.eating()//kobe 在吃东西
p2.running()//james 在跑步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 9、面向对象的特性 – 继承
- 面向对象有三大特性:封装、继承、多态
- 封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
- 多态:不同的对象在执行时表现出不同的形态;
- 那么这里我们核心讲继承。
- 那么继承是做什么呢?
- 继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可;
- 在很多编程语言中,继承也是多态的前提;
- 那么JavaScript当中如何实现继承呢?
- 不着急,我们先来看一下JavaScript原型链的机制;
- 再利用原型链的机制实现一下继承;
# 10、原型链
# 10.1.JavaScript原型链
- 在真正实现继承之前,我们先来理解一个非常重要的概念:原型链。
- 我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取:如下案例
var obj = {
name:'why',
age:18
}
obj.__proto__ = {}
obj.__proto__.__proto__ = {}
obj.__proto__.__proto__.__proto__ = {
address:'北京市'
}
console.log(obj.address)//北京市
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 10.2.Object的原型
- 那么什么地方是原型链的尽头呢?比如第三个对象是否也是有原型
__proto__
属性呢?
console.log(obj.__proto__.__proto__.__proto__.__proto__)//[Object: null prototye] {}
1
- 我们会发现它打印的是 [Object: null prototype] {}
- 事实上这个原型就是我们最顶层的原型了
- 从Object直接创建出来的对象的原型都是 [Object: null prototype] {}。
- 那么我们可能会问题: [Object: null prototype] {} 原型有什么特殊吗?
- 特殊一:该对象有原型属性,但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
- 特殊二:该对象上有很多默认的属性和方法;
var obj = {
name: 'why',
age: 18
}
obj.__proto__ = {}
obj.__proto__.__proto__ = {}
obj.__proto__.__proto__.__proto__ = {
address: '北京市'
}
console.log(obj.address)//北京市
console.log(obj.__proto__.__proto__.__proto__.__proto__ === Object.prototype)//ture
console.log(Object.prototype.__proto__)//null
console.log(obj.__proto__.__proto__.__proto__.__proto__.__proto__)//null
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 10.3.创建Object对象的内存图
# 10.4.原型链关系的内存图
# 10.5.Object是所有类的父类
- 从我们上面的Object原型我们可以得出一个结论:原型链最顶层的原型对象就是Object的原型对象
function Person(name,age) {
this.name = name,
this.age = age
}
Person.prototype.running = function() {
console.log(this.name,'running')
}
var p1 = new Person('why',18)
console.log(p1)
console.log(p1.valueOf())
console.log(p1.toString())
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 11、原型链实现继承
# 11.1.通过原型链实现继承
- 如果我们现在需要实现继承,那么就可以利用原型链来实现了:
- 目前stu的原型是p对象,而p对象的原型是Person默认的原型,里面包含running等函数;
- 注意:步骤4和步骤5不可以调整顺序,否则会有问题
// 1.定义父类构造函数
function Person() {
this.name = 'why'
}
// 2.父类原型上添加内容
Person.prototype.running = function () {
console.log(this.name, 'running')
}
//3.定义子类构造函数
function Student() {
this.sno = 111
}
// 4.创建父类对象,并且作为子类的原型对象
var p = new Person()
Student.prototype = p
// 5.在子类原型上添加内容
Student.prototype.studying = function() {
console.log(this.name,'studying')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 11.2.继承创建对象的内存图
# 11.3.原型链继承的弊端
但是目前有一个很大的弊端:某些属性其实是保存在p对象上的;
- 第一,我们通过直接打印对象是看不到这个属性的;(结合下面案例:这个属性指:p对象的name属性)
- 第二,这个属性会被多个对象共享,如果这个属性是一个引用类型,那么就会造成问题;
// 1.定义父类构造函数 function Person() { this.name = 'why', this.friend = [ {name:'kobe'} ] } // 2.父类原型上添加内容 Person.prototype.running = function () { console.log(this.name, 'running') } //3.定义子类构造函数 function Student() { this.sno = 111 } // 4.创建父类对象,并且作为子类的原型对象 var p = new Person() Student.prototype = p // 5.在子类原型上添加内容 Student.prototype.studying = function() { console.log(this.name,'studying') } var stu1 = new Student() var stu2 = new Student() console.log(stu1.friend[0].name,stu1.name)//kobe why stu1.friend[0].name= 'james' stu1.name = 'curry' //friend属性被多个对象共享,而这个是一个引用类型,所以stu1改了之后,会造成问题 console.log(stu2.friend[0].name,stu2.name)//james why console.log(stu1,stu2)//Student {sno: 111, name: 'curry'} Student {sno: 111}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33- 第三,不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的(没办法定制化);
# 12、借用构造函数继承
- 为了解决原型链继承中存在的问题,开发人员提供了一种新的技术: constructor stealing(有很多名称: 借用构造函数或者称之为经典继承或者称之为伪造对象):
- steal是偷窃、剽窃的意思,但是这里可以翻译成借用;
- 借用继承的做法非常简单:在子类型构造函数的内部调用父类型构造函数.
- 因为函数可以在任意的时刻被调用;
- 因此通过apply()和call()方法也可以在新创建的对象上执行构造函数;
function Person(name,age) {
this.name = name,
this.age = age
}
function Student(name,age,sno) {
Person.call(this,name,age)//借用构造函数继承(继承属性)
this.sno = sno
}
var stu = new Student('lyk',23,102564)
console.log(stu)//Student {name: 'lyk', age: 23, sno: 102564}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 13、组合借用继承及问题
// 定义Person构造函数(类)
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
// 定义学生类
function Student(name, age, height, address, sno, score) {
// 重点: 借用构造函数
Person.call(this, name, age, height, address)
// this.name = name
// this.age = age
// this.height = height
// this.address = address
this.sno = sno
this.score = score
}
// 方式一: 父类的原型直接赋值给子类的原型
// 缺点: 父类和子类共享通一个原型对象, 修改了任意一个, 另外一个也被修改
// Student.prototype = Person.prototype
// 方式二: 创建一个父类的实例对象(new Person()), 用这个实例对象来作为子类的原型对象
var p = new Person("why", 18)
Student.prototype = p
// Student.prototype.running = function() {
// console.log("running~")
// }
// Student.prototype.eating = function() {
// console.log("eating~")
// }
Student.prototype.studying = function() {
console.log("studying~")
}
// 创建学生
var stu1 = new Student("kobe", 30, 111, '洛杉矶', 10220801002, 96)
var stu2 = new Student("james", 25, 111, '克城', 10220801001, 100)
stu1.running()
stu1.studying()
console.log(stu1.name, stu1.age)
console.log(stu1)
console.log(stu2.name, stu2.age)
console.log(stu1,stu1.__proto__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
组合继承是JavaScript最常用的继承模式之一:
- 如果你理解到这里, 点到为止, 那么组合来实现继承只能说问题不大;
- 但是它依然不是很完美,但是基本已经没有问题了;
组合继承存在什么问题呢?
组合继承最大的问题就是无论在什么情况下,都会调用两次父类构造函数。
- 一次在父类创建实例化对象 赋值给 子类原型的时候;
- 另一次在子类构造函数内部(也就是每次创建子类实例的时候);
另外,如果你仔细按照我的流程走了上面的每一个步骤,你会发现:所有的子类实例事实上会拥有两份父类的属性
- 一份在当前的实例自己里面(也就是stu本身的),另一份在子类对应的原型对象中(也就是
stu.__proto__
里面); - 当然,这两份属性我们无需担心访问出现问题,因为默认一定是访问实例本身这一部分的;
- 一份在当前的实例自己里面(也就是stu本身的),另一份在子类对应的原型对象中(也就是
# 14、原型式继承函数
- 原型式继承的渊源
- 这种模式要从道格拉斯·克罗克福德(Douglas Crockford,著名的前端大师,JSON的创立者)在2006年写的一篇文章说起:Prototypal Inheritance in JavaScript(在JavaScript中使用原型式继承)
- 在这篇文章中,它介绍了一种继承方法,而且这种继承方法不是通过构造函数来实现的.
- 为了理解这种方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法.
- 最终的目的:student对象的原型指向了person对象;
// 为了理解这种继承方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法.
var obj = {
name:'lyk',
age:23,
running() {
console.log(this.name,'running')
}
}
// 方法一
function object1 (obj) {
function Fn() {}
Fn.prototype = obj
return new Fn()
}
var objExtends1 = object1(obj)
objExtends1.running()//lyk running
console.log(objExtends1.name,objExtends1.age)//lyk 23
// 方法二
function object2 (obj) {
var newObj = {}
Object.setPrototypeOf(newObj,obj)//相当于:newObj.__proto__ = obj
return newObj
}
var objExtends2 = object2(obj)
objExtends2.running()//lyk running
console.log(objExtends2.name,objExtends2.age)//lyk 23
// 方法三
var student = Object.create(obj,{
address: {
value:'北京市',
enumerable:true
}
})
student.running()//lyk running
console.log(student)//{ address: '北京市' }
console.log(student.name,student.age)//lyk 23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 15、寄生式继承函数
- 寄生式(Parasitic)继承
- 寄生式(Parasitic)继承是与原型式继承紧密相关的一种思想, 并且同样由道格拉斯·克罗克福德(Douglas Crockford)提出和推广的;
- 寄生式继承的思路是结合原型类继承和工厂模式的一种方式;
- 即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回;
function Person(name,age) {
this.name = name
this.age = age
}
function createObject(obj) {
function Func() {}
Func.prototype = obj
return new Func()
}
var person = new Person('lyk',22)
function createStudent(person) {
var newObj = createObject(person)
newObj.studying = function() {
console.log(this.name,'studying')
}
return newObj
}
var obj1 = createStudent(person)
console.log(obj1)//Person { studying: [Function (anonymous)] }
obj1.studying()//lyk studying
console.log(obj1.name)//lyk
console.log(obj1.age)//22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 16、寄生组合式继承
- 现在我们来回顾一下之前提出的比较理想的组合继承
- 组合继承是比较理想的继承方式, 但是存在两个问题:
- 问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.
- 问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中.
- 事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉.
- 你需要先明确一点: 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中的属性和方法复制一份到了子类型中. 所以父类型本身里面的内容, 我们不再需要.
- 这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法.
- 能不能直接让子类型的原型对象 = 父类型的原型对象呢?不要这么做, 因为这么做意味着以后
- 修改了子类型原型对象的某个引用类型的时候, 父类型原生对象的引用类型也会被修改.
- 我们使用前面的寄生式思想就可以了.
//1.创建父类构造函数
function Person(name, age) {
this.name = name,
this.age = age
}
Person.prototype.running = function () {//实例方法
console.log(this.name, 'running')
}
Person.prototype.eating = function () {//实例方法
console.log(this.name, 'eating')
}
Person.flying = function() {//类方法
console.log(this.name,'flying')
}
// 2.创建子类构造函数
function Student(name, age, score) {
Person.call(this, name, age)
this.score = score
}
Student.prototype.studying = function () {
console.log(this.name, 'studying')
}
// 3.实现子类的实例化对象,继承父类原型上的方法(即实例方法)
// 打印构造函数原型的数据属性描述符的默认值
console.log(Object.getOwnPropertyDescriptor(Person.prototype, 'constructor'))//{enumerable:false,configurable:true,writable:true,value:f Person(name, age)}
function createObj(o) {
function Fn() { }
Fn.prototype = o
return new Fn()
}
function inherit(subClass, superClass) {//
// 方法一:直接用Object类方法:Object.create()
// subClass.prototype = Object.create(superClass.prototype, {
// constructor: {
// enumerable: false,
// configurable: true,
// writable: true,
// value: subClass
// }
// })
// 方法二:自己封装的方法
subClass.prototype = createObj(superClass.prototype)
Object.defineProperty(subClass.prototype,'constructor',{
enumerable:false,
configurable:true,
writable:true,
value:subClass
})
}
inherit(Student, Person)
var stu1 = new Student('lyk', 23, 98)
console.log(stu1)//Student { name: 'lyk', age: 23, score: 98 }
stu1.running()//lyk running
//4.实现子类继承父类的类方法
Object.setPrototypeOf(Student,Person)//相当于 Student.__proto__ = Person
Student.flying()//Student flying
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 17、对象的方法补充
hasOwnProperty
语法:
obj.hasOwnProperty(prop)
对象是否有某一个属于自己的属性(不是在原型上的属性)
function Person(name,age) {
this.name = name,
this.age = age
}
Person.prototype.address = "北京"
var p = new Person('kobe',23)
console.log(p)
console.log(p.hasOwnProperty('name'))//true
console.log(p.hasOwnProperty('address'))//false
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- in/for in 操作符
- 判断某个属性是否在某个对象或者对象的原型上
function Person(name,age) {
this.name = name,
this.age = age
}
Person.prototype.address = "北京"
var p = new Person('kobe',23)
console.log(p)
console.log('name' in p)//true
console.log('address' in p)//true
for(var key in p){
console.log(key)//name age address
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- instanceof
- 用于检测构造函数(Person、Student类)的pototype,是否出现在某个实例对象的原型链上
function Person(name, age) {
this.name = name,
this.age = age
}
Person.prototype.running = function () { console.log(this.name, 'runnig') }
function Student(name, age) {
Person.call(this, name, age)
}
Student.prototype = Object.create(Person.prototype{
constructor: {
value: Student,
enumerable: false,
configurable: true,
writable: true
}
})
function Teacher(name, age) {
Person.call(this, name, age)
}
var p = new Student('kobe', 23)
console.log(p)
p.running()
console.log(p instanceof Student)//true
console.log(p instanceof Person)//true
console.log(p instanceof Object)//true
console.log(p instanceof Teacher)//false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
isPrototypeOf
语法:
prototypeObj.isPrototypeOf(object)
用于检测某个对象,是否出现在某个实例对象的原型链上
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 18、原型继承关系图-原型链
# 19、Object.create()方法的深入
function Person(name) {
this.name = name
}
Person.prototype.running = function () {
console.log(this.name, 'running')
}
function Student(name) {
Person.call(this, name)
}
//直接用Object.create() 类方法实现
// Student.prototype = Object.create(Person.prototype, {
// constructor: {
// value: Student,
// enumerable: false,
// configurable: true,
// writable: true
// }
// })
// 提出疑问:Object.create()内部是怎么实现的,下面我们自己来封装一个
Object.prototype.hyCreate = function(obj,prop) {
function Fn() {}
Fn.prototype = obj
var f1 = new Fn()
for(var key in prop) {
Object.defineProperty(f1,key,prop[key])
}
return f1
}
Object.defineProperty(Object.prototype,'hyCreate',{
enumerable:false,//不可枚举
})
//实现寄生组合式继承,来检测我们封装的函数
Student.prototype = Object.hyCreate(Person.prototype, {
constructor: {
value: Student,
enumerable: false,
configurable: true,
writable: true
},
// name: {
// value:'kobe'
// }
})
var stu = new Student('lyk')
stu.running()//lyk running
console.log(stu.__proto__)
console.log(stu)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 20、创建对象的内存表现及总结
- p1是Person的实例化对象
- obj是Object的实例化对象
- Function/Object/Foo等所有函数都是Function的实例化对象
- 所有函数的原型对象prototype默认创建时,隐式原型都是指向Object的显式原型的(Obejct函数的原型对象prototype例外,它的隐式原型是指向null)
- 由上可以推导出一个结论,Object是Person/Function及所有构造函数的父类