资讯专栏INFORMATION COLUMN

【JS基础】对象继承的定义与实现

darry / 1274人阅读

简介
类的概念,本身在javascript的语言上是不存在的, 但由于最近人们使用ES6语法,TS语言上都会有的class extends 继承的概念, 下面我们需要使用原生js, 结合原型链,实现类的 继承,多态
ES5实现继承

原型继承

借用构造函数继承

mixin 复制继承

寄生继承

原型继承方式

原型继承, 主要利用对象的原型链 __proto__, 每一个对象都拥有__proto__, 它指向的是构造函数的prototype 原型对象.

一个对象的属性或函数的寻找会经历以下几个步骤。
以定义 var o = {};, 执行 var toString = o.toString 为例.

执行 var tmp = o,作为临时引用 (为了描述使用)

尝试检查 tmp 是否自定义toString(),如果存在自定义属性则立即执行。如果当前对象无定义该属性, 进入第3步

尝试检查 tmp 是否使用 Object.defineProperty 定义toString 的属性描述, 如果存在定义,则直接引用,如果不存在则进入第4步

尝试检查 tmp 是否存在 __proto__,如果存在,则将tmp = o.__proto__, 执行第2步; 如果不存在,则返回 undefined, 属性查找结束;

具体案例

function Animal () {
  throw new Error("抽象类, 不允许直接实例化");
}
Animal.prototype.voice = function () {
  console.log("the " + this.name + " sound");
}

function Dog () {
  this.name = "dog";
}
Dog.prototype = Object.create(Animal.prototype);

// 显示指向
Dog.prototype.constructor = Dog;

var dog = new Dog();

dog.voice(); // the dog sound
console.log(dog instanceof Dog);
console.log(dog instanceof Animal);

// 隐世指向 Animal.prototype.constructor
console.log(dog.__proto__.constructor === Animal);

优点:

可以使用 instanceof 检测是否是某一个父类

原型链实现方式

缺点:

无法借用父类构造函数

借用构造函数

// 模拟调用父类函数
Object.prototype.super = function (proto, name) {
  var args = Array.from(arguments).slice(1);
  var proto = proto.__proto__;
  while (proto && null == proto[name]) {
    proto = proto[name];
  }
  if (proto && typeof proto[name] === "function") {
    return proto[name].apply(this, args);
  }
  console.warn("the instance have not super " + name + " function");
};
function Animal (name) {
  this.name = name;
}

Animal.prototype.voice = function () {
  console.log(this.name + " sound");
};

