JS 继承详解

原型链继承

原型链继承实现的本质: 是改变 构造函数的.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静态属性
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容