js继承java方法吗_面试官问:JS 的继承

原标题:面试官问:JS 的继承

作者:若川

链接:https://segmentfault.com/a/1190000018221673

用过 React的读者知道,经常用 extends继承 React.Component:

// 部分源码

function Component(props, context, updater) {

// ...

}

Component.prototype.setState = function(partialState, callback){

// ...

}

const React = {

Component,

// ...

}

// 使用

class index extends React.Component{

// ...

}

React github源码:https://github.com/facebook/react/blob/master/packages/react/src/ReactBaseClasses.js。

面试官可以顺着这个问JS继承的相关问题,比如:ES6的 class继承用ES5如何实现。据说很多人答得不好。

构造函数、原型对象和实例之间的关系

要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。

代码表示:

function F(){}

var f = new F();

// 构造器

F.prototype.constructor === F; // true

F.__proto__ === Function.prototype; // true

Function.prototype.__proto__ === Object.prototype; // true

Object.prototype.__proto__ === null; // true

// 实例

f.__proto__ === F.prototype; // true

F.prototype.__proto__ === Object.prototype; // true

Object.prototype.__proto__ === null; // true

笔者画了一张图表示:

973e50dad40bc26c1f6011e6b4d63040.png

ES6 extends 继承做了什么操作

我们先看看这段包含静态方法的ES6继承代码:

// ES6

class Parent{

constructor(name){

this.name = name;

}

static sayHello(){

console.log('hello');

}

sayName(){

console.log('my name is ' + this.name);

return this.name;

}

}

class Child extends Parent{

constructor(name, age){

super(name);

this.age = age;

}

sayAge(){

console.log('my age is ' + this.age);

return this.age;

}

}

let parent = new Parent('Parent');

let child = new Child('Child', 18);

console.log('parent: ', parent); // parent: Parent {name: "Parent"}

Parent.sayHello(); // hello

parent.sayName(); // my name is Parent

console.log('child: ', child); // child: Child {name: "Child", age: 18}

Child.sayHello(); // hello

child.sayName(); // my name is Child

child.sayAge(); // my age is 18

其中这段代码里有两条原型链,不信看具体代码。

// 1、构造器原型链

Child.__proto__ === Parent; // true

Parent.__proto__ === Function.prototype; // true

Function.prototype.__proto__ === Object.prototype; // true

Object.prototype.__proto__ === null; // true

// 2、实例原型链

child.__proto__ === Child.prototype; // true

Child.prototype.__proto__ === Parent.prototype; // true

Parent.prototype.__proto__ === Object.prototype; // true

Object.prototype.__proto__ === null; // true

一图胜千言,笔者也画了一张图表示,如图所示:

2d061fc49f0d0a621cc96f1fc3d3df46.png

结合代码和图可以知道,ES6extends继承,主要就是:

1、把子类构造函数(Child)的原型(__proto__)指向了父类构造函数(Parent)。

2、把子类实例child的原型对象(Child.prototype) 的原型(__proto__)指向了父类parent的原型对象(Parent.prototype)。这两点也就是图中用不同颜色标记的两条线。

3、子类构造函数Child继承了父类构造函数Preant的里的属性。使用super调用的(ES5则用call或者apply调用传参)。也就是图中用不同颜色标记的两条线。

看过《Java高级程序设计-第3版》 章节6.3继承的读者应该知道,这2和3小点,正是寄生组合式继承,书中例子没有第1小点。

1和2小点都是相对于设置了__proto__链接。那问题来了,什么可以设置__proto__链接呢。

new、Object.create和Object.setPrototypeOf可以设置proto

new、 Object.create和 Object.setPrototypeOf可以设置 __proto__。

说明一下, __proto__这种写法是浏览器厂商自己的实现。

再结合一下图和代码看一下的 new, new出来的实例的 __proto__指向构造函数的 prototype,这就是 new做的事情。

new 做了什么

1、创建了一个全新的对象。

2、这个对象会被执行[[Prototype]](也就是__proto__)链接。

3、生成的新对象会绑定到函数调用的this。

4、通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。

5、如果函数没有返回对象类型Object(包含Functoin,Array,Date,RegExg,Error),那么new表达式中的函数调用会自动返回这个新的对象。

