#tag prototype 原型 proto
#d 何为原型对象
原型对象是每个函数都有的属性, js中的函数会有一个protorype
属性,
这个属性是一个对象, 这个对象有一个属性为constructor
,意为构造函数.
专门指向函数本身. 在原型对象中, 存放着构造函数的公共属性和方法.
#l(构造函数)
#e 原型示意 构造函数 FN
function FN(){
this.name = 'p'
this.age = 18
}
FN.prototype.say = function (){
console.log(`hello my name is ${this.name}`)
}
此时构造函数 FN 的原型对象可以视作如下内容
属性 | 值 |
---|---|
constructor | FN |
proto | Object |
公共属性 | |
name | p |
age | 18 |
公共方法 | say |
let fn = new FN()
fn.say() // hello my name is p
#d 原型对象作用
#d 什么是原型链
在js中, 每个对象都有一个__proto__
的属性,
这个属性指向了该对象的[原型对象], 但是这个[原型对象]本身是一个对象,
所以它也有[原型]属性, 所以我们将之称为[原型链].
这个链条的最终会指向Object
对象, Object也会有一个[原型]属性,
但是这个属性为null
#e 原型链示意 下面是一个简单的原型链示意图
function FN(){}
FN.prototype.say = function (){}
let fn = new FN()
// fn实例的原型
fn.__proto__ === FN.prototype // true
// FN对象的原型
FN.prototype.__proto__ === Object.prototype // true
// Object对象的原型
Object.prototype.__proto__ === null // true
#d 原型链作用
#e 原型链应用示例
function FN(){}
FN.prototype.say = function (){
console.log(`hello`)
}
let fn = new FN()
fn.say() // hello
fn.say === fn.__proto__.say // true
fn.say === FN.prototype.say // true
// 无法在 object 对象中找到
fn.say === Object.prototype.say // false
fn.age = 18
console.log(fn) // FN { age: 18 }
let fn1 = new FN()
console.log(fn1.age) // undefined
fn1.say() // hello
// 修改原型
FN.prototype.say = function (){
console.log(`hello my age is ${this.age}`)
}
fn.say() // hello my age is 18
fn1.say() // hello my age is undefined
FN.prototype.age = 19
// fn1 中无法找到age属性, 所以用fn1.age访问到的是FN.prototype.age属性
console.log(fn1.age) // 19
fn1.say() // hello my age is 19
// 修改fn1中的的age属性, 之后在访问该属性, 访问的是fn1.age属性
fn1.age = 20
console.log(fn1.age) // 20
#d 原型链的缺点
#c 继承效果
我们可以通过原型链实现继承效果, 父类中的属性和方法, 都会继承给子类.
#e 继承
父类构造函数
function Person(name){
this.name = name
this.say = function (){
console.log(`hello my name is ${this.name}`)
}
}
let p = new Person('p')
p.say() // hello my name is p
子类构造函数
function Student(name, age){
this.age = age
}
Student.prototype = new Person()
let s = new Student('s', 18)
// 父类属性会被继承给子类, 但是name属性暂时未赋值
s.say() // hello my name is undefind
console.log(s) // Student { age: 18 }
父类属性赋值
Student.prototype.name = 's'
s.say() // hello my name is s
另一种写法
该写法, 会将父类构造函数中的属性和方法, 赋值给子类构造函数的原型对象中,
其属性与方法, 都是子类实例对象所共享的, 不会影响父类构造函数中的属性和方法
function Student2(name, age){
Person.call(this, name)
this.age = age
}
let s2 = new Student2('s2', 19)
s2.say() // hello my name is s2
console.log(s2) // Student2 { age: 19, name: 's2', say: [Function (anonymous)] }
组合继承
父类的构造函数被调用了两次(创建子类原型时调用了一次,创建子类实例时又调用了一次),
导致子类原型上会存在父类实例属性,浪费内存。
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = new Parent();
const child = new Child(1)
child.getValue();
child instanceof Parent;
寄生组合继承
使用 Object.create(Parent.prototype) 创建一个新的原型对象赋予子类从而解决组合继承的缺陷:
// 寄生组合继承实现
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false, // 不可枚举该属性
writable: true, // 可改写该属性
configurable: true // 可用 delete 删除该属性
}
})
const child = new Child(1)
child.getValue();
child instanceof Parent;