复习 call 的基本实现原理
- call 能改变函数的 this
- call 的第一个参数是 this, 后面的参数都是无限的
const obj = {
name: "jack",
};
function getB(x, y) {
console.log(this, x, y);
}
Function.prototype.myCall = function (self) {
const args = Array.from(arguments);
// 给 对象的原型上添加一个 getB
self.__proto__.func = this;
// 对象调用 getB,this 改为 对象
const res = self.func(...args.slice(1));
delete self.__proto__.func;
return res;
};
getB.myCall(obj, 1, 2); // obj 1 2
借用构造函数继承的实现原理
核心:利用 call 或者 apply 方法改变父类 this 在作用域中的指向,在子类中对父类调用这个方法,就是将子类的变量在父类中执行一遍。
缺点一:相同属性后者会覆盖前者
// 声明父类
function Parent(id) {
this.books = ["JavaScript", "html", "css"];
this.id = id || "";
}
// 声明母类
function Monther(id) {
this.books = ["UI", "JAVA"]; // 后面的会覆盖前面相同的属性
this.id = id || "";
}
//声明子类
function Child(id) {
console.log("call改变原实例的this指向", this);
Parent.call(this, id); //call改变父类this作用域名的指向
Monther.call(this, id); //call改变母类this作用域名的指向,但是相同属性后者会覆盖前者
}
var test1 = new Child(11);
var test2 = new Child(12);
console.log("---------------------输出测试实例1----------------------");
console.log(test1);
console.log("---------------------输出测试实例2----------------------");
console.log(test2);
缺点二:无法继承 父类声明在原型的方法
如果想要继承父类的原型方法就必须绑定在 this 上面,这样创建出来的每一个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则。
//声明父类
function Parent(id) {
this.books = ["JavaScript", "html", "css"];
this.id = id || "";
// this.showBooks = function() {
// console.log(this.books);
// }
}
//父类声明原型方法
Parent.prototype.showBooks = function () {
console.log(this.books);
};
//声明子类
function Child(id) {
console.log("call改变原实例的this指向", this);
Parent.call(this, id); //call改变父类this作用域名的指向
Monther.call(this, id); //call改变母类this作用域名的指向,但是相同属性后者会覆盖前者
}
var test1 = new Child(11);
// test1.showBooks(); // test1.showBooks is not a function
总结:
- Parent.call(this,id)是构造函数式的精髓,由于 call 这个方法可以更改函数的作用环境,
- 因此在子类中,对 Parent 调用这个方法,就是将子类的变量在父类中执行一遍,
- 由于父类中是给 this 绑定属性的,因此子类自然就继承了父类的共有属性。由于这种类型的继承没有涉及 prototype, 所以父类的原型方法自然就不会被子类继承。
- 如果想要继承父类的原型方法就必须绑定在 this 上面,这样创建出来的每一个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则。
- 为了综合之前两种模式的有点于是有了组合式继承