Object.create:ES5提供的

Object.create(proto,[propertiesObject])方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是undefined)。对于不支持ES5的浏览器,MDN上提供了ployfill方案:MDN Object.create()

// 简版:也正是应用了new会设置__proto__链接的原理。

if(typeof Object.create !== 'function'){

Object.create = function(proto){

function F() {}

F.prototype = proto;

return new F();

}

}

Object.setPrototypeOf:ES6提供的

Object.setPrototypeOf()方法设置一个指定的对象的原型(即内部[[Prototype]]属性)到另一个对象或null:Object.setPrototypeOf(obj,prototype)。

`ployfill`

// 仅适用于Chrome和FireFox,在IE中不工作:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {

obj.__proto__ = proto;

return obj;

}

nodejs源码就是利用这个实现继承的工具函数的。

function inherits(ctor, superCtor) {

if (ctor === undefined || ctor === null)

throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);

if (superCtor === undefined || superCtor === null)

throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);

if (superCtor.prototype === undefined) {

throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',

'Object', superCtor.prototype);

}

Object.defineProperty(ctor, 'super_', {

value: superCtor,

writable: true,

configurable: true

});

Object.setPrototypeOf(ctor.prototype, superCtor.prototype);

}

extends的ES5版本实现

知道了ES6extends继承做了什么操作和设置__proto__的知识点后,把上面ES6例子的用ES5就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:

// ES5 实现ES6 extends的例子

function Parent(name){

this.name = name;

}

Parent.sayHello = function(){

console.log('hello');

}

Parent.prototype.sayName = function(){

console.log('my name is ' + this.name);

return this.name;

}

function Child(name, age){

// 相当于super

Parent.call(this, name);

this.age = age;

}

// new

function object(){

function F() {}

F.prototype = proto;

return new F();

}

function _inherits(Child, Parent){

// Object.create

Child.prototype = Object.create(Parent.prototype);

// __proto__

// Child.prototype.__proto__ = Parent.prototype;

Child.prototype.constructor = Child;

// ES6

// Object.setPrototypeOf(Child, Parent);

// __proto__

Child.__proto__ = Parent;

}

_inherits(Child, Parent);

Child.prototype.sayAge = function(){

console.log('my age is ' + this.age);

return this.age;

}

var parent = new Parent('Parent');

var child = new Child('Child', 18);

console.log('parent: ', parent); // parent: Parent {name: "Parent"}

Parent.sayHello(); // hello

parent.sayName(); // my name is Parent

console.log('child: ', child); // child: Child {name: "Child", age: 18}

Child.sayHello(); // hello

child.sayName(); // my name is Child

child.sayAge(); // my age is 18

