原型、构造器、隐式原型、原型链

本文最后更新于:1 年前

原型、构造器、隐式原型、原型链

实例、构造函数、原型之间,形成一个环形指向它们的指向关系是:
由构造函数 new 出实例,然后实例的隐式原型(__proto__)指向构造函数的显示原型(prototype)。

原型prototype

原型对象prototype其实是构造函数(function对象)的一个属性,但是他也是一个对象
只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。
默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。

1
2
3
function Test() {}
//prototype
console.log(Test.prototype) //{constructor: ƒ}

prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出来的对象都可以继承原型上的属性和方法
对象自己身上有的就不会访问原型上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Handphone(color, brand) {
this.color = color
this.brand = brand
this.screen = '18:9'
}
Handphone.prototype.rom = '64G'
Handphone.prototype.ram = '6G'
Handphone.prototype.screen = '16:9'
let hp1 = new Handphone('red', '小米')
let hp2 = new Handphone('black', '红米')
console.log(hp1.rom) //64D
console.log(hp2.ram) //6G
console.log(hp1.screen) //18:9 //自己身上有的就访问自己的,不会访问祖先的
console.log(hp2.screen) //18:9

推荐固定的值或者方法写在原型上,需要变化或者传参配置的值写在构造函数内部,这样可以减少代码冗余

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   function Handphone(color, brand) {
this.color = color
this.brand = brand
}
// Handphone.prototype.rom = '64G'
// Handphone.prototype.ram = '6G'
// Handphone.prototype.screen = '16:9'
// Handphone.prototype.call = function () {
// console.log('I am calling')
// }
//开发中一般写在一起
Handphone.prototype = {
rom: '64G',
ram: '6G',
screen: '16:9',
call: function () {
console.log('I am calling')
},
}
let hp1 = new Handphone('red', '小米')
let hp2 = new Handphone('black', '红米')
hp2.call() //I am calling

通过实例化对象不能增删改祖先prototype上的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Test() {}
Test.prototype.name = 'TEST'
let test = new Test()
console.log(Test.prototype) //{name: 'TEST', constructor: ƒ}
test.job = 123
console.log(Test.prototype, test)
//{name: 'TEST', constructor: ƒ}
//Test {job: 123}
test.name = test
console.log(Test.prototype, test)
//{name: 'TEST', constructor: ƒ}
//Test {job: 123, name: Test}
delete test.name
console.log(Test.prototype, test)
//{name: 'TEST', constructor: ƒ}
//Test {job: 123}

构造器constructor

constructor默认指向构造函数本身
在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object。
构造函数有一个 prototype 属性引用其原型对象,而这个原型对象也有一个 constructor 属性,引用这个构造函数。换句话说,两者循环引用

1
2
3
4
5
6
7
8
9
10
11
function Handphone(color, brand) {
this.color = color
this.brand = brand
}
console.log(Handphone.prototype) //{constructor: ƒ}
// {constructor: ƒ}
// constructor: ƒ Handphone(color, brand)
// [[Prototype]]: Object

console.log(Handphone.prototype.constructor === Handphone)

可以通过原型内部的constructor更改构造函数的constructor

1
2
3
4
5
6
7
8
9
10
11
12
function Telephone() {}
function Handphone(color, brand) {
this.color = color
this.brand = brand
}
Handphone.prototype = {
constructor: Telephone,
}
console.log(Handphone.prototype)
// {constructor: ƒ}
// constructor: ƒ Telephone()
// [[Prototype]]: Object

__proto__( [[Prototype]] )

__proto__只是一个容器,实例对象通过__proto__这个键名指向原型对象prototype

实例的__proto__=== 构造函数的prototype

1
2
3
function Car() {}
let car = new Car()
console.log(Car.prototype === car.__proto__) //true

__proto__是实例化以后的结果,__proto__属于实例化对象
构造函数被实例化时会产生一个this,this默认有一个__proto__属性,指向他的原型。
如果this中没有找到对应的属性,就会沿着__proto__找原型里的属性

1
2
3
4
5
6
7
8
9
function Car() {
// var this = {
// __proto__: Car.prototype, //产生的this里边默认有__proto__属性,指向他的原型
// 如果this中没有对应的属性,就会根据__proto__向上找原型里的属性
// }
}
Car.prototype.name = 'Benz'
let car = new Car() //构造函数被实例化时,内部产生一个this
console.log(car.name) //Benz

__proto__只是一个内部属性,也可以改

1
2
3
4
5
6
7
8
9
function Person() {}
Person.prototype.name = '张三'
let p1 = {
name: '李四',
}
let person = new Person()
console.log(person.name) //张三
person.__proto__ = p1
console.log(person.name) //李四

实例化前后赋值prototype

实例化之前赋值prototype可以修改

1
2
3
4
5
Car.prototype.name = 'Mazda'
Car.prototype.name = 'Benz'
function Car() {}
let car = new Car()
console.log(car.name) //Benz

实例化之后赋值prototype可以修改

1
2
3
4
5
Car.prototype.name = 'Mazda'
function Car() {}
let car = new Car()
Car.prototype.name = 'Benz'
console.log(car.name) //Benz

实例化前后重写prototype

实例化之前重写可以修改prototype属性

1
2
3
4
5
Car.prototype.name = 'Benz'
function Car() {}
Car.prototype = { name: 'Mazkda' }
let car = new Car()
console.log(car.name) //Mazkda

实例化之后重写的prototype属性不能影响实例化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Car.prototype.name = 'Benz'
function Car() {}
let car = new Car()
Car.prototype = {
//这是实例化之后重写的,不能影响实例的值
name: 'Mazkda',
}
console.log(car.name) //Benz
//实例化时
// function Car() {
// let this = {
// __proto__: (Car.prototype = {
// name: 'Benz',
// }),
// }
// }

