JS 继承详解
2022-03-10 本文已影响0人
wyc0859
原型链继承
原型链继承实现的本质: 是改变 构造函数的.prototype的指向
原型链继承容易被遗忘的重要一步: 构造函数.prototype.constructor = 构造函数
原型链继承的不足: 不能通过子类构造函数向父类构造函数传递参数
function banji(className, teacherName) {
this.className = className;
this.teacherName = teacherName;
}
function student(name, age) {
this.name = name;
this.age = age;
}
const twoClass = new banji("二班", "小雪老师");
const lilei = new student("李雷", 18);
console.log("二班:", twoClass); // {className: '二班', teacherName: '小雪老师'}
console.log("李雷:", lilei); // {name: '李雷', age: 18}
console.log("李雷的原型空间:", lilei.__proto__); // constructor: ƒ student(name, age)
console.log("student构造函数:", student()); //空,因为没return
console.log("student的原型空间:", student.prototype); // {constructor: student函数}
student.prototype.job = "学生"; //此时20行打印会立即变化,因为对原型对象的修改是实时的
banji.prototype.course = ["数学", "物理", "化学"];
student.prototype = twoClass; //指向twoClass,来作为原型空间。即原型链继承
// banji {className: '二班', teacherName: '小雪老师'}
console.log("原型链继承后的 student的原型空间1:", student.prototype); //但作为原型空间怎能没有construct属性
student.prototype.construct = student;
//对原型对象的属性或方法做修改或新增,所有的实例对象立即可以访问
//如果刷新浏览器,会发现原来第19行的打印都会跟着改变。
//这行打印多余,因为和31行一样。是实时改变,而不论先后顺序
console.log("原型链继承后的 student的原型空间2:", student.prototype);
以上就是原型链继承,结构图如下
JS原型链继承.png原型链继承+借用构造函数
2者结合后,2缺点就不存在了。但新缺点就是父类会被调用2次
function banji(className, teacherName) {
console.log("banji构造函数执行了");
this.className = className;
this.teacherName = teacherName;
}
banji.prototype.str = function () {
console.log(`${this.name}在${this.className},老师是:${this.teacherName}`);
};
function student(name, age) {
this.name = name;
this.age = age;
banji.apply(this, ["二班", "小雪老师"]); //第二次触发banji构造函数
}
const twoClass = new banji("二班", "小雪老师"); //第一次触发banji构造函数
student.prototype = twoClass; //原型链继承
student.prototype.construct = student; // 添加原型空间缺少的construct属性
const lilei = new student("李雷", 18);
console.log("班级名称:", lilei.className);
console.log("lilei.__proto__:", lilei.__proto__); //结果为:二班实例对象
lilei.str(); //李雷在二班,老师是:小雪老师 //自己会一层层的去找,无需具体位置
lilei.__proto__.__proto__.str(); //undefined在undefined,老师是:undefined
//直接从具体位置调用,反倒拿不到前面的数据
继承到这里,即实现了调用父类对象,也可以传值给父类构造函数。
但缺点就是父类会被调用2次
寄生组合继承模式=借用构造函数+原型链继承+寄生继承
利用中间函数,完美的解决了调用2次构造函数的问题
function banji(className, teacherName) {
console.log("banji构造函数执行了");
this.className = className;
this.teacherName = teacherName;
}
banji.prototype.str = function () {
return `${this.name}在${this.className},老师是:${this.teacherName}`;
};
function student(name, age, className, teacherName) {
this.name = name;
this.age = age;
banji.apply(this, [className, teacherName]);
}
//寄生组合继承实现步骤
// 第一步: 创建一个寄生构造函数
function _extend() {
this.title = "某属性";
}
const one_extend = new _extend();
console.log("one_extend:", one_extend);
console.log("one_extend的原型对象:", one_extend.__proto__); //对的,是寄生构造函数原型对象
//寄生构造函数的原型对象 => 班级构造函数的原型对象
_extend.prototype = banji.prototype;
// 第二步:创建一个 寄生构造函数的实例对象
const two_extend = new _extend();
console.log("two_extend:", two_extend);
console.log("two_extend的原型对象:", two_extend.__proto__); //对的,是班级原型对象
// 第三步:student子类的原型对象属性指向第二步的 寄生实例对象
student.prototype = two_extend;
student.prototype.constructor = student;
//往two_extend寄生实例对象上加了一个constructor属性,让他指向自己
let one = new student("李雷", 18, "二班", "小雪老师");
console.log("学生A的原型对象:", one.__proto__); //banji {title: '某属性', constructor: ƒ}
console.log("学生A的原型对象下C属性:", one.__proto__.constructor); //student(name, age) {}
/**
* 学生A实例对象的原型(student.prototype) -> 寄生实例对象two 【它的原型】-> 班级构造函数的原型对象
* 得出:学生A实例对象的原型 == 班级构造函数的原型对象
*
* 学生A实例对象的原型下constructor属性(student.prototype.constructor)-> 学生构造函数空间
* 得出:学生A实例对象的原型下constructor属性 == 学生构造函数空间
*/
console.log("学生A属性:", one);
console.log("学生A信息:", one.str());
//所以学生的实例对象:有班级的原型对象属性方法。它的原型下construct属性==自己的构造函数空间。完美
let two = new student("梅梅", 17, "三班", "大壮老师");
console.log("学生B属性:", two);
console.log("学生B信息:", two.str());
上面已经OK了,但封装下,复用性更好
封装:寄生组合继承模式
封装后2行代码就能实现继承。Object.create方法和自行封装的寄生组合继承,逻辑代码上是一样的,但调用需3行代码。且内部不能做任何扩展,自行封装的自由度高,所以用自行封装的更好用
student.prototype = _extend(banji, student);
const a = new student("a", 18, "二班", "小雪");
function banji(className, teacherName) {
console.log("banji构造函数执行了");
this.className = className;
this.teacherName = teacherName;
}
banji.prototype.str = function () {
return `${this.name}在${this.className},老师是:${this.teacherName}`;
};
function student(name, age, className, teacherName) {
this.name = name;
this.age = age;
banji.apply(this, [className, teacherName]);
}
//寄生组合继承实现步骤
function _extend(parent, son) {
function Middle() {
this.title = "某属性";
this.constructor = son; //往寄生实例对象上加了constructor属性,让他指向自己(子类)
}
Middle.prototype = parent.prototype; //寄生构造函数的原型对象 => 父类构造函数的原型对象
let middle = new Middle();
return middle;
}
student.prototype = _extend(banji, student);
const a = new student("a", 18, "二班", "小雪");
console.log("学生A属性:", a);
console.log("学生A信息:", a.str());
Object.setPrototypeOf 与 Object.crate 区别
setPrototypeOf(现有对象,原型对象),为现有对象设置原型,返回一个新对象
接收两个参数,并且可继承父类的静态方法,是es6方法
Object.crate(原型对象,{参数}) 会使用指定的原型对象以及属性去创建一个新的对象,是es5方法
2者的区别:Object.crate继承了原型,但不能再访问原型中的前属性。Object.setPrototypeOf则可以访问
function banji(className, teacherName) {
this.className = className;
this.teacherName = teacherName;
}
banji.static_grade = "五年级"; //静态属性
function student(name, age) {
this.name = name;
this.age = age;
}
const s_a = new student("小A", 18);
console.log("a的原型空间-1:", s_a.__proto__); //constructor: ƒ student(name, age)
Object.setPrototypeOf(s_a, banji.prototype); //第二个参数也可以是 banji;和banji.prototype的区别在静态属性上
console.log("a的原型空间-2:", s_a.__proto__); //constructor: ƒ banji(className, teacherName)
//浏览器控制台能看见打印出的结果,constructor下有grade静态属性