摘要:函数继承是在JS里比较基础也是比较重要的一部分,而且也是面试中常常要问到的。下面带你快速了解JS中有哪几种是经常出现且必须掌握的继承方式。掌握下面的内容面试也差不多没问题啦~

本文分享自华为云社区《人类高质量JS函数继承》,作者:北极光之夜。

一. 前言:

函数继承是在JS里比较基础也是比较重要的一部分,而且也是面试中常常要问到的。下面带你快速了解JS中有哪几种是经常出现且必须掌握的继承方式。掌握下面的内容面试也差不多没问题啦~

二.原型链继承:

原型链继承的要点在于父类的实例作为子类的原型。直接看下面这个例子:

 // 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数 Jack
function Jack() {}
// 父类的实例作为子类的原型 (-------------------实现核心--------------------------)
Jack.prototype = new Person();

现在我们创建两个Jack 的实例,测试看是否实现了继承Person:

  var jack1 = new Jack();
var jack2 = new Jack();
jack2.nature[0] = "sea";
jack1.sayLove();
jack2.sayLove();
console.log(jack1.nature);
console.log(jack2.nature);

看运行结果确实继承了,能执行sayLove方法。但有甚多缺点,创建Jack实例的时候传递不了参数name和age,而且不同实例间nature引用类型属性相互影响,一个改变那都改变:

三.借用构造函数继承(对象伪装):

核心在于“盗用构造函数”(constructor stealing)。在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象为上下文执行构造函数。它能解决原型链继承中传参数和引用类型属性冲突。还是直接看例子:

// 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数 Lucy
function Lucy(name, age) {
// 通过call把this指向Lucy,相当于拷贝了一份父函数 Person 里的内容(---------实现核心--------------)
Person.call(this, name, age);
}
//给子函数原型上也定义一个方法
Lucy.prototype.syaName = function () {
console.log("My name is " + this.name);
};

现在我们创建两个Lucy 的实例,测试看是否实现了继承Person:

   var lucy1 = new Lucy("lucy1", "20");
var lucy2 = new Lucy("lucy2", "22");
lucy2.nature[0] = "sea";
console.log(lucy1.name);
console.log(lucy1.nature);
console.log(lucy2.nature);
lucy1.syaName();
lucy2.syaName();
lucy1.sayLove();

结果看可以继承了,能传参数,引用类型属性也不互相影响,但是缺点显而易见,可以看到报错,无法使用父类的原型上的方法sayLove。

四.组合继承:

组合继承就是结合了原型链继承和借用构造函数继承两者的核心实现的一种继承方法,既能传递参数,引用类型属性也互不影响,同时子类也能获取得到父类的方法。这也是目前比较常用的继承方式。直接看例子:

 // 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数Lisa
function Lisa(name, age) {
// 通过call把this指向Lisa,相当于拷贝了一份父函数 Person 里的内容(------实现核心-----------)
Person.call(this, name, age);
}
// 父类的实例作为子类的原型 (--------------实现核心-------------------)
Lisa.prototype = new Person();
//小知识点,这里是让Lisa的constructor重新指向Lisa,不然因为Lisa的原型为Person实例,constructor会指向Person
Lisa.prototype.constructor = Lisa;

现在我们创建两个Lisa 的实例,测试看是否实现了继承Person:

    var lisa1 = new Lisa("lisa1", "20");
var lisa2 = new Lisa("lisa2", "21");
lisa2.nature[0] = "sea";
console.log(lisa1.name);
console.log(lisa1.nature);
console.log(lisa2.nature);
lisa1.sayLove();
lisa2.sayLove();

可以看到基本上实现了我们继承的功能。也修补了原型链和借用构造函数继承的缺点。但是呢,它还是有一个小缺点,就是可以看到在代码注释实现核心那,两次都调用了Person,那么Lisa原型上和实例上有了两份相同的属性,那就会多少有一些性能浪费。

五.寄生组合继承:

其实寄生组合继承和组合继承差不多的,就是多了一个解决组合继承上原型和实例产生两份相同属性的缺点。解决核心是我们既然只是想要子类原型赋值为父类原型,那没必要new一个父类实例。直接创造一个新对象,它值为父类的原型,再将它赋值给子类原型就行了。

其中用到Object.create(proto,[propertiesObject])这个方法创建一个新对象。相当于新对象的__proto__为其参数proto。当然Object.create可能低版本ie没有,所以下面也自定义封装了Object.create方法,当然只是简单封装。直接看例子:

 // 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数 Andy
function Andy(name, age) {
Person.call(this, name, age);
}
// 如果没有 Object.create()方法,简单封装下
if (!Object.create) {
Object.create = function (proto) {
function Temp() {}
Temp.prototype = proto;
return new Temp();
};
}
// 调用Object.create方法,新建一对像,其__proto__为Person.prototype,并赋值给 Andy.prototype (-------实现核心----------)
Andy.prototype = Object.create(Person.prototype);
//修改constructor指向
Andy.prototype.constructor = Andy;

现在我们创建两个Andy的实例,测试看是否实现了继承Person:

   console.log(Andy.prototype.__proto__ === Person.prototype);
var andy1 = new Andy("andy1", "20");
var andy2 = new Andy("andy2", "21");
andy2.nature[0] = "sea";
console.log(andy1.name);
console.log(andy1.nature);
console.log(andy2.nature);
andy1.sayLove();
andy2.sayLove();

完美运行:

六.class继承:

ES6出了class语法糖之后,就可以通过class定义类并实现类的继承。直接看例子:

