一、对象和函数的原型
认识对象的原型
JavaScript中每个对象都有一个特殊的内置属性[[prototype]]
,这个特殊对象可以指向另外一个对象。
这个对象的作用:
- 当通过引用对象的属性key来获取一个value时,它会触发
[[Get]]
的操作
- 这个操作会首先检查该对象是否有对应的属性,如果有就使用它
- 如果该对象中没有对应的属性,那么就会访问对象
[[prototype]]
内置属性指向的对象上的属性
那么如果通过字面量直接创捷一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性?
获取的方式有两种:
- 方式一:通过对象的
__proto__
属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)
- 方式二:通过
Object.getPrototypeOf()
可以获取到
1
2
3
4
5
6
7
8
9
10
11
12
|
var obj = {
name: "mohca",
age: 18
}
// 获取对象的原型
// 非标准方式
console.log(obj.__proto__)
// 标准方式
console.log(Object.getPrototypeOf(obj))
|
认识函数的原型
将函数看成普通对象时,它具备__proto__
(隐式原型)
1
2
3
4
5
6
7
|
var obj = {}
function foo() {}
console.log(obj.__proto__)
console.log(foo.__proto__)
|
将函数看成是函数时,它具备 prototype
(显式原型)
1
2
3
4
5
6
|
var obj = {}
function foo() {}
console.log(obj.prototype) // 对象上是没有的
console.log(foo.prototype)
|
new操作过程:
- 创建空对象
var obj = {}
- 将这个空对象复制给this
this = obj
- 将函数的显示原型赋值给这个对象作为它的隐式原型
obj.__proto__ = Person.prototype
- 执行函数体中的代码
- 将这个对象默认返回
重写原型对象
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
|
function Person() { }
console.log(Person.prototype)
// 在原有的原型对象上添加新的属性
Person.prototype.message = "Hello Person"
Person.prototype.info = { name: "hahaha" }
// 直接赋值一个新的原型对象
Person.prototype = {
message: "Hello Person",
info: { name: "hahaha" },
// constructor: Person
}
Object.defineProperty(Person.prototype, "constructor", {
configurable: true,
writable: true,
enumerable: false,
value: Person
})
console.log(111, Object.keys(Person.prototype))
// 新建实例对象
var p = new Person()
console.log(p.message)
|
二、原型链的查找顺序
- 在
obj
上查找
- 在
obj.__proto__
上查找
- 在
obj.__proto__.proto__
上查找,查找不到返回undefined
1
2
3
4
|
var obj = {
name: "mocha",
age: 18
}
|
三、原型链实现方法的继承
面向对象有三大特性:封装、继承、多态
- 封装:将属性和方法封装到有个类中,称之为封装的过程
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态的前提
- 多态:不同的对象在执行时表现出不同的形态
方式一
父类的原型直接赋值给子类的原型
缺点:父类和子类共享一个原型对象,修改了任意一个,另外一个也会被修改
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构造函数(类)
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~")
}
// 定义学生类
function Student(name, age, sno, score) {
this.name = name
this.age = age
this.sno = sno
this.score = score
}
Student.prototype = Person.prototype
Student.prototype.studying = function () {
console.log(this.name + " studying~")
}
var stu1 = new Student("mocha", 18, 111, 100)
stu1.running() // mocha running~
stu1.studying() // mocha studying~
console.log(stu1) // Student {name: 'mocha', age: 18, sno: 111, score: 100}
|
方式二
创建一个父类的实例对象,用这个实例对象作为子类的原型对象
缺点:原型上有重复多余的变量
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
|
// 定义Person构造函数(类)
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~")
}
// 定义学生类
function Student(name, age, sno, score) {
this.name = name
this.age = age
this.sno = sno
this.score = score
}
var p = new Person("latte", 18)
Student.prototype = p
Student.prototype.studying = function () {
console.log(this.name + " studying~")
}
var stu1 = new Student("mocha", 20, 111, 100)
stu1.running() // mocha running~
stu1.studying() // mocha studying~
console.log(stu1) // Student {name: 'mocha', age: 20, sno: 111, score: 100}
|
四、借用构造函数实现属性的继承
为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(借用构造函数或者称之为经典继承或者称之为伪造对象)
做法:在在子类型构造函数的内部调用父类型构造函数
- 因为函数可以在任意的时刻被调用
- 因此通过apply()和call()方法也可以在新创建的对象上执行构造函数
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
|
// 定义Person构造函数(类)
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~")
}
// 定义学生类
function Student(name, age, sno, score) {
Person.call(this, name, age)
this.sno = sno
this.score = score
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function () {
console.log(this.name + " studying~")
}
var stu1 = new Student("mocha", 20, 111, 100)
stu1.running() // mocha running~
stu1.studying() // mocha studying~
console.log(stu1) // Student {name: 'mocha', age: 20, sno: 111, score: 100}
|
弊端:
- 会调用两次父类构造函数
- 一次在创建子类原型的时候
- 另一次再子类构造函数内部(也就是每次创建子类实例的时候)
- 所有的子类实例事实上会拥有两份父类的属性
- 一份在当前的实例自己里面(person本身),另一份在子类对应的原型对象中(
person.__proto__
)
- 两份属性不会出现访问问题,因为默认一定是访问实例本身的那部分属性
五、寄生组合实现继承
方案一
创建新的对象,让对象原型指向父类原型,最后将对象原型赋值给子类原型
1
2
3
|
var obj = {}
Object.setPrototypeOf(obj.Person.prototype)
Student.prototype = obj
|
方案二
创建新的函数,让函数原型指向父类原型,最后将函数原型赋值给子类原型
1
2
3
|
function F() { }
F.prototype = Person.prototype
Student.prototype = new F()
|
方案三
使用Object.create()
1
2
|
var obj = Object.create(Person.prototype)
Student.prototype = obj
|
最终方案
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
|
function createObject(o) {
function F() { }
F.prototype = o
return new F()
}
function inherit(Subtype, Supertype) {
Subtype.prototype = createObject(Supertype.prototype)
Object.defineProperty(Subtype.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Subtype
})
}
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function () {
console.log(this.name + " running~")
}
Person.prototype.eating = function () {
console.log(this.name + " eating~")
}
function Student(name, age, height, sno, score) {
Person.call(this, name, age, height)
this.sno = sno
this.score = score
}
inherit(Student, Person)
Student.prototype.studying = function () {
console.log(this.name + " studying~")
}
var stu = new Student("mocha", 18, 1.88, 1001, 100)
console.log(stu)
stu.running()
stu.eating()
stu.studying()
|
六、Object是对象父类
Object是所有对象的父类
1
2
3
4
5
6
|
var info = {
name: "mocha",
age: 18
}
console.log(Object.getPrototypeOf(info).__proto__ === Object.prototype.__proto__) // true
|
七、对象的其他方法补充
hasOwnProperty
判断对象是否有某一个属于自己的属性(不是在原型上的属性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function createObject(o) {
function F() { }
F.prototype = o
return new F()
}
var obj = {
name: "mocha",
age: 18
}
var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"
console.log(info.hasOwnProperty("name")) // false
console.log(info.hasOwnProperty("address")) // true
|
in操作符
判断某个属性是否在某个对象或者对象的原型上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function createObject(o) {
function F() { }
F.prototype = o
return new F()
}
var obj = {
name: "mocha",
age: 18
}
var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"
console.log("name" in info) // true
console.log("address" in info) // true
|
for in
遍历的不仅仅是自己身上的属性,也包括原型上的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function createObject(o) {
function F() { }
F.prototype = o
return new F()
}
var obj = {
name: "mocha",
age: 18
}
var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"
for (var key in info) {
console.log(key)
}
// name
// age
// address
// intro
|
instanceof操作符
用于检测构造函数的prototype是否出现在某个实例对象的原型链上
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
|
function createObject(o) {
function F() { }
F.prototype = o
return new F()
}
function inherit(Subtype, Supertype) {
Subtype.prototype = createObject(Supertype.prototype)
Object.defineProperty(Subtype.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Subtype
})
}
function Person() { }
function Student() { }
inherit(Student, Person)
var stu = new Student()
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
console.log(stu instanceof Array) // false
|
isPrototypeOf
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
|
function createObject(o) {
function F() { }
F.prototype = o
return new F()
}
function inherit(Subtype, Supertype) {
Subtype.prototype = createObject(Supertype.prototype)
Object.defineProperty(Subtype.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Subtype
})
}
var obj = {
name: "mocha",
age: 18
}
var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"
function Person() { }
function Student() { }
inherit(Student, Person)
var stu = new Student()
console.log(Student.prototype.isPrototypeOf(stu)) // true
console.log(Person.prototype.isPrototypeOf(stu)) // true
/* 可以用于判断对象之间的继承 */
console.log(obj.isPrototypeOf(info)) // true
|