- 概念前提
1.对象(普通对象和函数对象,基本上js里都是对象)都有__proto__属性,只有函数对象独有prototype属性。 2. 对象的__proto__指向其构造函数的原型(prototype)。js里有内置有构造函数,比如Function, Object, Number, String... 3. 构造函数的prototype也是一个普通对象。 4. 每个对象都有一个constructor属性。
- 原型链
对象访问其属性时,若本身属性不存在,则通过__proto__一层一层向其原型查找而形成的链式结构称为原型链。
- 原型
原型指的是构造函数的prototype,其上的属性和方法能让基于此构造函数创建的对象去使用。如Array构造函数上有push方法,则通过Array构造出的的对象也可以使用。如:
let arr = new Array()
arr.push('new item')
arr对象没有push方法,但其构造函数的原型上有push方法,因此可以使用。 当对象所使用的属性和方法在当前对象不存在时,对像就会通过__proto__隐式原型找到其构造函数的原型,查找构造函数原型上是否有该属性或者方法,没有则一直往上找,直到终点null,形成的链式结构也叫原型链。
- 举例
function Person(){ ... }; //构造函数
letstudent= new Person(); //实例化Person, 创建student对旬
//Person 和 person1 都有__proto__属性,__proto__也可以叫作隐式原型
//Person作为函数,其__proto__指向Function的原型
Person.__proto__ == Function.prototype; //true
//Person.prototype是通过Object构造函数而来(4)
Person.prototype.__proto__ == Object.prototype;
//Object作为函数,其__proto__指向Function的原型
Object.__proto__ == Function.prototype; //true
//实例对象的__proto__只想其构造函数
student.__proto__ == Person.prototype; //true
//实例person1里的constroctor属性是继承其构造函数原型上的constructor
student.constructor == Person; //true
//构造函数原型的constructor指向其本身
Person.prototype.constructor = Person;
student.constructor == Person.prototype.constructor; //true
Object.prototype == null; //true
- 原型的继承
ES5继承方式
//构造函数
function Person (name) {
this.name = name;
this.arr = [1];
function eat(){
console.log(`${this.name} is eatting`)
}
}
function Student() {};
//继承自 Person
//1、prototype模式
Student.prototype = new Person() //由于直接实例化Person赋给了Student, 而Person里有引用对象arr,
let stu1 = new Student() //通过Student实例化出来的实例会共用同一个arr, 实例的constructor
let stu2 = new Student() //也会指向Person, 而不是Student,这样会导致继承链紊乱
stu1.arr.push(2) //同时这种方法也不能在实例化Student时不能传参
stu2.arr // [1,2]
//在上面的基础上,将Student原型上的constructor重新指向Student
Student.prototype.constructor = Student;
//2、直接继承prototype
Student.prototype = Person.prototype
//将constructor指回Student
Student.prototype.constructor = Student //前面提到,函数对象的prototype是个对象,通过 = 赋值只是将指针指向同一对象
//的地址,当Student修改原型上的属性时,也会影响到Person原型上。
//3、通过构造函数方法
function Student(name, age){ //可以向Person传参,每个属性和方法都有自己独立的空间
Person.call(this, name) //同样的功能却有两个方法
this.age = age //也继承不了Person原型上的方法,只能继承Person里边的方法
}
//4、结合prototype模式和构造函数方法继承
function Student(name, age){
Person.call(this, name)
this.age = age
}
//将Person的实例赋值给Student原型
Student.prototype = new Person() //若是Student.prototype = Person.prototype 缺点和第2点一样
//修改Student原型constructor的只想
Student.prototype.constructor = Student //这样的方法能继承到Person原型链上的方法,不过需要调用两次Person,会多出一份Person实例的属性
//5、组合寄生
function Student(name, age){
Person.call(this,name)
this.age = age
}
//通过Object.create()创建中间对象
Student.prototype = Object.create(Person.prototype) // 通过Object.create()创建的中间对象会隔离两个对象,这样就不会共享同一个对象
ES6继承
ES6有class概念,通过extends可以继承父类
class Person {
constructor(name}{
this.name = name;
}
eat() {
console.log(`${this.name} is eating`)
}
}
//子类继承
class Student extends Person {
constructor(name, age){
super(name)
this.age = age
}
}
let student = new Student('tom', 20)
student.eat() //'tom is eating'
类的本质也是一个函数对象。 子类继承父类,没有this属性。如果没有constructor属性,则会默认添加constructor属性,并调用super。继承的子类没有this,需要通过super将父类的指针指向子类。 在子类有写constructor属性时,必须调用super后才能使用this,不然不报错,同时要向super传入父类需要的参数,再传入子类的参数,否则子类实例调用父类属性会为undefined。
class Person {
constructon(name, age){
this.name = name;
this.age = age;
}
}
class Student extends Person{
constructor(grade){
//在super前调用this,报错
//console.log(this)
super() //若此处是super(name),则子类实力传入的第一个参数赋给name,
//第二个参数本应该赋给age,但由于没有向父类传参,故age为undefined.
this.grade = grade //若没有第三个参数,对就的age也是undefined
}
}
let student1 = new Student(80)
console.log(student1) // '{name: undefined, age: undefined, grade: 80}'