//定义一个父类 Animal
class Animal {
//这里constructor指向类本身,跟es5行为一样的
constructor(name) {
this.name = name;
}
likeEat() {
console.log(this.name + " like eat " + this.food);
}
}
//定义一个子类 Dog ,通过 extends 继承父类Animal
class Dog extends Animal {
constructor(name, food) {
//通过super(属性名)继承父类属性
super(name);
this.food = food;
}
likeEat() {
//通过super.+父类方法 实现继承父类方法
super.likeEat();
}
}

new一个Dog实例,测试看看,Dog是否继承了Animal:

 var jinmao = new Dog("jinmao", "bone");
console.log(jinmao.name);
jinmao.likeEat();

可以看到完美实现了:

七.总结:

上面就是这篇文章的全部内容啦,如果有错误的地方恳请指出~

点击关注,第一时间了解华为云新鲜技术~

学会这5种JS函数继承方式,前端面试你至少成功50%的更多相关文章

  1. js的5种继承方式——前端面试

    js主要有以下几种继承方式:对象冒充,call()方法,apply()方法,原型链继承以及混合方式.下面就每种方法就代码讲解具体的继承是怎么实现的. 1.继承第一种方式:对象冒充 function P ...

  2. js各种继承方式和优缺点的介绍

    js各种继承方式和优缺点的介绍 作者: default 参考网址2 写在前面 本文讲解JavaScript各种继承方式和优缺点. 注意: 跟<JavaScript深入之创建对象>一样,更像 ...

  3. js 中继承方式小谈

    题外话 前段时间面试中笔试题有这道题目: 请实现一个继承链,要求如下: 构造函数A():构造函数中有consoleA方法,可以实现console.log("a") 实例对象 a:a ...

  4. Javascript学习笔记:3种定义函数的方式

    ①使用函数声明语法定义函数 function sum(num1,num2){ return num1+num2; } ②使用函数表达式定义函数 var sum=function(num1,num2){ ...

  5. js函数验证方式:验证是否是数字,支持小数,负数

    验证 datatype="/^\d+(\.\d+)?$/" validatform验证是否是数字 支持小数点 datatype="d" 貌似支持小数 js函数验 ...

  6. javascript两种声明函数的方式的一次深入解析

    声明函数的方式 javascript有两种声明函数的方式,一个是函数表达式定义函数,也就是我们说的匿名函数方式,一个是函数语句定义函数,下面看代码: /*方式一*/ var FUNCTION_NAME ...

  7. JS中继承方式总结

    说在前面:为了使代码更为简洁方便理解, 本文中的代码均将"非核心实现"部分的代码移出. 一.原型链方式关于原型链,可点击<深入浅出,JS原型链的工作原理>,本文不再重复 ...

  8. 【深入JavaScript】一种JS的继承方法

    这些天读了John Resig的<Secrets of JavaScript Ninja>,其中讨论到JS中实现继承的方案,非常有趣,自己探索了一下,形成了笔记,放到这里. 这个方案在Re ...

  9. 漫谈JS 的继承方式

    一.原型链原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针.如果:我们让 ...

  10. js的继承方式分别适合哪些应用场景?

    一.原型链 利用 Person.prototype = new Animal("Human") 实现继承: static式继承.能继承Animal.prototype.不可多重继承 ...

随机推荐

  1. Unity - UIWidgets 3. 页面跳转

    Flutter的Route概念, 移动开发常指Page, 在android中指activity, ios中指viewcontroller, UGUI中常称为Panel\Form\View? 大概说的就 ...

  2. CAP 定理的含义(转)

    分布式系统(distributed system)正变得越来越重要,大型网站几乎都是分布式的. 分布式系统的最大难点,就是各个节点的状态如何同步.CAP 定理是这方面的基本定理,也是理解分布式系统的起 ...

  3. 关于XML的总结——Schema和DTD(转)

    XML DTD(XML的文档类型定义)是近几年来XML技术领域所使用的最广泛的一种模式.但是,由于XML DTD并不能完全满足XML自动化处理的要求,例如不能很好实现应用程序不同模块间的相互协调,缺乏 ...

  4. Axure RP Pro 6.5如何创建Tab标签效果(转)

    http://jingyan.baidu.com/article/ce09321b3c665f2bff858f01.html  Axure RP Pro 能帮助网站需求设计者,快捷而简便的创建基于网站 ...

  5. 数据结构与算法 | 记忆化搜索(Memorize Search)

    在本系列的文章中已经写了二叉树(Binary Tree).深搜(DFS)与广搜(BFS).哈希表(Hash Table)等等,计划接下来要写的是动态规划(Dynamic Programming,DP) ...

  6. 鸿蒙开发学习(一)之ArkTS

    目录 TypeScript语法 基础 module ArkTS 基本UI描述 基本概念 状态管理 页面级变量的状态管理 @State @Prop @Link 应用级变量的状态管理 开发入门 应用模型 ...

  7. 从一道题来看看golang中的slice作为参数时的现象

    1.题目 最近看群友在群里问一道关于golang中slice的题,题目如下: package main import "fmt" func main() { k := []int{ ...

  8. JavaWeb开发-CSS基础

    2.CSS层叠样式表基本语法 层叠样式表,用来控制页面的样式 (1)CSS的三种引入方式 内部样式表:适合学习使用,将CSS代码写在style标签里面,style标签嵌套在title里 外部样式表:开 ...

  9. httpclients 和 okhttp 区别

    HttpClient使用介绍使用HttpClient发送请求主要分为以下几步骤: 创建 CloseableHttpClient对象或CloseableHttpAsyncClient对象,前者同步,后者 ...

  10. [ABC327G] Many Good Tuple Problems

    题目链接 简化题意:有一个 \(n\) 个点的图,问有多少个长度为 \(M\) 的边序列,满足连边后图是二分图. \(n\le 30,m\le 10^9\) 考虑先强制要求无重边. 定义 \(f_{i ...