我们完全可以把上述ES6的例子通过babeljs(https://babeljs.io/repl)转码成ES5来查看,更严谨的实现。

// 对转换后的代码进行了简要的注释

"use strict";

// 主要是对当前环境支持Symbol和不支持Symbol的typeof处理

function _typeof(obj) {

if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {

_typeof = function _typeof(obj) {

return typeof obj;

};

} else {

_typeof = function _typeof(obj) {

return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;

};

}

return _typeof(obj);

}

// _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。

function _possibleConstructorReturn(self, call) {

if (call && (_typeof(call) === "object" || typeof call === "function")) {

return call;

}

return _assertThisInitialized(self);

}

// 如何 self 是void 0 (undefined) 则报错

function _assertThisInitialized(self) {

if (self === void 0) {

throw new ReferenceError("this hasn't been initialised - super() hasn't been called");

}

return self;

}

// 获取__proto__

function _getPrototypeOf(o) {

_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {

return o.__proto__ || Object.getPrototypeOf(o);

};

return _getPrototypeOf(o);

}

// 寄生组合式继承的核心

function _inherits(subClass, superClass) {

if (typeof superClass !== "function" && superClass !== null) {

throw new TypeError("Super expression must either be null or a function");

}

// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

// 也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true

subClass.prototype = Object.create(superClass && superClass.prototype, {

constructor: {

value: subClass,

writable: true,

configurable: true

}

});

if (superClass) _setPrototypeOf(subClass, superClass);

}

// 设置__proto__

function _setPrototypeOf(o, p) {

_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {

o.__proto__ = p;

return o;

};

return _setPrototypeOf(o, p);

}

// instanceof操作符包含对Symbol的处理

function _instanceof(left, right) {

if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {

return right[Symbol.hasInstance](left);

} else {

return left instanceof right;

}

}

function _classCallCheck(instance, Constructor) {

if (!_instanceof(instance, Constructor)) {

throw new TypeError("Cannot call a class as a function");

}

}

// 按照它们的属性描述符 把方法和静态属性赋值到构造函数的prototype和构造器函数上

function _defineProperties(target, props) {

for (var i = 0; i < props.length; i++) {

var deor = props[i];

deor.enumerable = deor.enumerable || false;

deor.configurable = true;

if ("value" in deor) deor.writable = true;

Object.defineProperty(target, deor.key, deor);

}

}

// 把方法和静态属性赋值到构造函数的prototype和构造器函数上

function _createClass(Constructor, protoProps, staticProps) {

if (protoProps) _defineProperties(Constructor.prototype, protoProps);

if (staticProps) _defineProperties(Constructor, staticProps);

return Constructor;

}

// ES6

var Parent = function () {

function Parent(name) {

_classCallCheck(this, Parent);

this.name = name;

}

_createClass(Parent, [{

key: "sayName",

value: function sayName() {

console.log('my name is ' + this.name);

return this.name;

}

}], [{

key: "sayHello",

value: function sayHello() {

console.log('hello');

}

}]);

return Parent;

}();

var Child = function (_Parent) {

_inherits(Child, _Parent);

function Child(name, age) {

var _this;

_classCallCheck(this, Child);

// Child.__proto__ => Parent

// 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换

// _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。

_this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));

_this.age = age;

return _this;

}

_createClass(Child, [{

key: "sayAge",

value: function sayAge() {

console.log('my age is ' + this.age);

return this.age;

}

}]);

return Child;

}(Parent);

var parent = new Parent('Parent');

var child = new Child('Child', 18);

console.log('parent: ', parent); // parent: Parent {name: "Parent"}

Parent.sayHello(); // hello

parent.sayName(); // my name is Parent

console.log('child: ', child); // child: Child {name: "Child", age: 18}

Child.sayHello(); // hello

child.sayName(); // my name is Child

child.sayAge(); // my age is 18

1、《Java高级程序设计第3版》第6章——面向对象的程序设计

6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址http://www.ituring.com.cn/book/946,后文放出github链接,里面包含这几种继承的代码demo。

2、《Java面向对象编程第2版》第6章——继承

12种继承的方案:

1、原型链法(仿传统)

2、仅从原型继承法

3、临时构造器法

4、原型属性拷贝法

5、全属性拷贝法(即浅拷贝法)

6、深拷贝法

7、原型继承法

8、扩展与增强模式

9、多重继承法

10、寄生继承法

11、构造器借用法

12、构造器借用与属性拷贝法

3、《ES6标准入门》第21章——class的继承

http://es6.ruanyifeng.com/#docs/class-extends

4、《深入理解ES6》第9章——Java中的类

5、《你不知道的Java》上卷第6章——行为委托和附录A(ES6中的class)

总结

继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。

继承方法可以有很多,重点在于必须理解并熟。

悉这些对象、原型以及构造器的工作方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。

回顾寄生组合式继承,主要就是三点:

1、子类构造函数的 __proto__指向父类构造器,继承父类的静态方法

2、子类构造函数的 prototype的 __proto__指向父类构造器的 prototype,继承父类的方法。

3、子类构造器里调用父类构造器,继承父类的属性。

行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和demo展示es6-extends,结合console、source面板查看更佳。

读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。

关于作者

常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。

个人博客:https://lxchuan12.github.io/

segmentfault前端视野专栏:https://segmentfault.com/blog/lxchuan12

掘金专栏:https://juejin.im/user/57974dc55bbb500063f522fd/posts

知乎前端视野专栏:https://zhuanlan.zhihu.com/lxchuan12

github:https://github.com/lxchuan12)

●编号900,输入编号直达本文返回搜狐,查看更多

责任编辑:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值