原型、构造器、隐式原型、原型链
实例、构造函数、原型之间,形成一个环形指向它们的指向关系是:
由构造函数 new 出实例,然后实例的隐式原型(__proto__
)指向构造函数的显示原型(prototype
)。
原型prototype
原型对象prototype其实是构造函数(function对象)的一个属性,但是他也是一个对象
只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。
默认情况下,所有原型对象自动获得一个名为 constructor
的属性,指回与之关联的构造函数。
| function Test() {}
console.log(Test.prototype)
|
prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出来的对象都可以继承原型上的属性和方法
对象自己身上有的就不会访问原型上的
| 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) console.log(hp2.ram) console.log(hp1.screen) console.log(hp2.screen)
|
推荐固定的值或者方法写在原型上,需要变化或者传参配置的值写在构造函数内部,这样可以减少代码冗余
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', ram: '6G', screen: '16:9', call: function () { console.log('I am calling') }, } let hp1 = new Handphone('red', '小米') let hp2 = new Handphone('black', '红米') hp2.call()
|
通过实例化对象不能增删改祖先prototype上的值
| function Test() {} Test.prototype.name = 'TEST' let test = new Test() console.log(Test.prototype) test.job = 123 console.log(Test.prototype, test)
test.name = test console.log(Test.prototype, test)
delete test.name console.log(Test.prototype, test)
|
构造器constructor
constructor默认指向构造函数本身
在自定义构造函数时,原型对象默认只会获得 constructor
属性,其他的所有方法都继承自 Object。
构造函数有一个 prototype 属性引用其原型对象,而这个原型对象也有一个 constructor 属性,引用这个构造函数。换句话说,两者循环引用
| function Handphone(color, brand) { this.color = color this.brand = brand } console.log(Handphone.prototype)
console.log(Handphone.prototype.constructor === Handphone)
|
可以通过原型内部的constructor更改构造函数的constructor
| function Telephone() {} function Handphone(color, brand) { this.color = color this.brand = brand } Handphone.prototype = { constructor: Telephone, } console.log(Handphone.prototype)
|
__proto__
( [[Prototype]]
)
__proto__
只是一个容器,实例对象通过__proto__
这个键名指向原型对象prototype
实例的__proto__
=== 构造函数的prototype
| function Car() {} let car = new Car() console.log(Car.prototype === car.__proto__)
|
__proto__
是实例化以后的结果,__proto__
属于实例化对象
构造函数被实例化时会产生一个this,this默认有一个__proto__
属性,指向他的原型。
如果this中没有找到对应的属性,就会沿着__proto__
找原型里的属性
| function Car() { } Car.prototype.name = 'Benz' let car = new Car() console.log(car.name)
|
__proto__
只是一个内部属性,也可以改
| 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可以修改
| Car.prototype.name = 'Mazda' Car.prototype.name = 'Benz' function Car() {} let car = new Car() console.log(car.name)
|
实例化之后赋值prototype可以修改
| Car.prototype.name = 'Mazda' function Car() {} let car = new Car() Car.prototype.name = 'Benz' console.log(car.name)
|
实例化前后重写prototype
实例化之前重写可以修改prototype属性
| Car.prototype.name = 'Benz' function Car() {} Car.prototype = { name: 'Mazkda' } let car = new Car() console.log(car.name)
|
实例化之后重写的prototype属性不能影响实例化对象
| Car.prototype.name = 'Benz' function Car() {} let car = new Car() Car.prototype = { name: 'Mazkda', } console.log(car.name)
|
实例化对象的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()
Car.prototype = { name: 'Mazkda', } console.log(car)
console.log(car.name)
console.log(car.constructor.prototype)
|
| function test() { let a = 1 function plus1() { a++ console.log(a) } return plus1 } let plus = test() plus() plus() plus()
|
原型链
如果原型是另一个类型的实例呢?
那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。
沿着__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)
|
原型链的顶端是Object.prototype
Object.prototype里边保存了toString方法
Object.prototype.__proto__是null
| console.log(Object.prototype.__proto__ === null)
|
如果是引用值,子级可以更改/添加父级或者祖先的属性(不推荐)
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)
|
如果是原始值,子级不能修改/增加父级或者祖先的属性
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)
|
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()
car.intro() Car.prototype.intro()
|
不是所有的对象都继承于Object.prototype。
Object.create(null)
创建的实例对象不继承Object.prototype。
| let obj = Object.create(null) obj.num = 1 console.log(obj)
let obj1 = { count: 2, } obj.__proto__ = obj1 console.log(obj.count)
|
默认情况下,所有引用类型都继承自 Object,这也是通过原型链实现的。
任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向 Object.prototype。
这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默 认方法的原因。
原始值是没有属性,所以undefined和null没有toString方法
undefined和null不能经过包装类,没有原型
| let num = 1 console.log(num.toString())
|