谈谈你对原型链的理解
- 原型对象和构造函数有何关系?
在JavaScript中,每当定义一个函数数据类型(普通函数、类)的时候,这个函数就会自带一个prototype属性,这个属性指向函数的原型对象
另外,这个原型对象会有一个constructor属性指回函数本身
当函数被new调用的时候,这个函数就称为了构造函数
new调用会创建一个实例对象,这个实例对象会自带一个__proto__属性,这个属性指向构造函数的原型对象
即o.__proto__ === Foo.prototype
- 描述一下原型链?
JavaScript对象通过__proto__指向父类原型对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,即原型链
原型链最顶层的原型对象就是Object的原型对象
另外,检查对象自身是否包含某属性,可以使用 hasOwnProperty()
使用 in 的话,即使自身没有,但是原型链中有,也会返回true
绿色箭头,原型链
o.__proto__.__proto__.__proto__
JS如何实现继承?
方式1:借助原型链
// 父类
function Person() {
this.name = 'pName'
this.age = 18
this.friends = []
}
Person.prototype.sayHello = function() {
console.log('Hello');
}
// 子类
function Student() {
this.height = 1.88
}
let p = new Person() // 获得Person类的属性方法
console.log(p);
// 我们让Student的原型指向p
Student.prototype = p
let stu = new Student()
console.log(stu.name); // pName
stu.sayHello() // Hello
// 可见我们已经拿到了父类的属性和方法
这种实现方法存在的问题:
1.当我们打印实例stu的时候,继承的属性不能直观看到,只能在__proto__属性中看到
console.log(stu)// Student {height: 1.88}
2.另外,如果我们创建两个实例对象,并改变friends属性
stu1.friends.push('aaa')
console.log(stu2.friends); // ['aaa']
明明改变的是stu1的属性,为什么stu2也变了?
因为我们它们的隐式原型指向的是同一个对象,p
3.前面实现的过程都没有传递参数
方式2:借助构造函数
使用call调用构造函数,传入的this为当前实例,并且可以把参数传给父类处理
// 父类
function Person(name, age) {
this.name = name
this.age = age
this.friends = []
}
Person.prototype.sayHello = function() {
console.log('Hello');
}
// 子类
function Student(name, age, height) {
Person.call(this, name, age) // 这里可以获得父类的属性
this.height = height
}
let p = new Person() // 依然需要这里来获得方法
Student.prototype = p
let stu1 = new Student('aaa', 18, 1.78)
let stu2 = new Student('bbb', 20, 1.88)
stu1.friends.push('aaa')
console.log(stu1);
console.log(stu2);
stu1.sayHello() // Hello
构造函数的方法可以解决原型链方式的问题,但是依然存在弊端
- Person 至少被调用两次(一开始new Person一次,后面Person.call又会调用Person)
- stu的原型对象上会多出一些属性, 但是这些属性是没有存在的必要(new Person的时候的)
方式3:寄生组合式继承(最终方案)
**
Object.create()**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。Object.create(XXX) 也就是传入的对象XXX,作为新对象的原型
// 父类
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.sayHello = function() {
console.log('Hello');
}
// 子类
function Student(name, age, friends, height) {
// 获取父类中的属性和方法
Person.call(this, name, age, friends)
this.height = height
}
// 再获取一份父类的prototype中的属性和方法
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
let stu = new Student('aaa', 18, ['fre1'], 1.88)
console.log(stu);
当然,我们也可以封装一个方法来继承父类prototype中的属性和方法
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Object.create(SuperType.prototype)
// 当然子类的prototype还需要有constructor指向子构造函数本身
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
...
inheritPrototype(Student, Person)