function Dog (name, type) {
  Animal.apply(this, [name]);
  this.type = type;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Animal;

Dog.prototype.voice = function () {
  console.log("the dog type is " + this.type);
  this.super(Animal.prototype, "voice");
};

var dog = new Dog("二哈", "哈士奇");
dog.voice();
// the dog type is 哈士奇
// 二哈 sound

优点:

能够借用父类构造函数

具备链式调用函数

缺点:

子类构造函数需要调用父类构造函数

mixin复制继承

依靠对象拷贝属性的方式, 给予一个源对象未有的属性赋值

function mixin(source, target) {
  for (var name in target) {
    if (!source[name]) {
      source[name] = target[name];
    }
  }
  return source;
}

var Animal = {
  name: "animal",
  voice: function () {
    console.log("the name is " , this.name);
    console.log("voice~~");
  }
};
var Cat = mixin({
  name: "cat",  
  sound: function () {
    return this.voice();
  }
}, Animal);

var helloKitty = mixin({
  name: "hello keitty"
}, Cat);

helloKitty.sound();

优点:

实现简单,只需要进行复制属性和方法

缺点:

处理对象都为对象, 没有处理构造函数

无法实现子类调用父类的场景

寄生继承

寄生继承属于重写, 新增父类创建的对象的属性, 返回扩展的对象

function Animal() {
  this.speed = 10;
}

Animal.prototype.run = function () {
  console.log("speed is " + this.speed);
}

function Cat () {
  var animal = new Animal();
  var runFn = animal.run;

  animal.speed = 20;  
  animal.run = function () {
    console.log("the cat will run");
    runFn.apply(this, arguments);
  };
  return animal;
}
var cat = new Cat();
console.log(cat instanceof Cat);

优点:

结合原型属性和实例属性实现方案

缺点:

无法共享属性, 每一个新的对象都创建新的实例属性和方法

Object.create

Object.create 是ES5定义的方法, 相比于字面量对象,构造函数对象, 又一种新的创建对象的方式。

var prototype = {foo: 1};
var o = Object.create(prototype);
console.log(o.foo); // 1
o.foo = 100;
console.log(o.foo); // 100
delete o.foo;
console.log(o.foo); // 1
console.log(o.__proto__ === prototype); // true

从上面可以看见, Object.create 传入一个对象,同时会返回一个新的对象,而这个新的对象的__proto__指向传入对象

Object.create(null)

返回一个无原型链的空对象, 对象的所有属性均为实例属性

Object.create 的 polyfill

// 简化版 polyfill
Object.create = Object.create || function (proto) {
  function F() {}
  F.prototype = proto;
  return new F();
};
ES6 的 class extends

说完ES5的实现方式,我们来聊聊ES6自带的语法。 classclass extends

ES6里的类

参照其它语言,如 java, 类中存在静态属性, 静态方法, 实例属性,实例方法.

声明一个类

class Rectangle {
  // 类的构造函数
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
let rect = new Reactangle(320, 240);
console.log(rect.height, rect.width);

注意: 类不存在声明提前的概念,必须先定义后使用

使用 extends 创建子类
class Animal { 

  constructor(name) {
    // 实例属性
    this.name = name;
  }
  // 原型属性描述
  get fullname () {
    console.log(this.name);
  }

  // 原型方法
  speak() {
    console.log(this.name + " makes a noise.");
  }
}

// 静态属性
Animal.name = "Animal";

class Dog extends Animal {
  construcotr(name) {
    // 调用父类构造函数
    super(name);
  }
  speak() {
    console.log(this.name + " barks.");
  }
  // 静态方法只适合创建工具函数
  // 返回 undefined
  static eat() {
    return this;
  }
}

var d = new Dog("Mitzie");
// "Mitzie barks."
d.speak();

实际上ES6的classclass extends 也是使用的原型链方式实现继承关系。 super 是一个关键词, 实际上是指向父类的prototype, 在constructor 使用super(), 可以调用父类的构造函数, 使用super.method() 可以调用父类的原型方法
原型属性采用ES5的defineProperty定义属性描述来实现。

小结

目前JS的使用场景越来越广, 面向对象编程的使用也越来越多, 前端已经Node.js都有需要用到类与继承。 同时这也是多年来不变的前端JS考题。

相关知识推荐

Object.create

ES6 Class

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94015.html

相关文章

  • 重温JS基础--继承

    摘要:继承了如上,我们通过方法借调了超类的构造函数,实际上是在新创建的实力环境下调用了构造函数。组合继承组合继承的基本思想将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。继承方法在上面这个例子中,构造函数定义了两个属性和。 在ECMAScript中只支持实现继承,而且实现继承主要是依靠原型链来实现的。 1. 什么是原型链 继承基本思想:利用原型让一个引用类型继承另一个...

    sixleaves 评论0 收藏0
  • Js基础知识(二) - 原型链继承精彩讲解

    摘要:有了原型链,就有了继承,继承就是一个对象像继承遗产一样继承从它的构造函数中获得一些属性的访问权。这里其实就是一个原型链与继承的典型例子,开发中可能构造函数复杂一点,属性定义的多一些,但是原理都是一样的。 作用域、原型链、继承与闭包详解 注意:本章讲的是在es6之前的原型链与继承。es6引入了类的概念,只是在写法上有所不同,原理是一样的。 几个面试常问的几个问题,你是否知道 insta...

    mrcode 评论0 收藏0
  • Js基础知识(二) - 原型链继承精彩讲解

    摘要:有了原型链,就有了继承,继承就是一个对象像继承遗产一样继承从它的构造函数中获得一些属性的访问权。这里其实就是一个原型链与继承的典型例子,开发中可能构造函数复杂一点,属性定义的多一些,但是原理都是一样的。 作用域、原型链、继承与闭包详解 注意:本章讲的是在es6之前的原型链与继承。es6引入了类的概念,只是在写法上有所不同,原理是一样的。 几个面试常问的几个问题,你是否知道 insta...

    lingdududu 评论0 收藏0
  • ES6—class面向对象编程(8)

    摘要:接下来我们看下类的写法,这个就很接近于传统面向对象语言了。如果你想了解传统面向对象语言,这里是一个好切入点。作为对象时,指向父类的原型对象。这些就是为将来在中支持面向对象的类机制而预留的。 在ES5中,我们经常使用方法或者对象去模拟类的使用,并基于原型实现继承,虽然可以实现功能,但是代码并不优雅,很多人还是倾向于用 class 来组织代码,很多类库、框架创造了自己的 API 来实现 c...

    wangjuntytl 评论0 收藏0

发表评论

0条评论

darry

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<