原型和原型链
原型
原型分为显式原型和隐式原型。
- 每个函数
function
对象都有prototype
(显式原型),在定义函数的时候自动添加,默认值就是一个空对象。 - 每个对象都有
__proto__
,创建对象的时候自动添加的,值为构造函数的prototype
指向的属性
当使用new 新建一个对象的时候:
- 创建一个新对象,这个新对象的
__proto__
属性指向构造函数的prototype
属性(第二条 对象__proto__
指向构造函数prototype
的原因 - 构造函数执行环境的
this
指向这个新对象 - 执行构造函数中的代码,通过
this
给新对象添加新的成员属性或方法。 - 如果return 返回一个基本数据类型的变量就返回构造新的对象 如果写死了return返回的对象就返回写死的对象 不返回新对象
原型和原型链
原型
原型分为显式原型和隐式原型。
- 每个函数
function
对象都有prototype
(显式原型),在定义函数的时候自动添加,默认值就是一个空对象。 - 每个对象都有
__proto__
,创建对象的时候自动添加的,值为构造函数的prototype
指向的属性
原型链
如图所示:Person为构造函数,通过new调用实例化出对象p1。
- 函数Person的
prototype
指向原型对象,通过new
调用之后p1
的__proto__
指向原型对象,两者没有直接关联,通过原型对象联系在一起 - 同时Person对象作为一个对象,也有自己的隐式原型
__proto__
,所有的函数实例都可以理解为new Function
构造得到,所以函数实例的__proto__
指向Function
, Function.prototype=Function.__proto__
,因为函数Function
即是函数,又是对象。在作为对象时,是通过new Function()
获得的。- Object函数是一个函数对象,所以其
__proto__
也是指向Function
Object
是所有对象的基类,所以原型对象都是Object的实例- 原型链的终点就是null
其实原型链主要分成两种情况
-
函数的继承
- 函数的继承链为
prototype
- 函数的继承链为
-
对象的继承
- 对象的继承链为
__proto__
- 对象的继承链为
对象的继承
当使用new 新建一个对象的时候:
- 创建一个新对象,这个新对象的
__proto__
属性指向构造函数的prototype
属性(第二条 对象__proto__
指向构造函数prototype
的原因 - 构造函数执行环境的
this
指向这个新对象 - 执行构造函数中的代码,通过
this
给新对象添加新的成员属性或方法。 - 如果return 返回一个基本数据类型的变量就返回构造新的对象 如果写死了return返回的对象就返回写死的对象 不返回新对象
继承
继承就是实现代码的复用。(ts是鸭子类型,继承关系并不需要真的继承,看类型就ok
es5有三种继承方式:
- 原型链继承
- 借用构造函数实现继承
- 寄生组合式继承
es6继承方式:
- 类继承extends
使用原型链继承
直接修改函数的prototype
function Person(){
this.friend=[]
this.name=111
}
function Student(){
this.sno=111
}
let p=new Person()
Student.prototype=p
let stu1=new Student()
let stu2=new Student()
stu1.friend.push('ali')
stu1.name='222'
console.log(stu1.friend,stu1.name)
console.log(stu2.friend,stu2.name)
// [ 'ali' ] 222
// [ 'ali' ] 111
弊端:
- 因为原型是同一个对象,所以修改原型上的引用类型会导致其他读取也有问题(不过这个是原型本身的问题)
- 不好传递参数
使用借用构造函数实现继承
将子类的prototype
指向父类的实例
function Person(name,age,friends){
this.name=name
this.age=age
this.friends=friends
}
function Student(name,age,friends){
//相当于在new指向的空对象里面一个一个赋值Person的属性
Person.call(this,name,age,friends)
this.sno=111
}
let p=new Person()
Student.prototype=p
let stu1=new Student('stun',18,['lucy'],111)
console.log(stu1)
//Person { name: 'stun', age: 18, friends: [ 'lucy' ], sno: 111 }
注意:
- 注意13行,
new
p的时候是没有给Person
传参的,所以p的name
,age
,friend
都是undefined
,就算第9行Person.call
了,其实并没有给p赋值,this指向的是父类的空对象,不是p!
弊端:
- 属性都在自己身上其实p就不用重复了
- 调用了两次 亲代类的构造函数 其实没什么必要
寄生组合式继承
拷贝父类的显式原型,将拷贝的原型对象的constructor
指向自己。
function Person(name,age,friends){
this.name=name
this.age=age
this.friends=friends
}
function Student(name,age,friends){
//相当于在new指向的空对象里面一个一个赋值Person的属性
Person.call(this,name,age,friends)
this.sno=111
}
//子类继承父类
let p=Object.create(Person.prototype)
Student.prototype=p
Student.prototype.constructor = Student;
let stu1=new Student('stun',18,['lucy'],111)
console.log(stu1)
//Person { name: 'stun', age: 18, friends: [ 'lucy' ], sno: 111 }
补充:
Object.create()
方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型
es6:
extends
引入类关键字class,但是其实就是function的语法糖。extends
不止能继承类,也能继承普通的构造函数。
es6规定,子类一定要在构造函数construnctor()
里面调用父类的构造函数super()
!因为ES6 的继承机制,与 ES5 完全不同。ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。 这就是为什么 ES6 的继承必须先调用super()
方法,因为这一步会生成一个继承父类的this
对象,没有这一步就无法继承父类。
es5:在子类实例上添加父类实例的方法
es6:先生成父类实例,然后再添加子类的属性,实例变成子类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
上面代码中,子类的constructor()
方法没有调用super()
之前,就使用this
关键字,结果报错,而放在super()
之后就是正确的(不涉及this
关键字的语句放在super
之前也是ok的