实例化对象的constructor指向的是构造函数,constructor保存的是实例化之前的东西,
实例化之后再重写prototype(属于未实例化/实例化之前) 就会被放到.prototype.constructor
所以实例化之后再重写prototype,此时重写的是实例化之前Car.prototype.constructor的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
28
29
Car.prototype.name = 'Benz'
function Car() {}
var car = new Car()
//相当于与上一次实例化之后在重写prototype(属于未实例化/实例化之前)
//(属于未实例化/实例化之前)就被放到Car.prototype.constructor里
Car.prototype = {
name: 'Mazkda', //此时重写的是实例化之前Car.prototype.constructor的prototype
}
console.log(car)
// Car {}
// [[Prototype]]: Object
// name: "Benz"
// constructor: ƒ Car() //constructor保存的是实例化之前的东西
// arguments: null
// caller: null
// length: 0
// name: "Car"
// prototype: {name: 'Mazkda'}
console.log(car.name) //Benz
//实例化对象的constructor指向的是Car构造函数
console.log(car.constructor.prototype) //{name: 'Mazkda'}
// function Car() {
// let this = {
// __proto__: (Car.prototype = {
// name: 'Benz',
// }),
// }
// }
//Car.prototype.constructor -> Car() -> prototype -> name : Benz
1
2
3
4
5
6
7
8
9
10
11
12
function test() {
let a = 1
function plus1() {
a++
console.log(a)
}
return plus1
}
let plus = test()
plus() //2
plus() //3
plus() //4

原型链

如果原型是另一个类型的实例呢?
那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

沿着__proto__( [[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
Professor.prototype.tSkill = 'JAVA'
function Professor() {}
let professor = new Professor()

Teacher.prototype = professor
function Teacher() {
this.mSkill = 'JS'
}
let teacher = new Teacher()

Student.prototype = teacher
function Student() {
this.pSkill = 'HTML'
}
let student = new Student()

console.log(student)
// Student {pSkill: 'HTML'}
// [Prototype]]: Professor
// mSkill: "JS"
// [[Prototype]]: Professor
// [[Prototype]]: Object
// tSkill: "JAVA"
// constructor: ƒ Professor()
// [[Prototype]]: Object

原型链的顶端是Object.prototype

Object.prototype里边保存了toString方法
Object.prototype.__proto__是null

1
console.log(Object.prototype.__proto__ === null) //true

如果是引用值,子级可以更改/添加父级或者祖先的属性(不推荐)

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
Professor.prototype.tSkill = 'JAVA'
function Professor() {}
let professor = new Professor()

Teacher.prototype = professor
function Teacher() {
this.mSkill = 'JS'
this.success = {
alibaba: '10',
}
}
let teacher = new Teacher()

Student.prototype = teacher
function Student() {
this.pSkill = 'THML'
}
let student = new Student()

student.success.baidu = '1'
student.success.alibaba = '1'
console.log(teacher, student)
// Professor {mSkill: 'JS', success: {…}}
// mSkill: "JS"
// success: {alibaba: '1', baidu: '1'}
// [[Prototype]]: Professor

// Student {pSkill: 'THML'}
// pSkill: "THML"
// [[Prototype]]: Professor
// mSkill: "JS"
// success: {alibaba: '1', baidu: '1'}
// [[Prototype]]: Professor

如果是原始值,子级不能修改/增加父级或者祖先的属性

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
Professor.prototype.tSkill = 'JAVA'
function Professor() {}
let professor = new Professor()

Teacher.prototype = professor
function Teacher() {
this.mSkill = 'JS'
this.students = 500
}
let teacher = new Teacher()

Student.prototype = teacher
function Student() {
this.pSkill = 'THML'
}
let student = new Student()

student.students++
console.log(teacher, student)
// Professor {mSkill: 'JS', students: 500}
// mSkill: "JS"
// students: 500
// [[Prototype]]: Professor

// Student {pSkill: 'THML', students: 501}
// pSkill: "THML"
// students: 501
// [[Prototype]]: Professor
// mSkill: "JS"
// students: 500
// [[Prototype]]: Professor

this谁用指向谁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Car() {
this.brand = 'Benz'
}
Car.prototype = {
brand: 'Mazda',
intro: function () {
console.log('我是' + this.brand)
},
}
let car = new Car()
//实例化时构造函数内部产生空this,然后执行代码将数据给this。
//构造函数中有brand就用自己的,没有intro就在原型上找
// function Car() {
// let this={
// barnd:'Benz'
// }
// }
// Car.prototype = {
// brand: 'Mazda',
// intro: function () {...},
// }
car.intro() //我是Benz
Car.prototype.intro() //我是Mazda. //this谁用指向谁

不是所有的对象都继承于Object.prototype。

Object.create(null)创建的实例对象不继承Object.prototype。

1
2
3
4
5
6
7
8
9
let obj = Object.create(null)
obj.num = 1
console.log(obj) //{num: 1} //不继承Object.prototype
// obj.toString() //obj.toString is not a function //也没有toString方法
let obj1 = {
count: 2,
}
obj.__proto__ = obj1 //没有继承Object.prototype的情况下,手动设置__proto__是无用的,无法访问
console.log(obj.count) //undefined

默认情况下,所有引用类型都继承自 Object,这也是通过原型链实现的
任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向 Object.prototype
这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默 认方法的原因。

原始值是没有属性,所以undefined和null没有toString方法
undefined和null不能经过包装类,没有原型

1
2
3
let num = 1
console.log(num.toString()) //打印出字符串 1
//这里原始值可以使用toString是因为进行了包装类new Number(1)所以才有toString()

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!