澳门新萄京官方网站-www.8455.com-澳门新萄京赌场网址

3天理解面向对象,JavaScript的原型继承详解

2019-06-29 作者:澳门新萄京赌场网址   |   浏览(100)

继承的实现方式及原型概述

2015/07/15 · JavaScript · 原型, 继承

原文出处: 名一的博客   

对于 OO 语言,有一句话叫“Everything is object”,虽然 JavaScript 不是严格意义上的面向对象语言,但如果想要理解 JS 中的继承,这句话必须时刻铭记于心。

JS 的语法非常灵活,所以有人觉得它简单,因为怎么写都是对的;也有人觉得它难,因为很难解释某些语法的设计,谁能告诉我为什么 typeof null 是 object 而 typeof undefined 是 undefined 吗?并且这是在 null == undefined 的前提下。很多我们自认为“懂”了的知识点,细细琢磨起来,还是会发现有很多盲点,“无畏源于无知”吧……

JavaScript的原型继承详解

   JavaScript是一门面向对象的语言。在JavaScript中有一句很经典的话,万物皆对象。既然是面向对象的,那就有面向对象的三大特征:封装、继承、多态。这里讲的是JavaScript的继承,其他两个容后再讲。

  JavaScript的继承和C 的继承不大一样,C 的继承是基于类的,而JavaScript的继承是基于原型的。

  现在问题来了。

  原型是什么?原型我们可以参照C 里的类,同样的保存了对象的属性和方法。例如我们写一个简单的对象

  代码如下:

  function Animal(name) {

  this.name = name;

  }

  Animal.prototype.setName = function(name) {

  this.name = name;

  }

  var animal = new Animal("wangwang");

  我们可以看到,这就是一个对象Animal,该对象有个属性name,有个方法setName。要注意,一旦修改prototype,比如增加某个方法,则该对象所有实例将同享这个方法。例如

  代码如下:

  function Animal(name) {

  this.name = name;

  }

  var animal = new Animal("wangwang");

  这时animal只有name属性。如果我们加上一句,

  代码如下:

  Animal.prototype.setName = function(name) {

  this.name = name;

  }

  这时animal也会有setName方法。

  继承本复制——从空的对象开始我们知道,JS的基本类型中,有一种叫做object,而它的最基本实例就是空的对象,即直接调用new Object()生成的实例,或者是用字面量{ }来声明。空的对象是“干净的对象”,只有预定义的属性和方法,而其他所有对象都是继承自空对象,因此所有的对象都拥有这些预定义的 属性与方法。原型其实也是一个对象实例。原型的含义是指:如果构造器有一个原型对象A,则由该构造器创建的实例都必然复制自A。由于实例复制自对象A,所以实例必然继承了A的所有属性、方法和其他性质。那么,复制又是怎么实现的呢?方法一:构造复制每构造一个实例,都从原型中复制出一个实例来,新的实例与原型占用了相同的内存空间。这虽然使得obj1、obj2与它们的原型“完全一致”,但也非常不经济——内存空间的消耗会急速增加。如图:

澳门新萄京官方网站 1

  方法二:写时复制这种策略来自于一致欺骗系统的技术:写时复制。这种欺骗的典型示例就是操作系统中的动态链接库(DDL),它的内存区总是写时复制的。如图:

澳门新萄京官方网站 2

  我们只要在系统中指明obj1和obj2等同于它们的原型,这样在读取的时候,只需要顺着指示去读原型即可。当需要写对象(例如obj2)的属性时,我们就复制一个原型的映像出来,并使以后的操作指向该映像即可。如图:

澳门新萄京官方网站 3

  这种方式的优点是我们在创建实例和读属性的时候不需要大量内存开销,只在第一次写的时候会用一些代码来分配内存,并带来一些代码和内存上的开销。但此后就不再有这种开销了,因为访问映像和访问原型的效率是一致的。不过,对于经常进行写操作的系统来说,这种方法并不比上一种方法经济。方法三:读遍历这种方法把复制的粒度从原型变成了成员。这种方法的特点是:仅当写某个实例的成员,将成员的信息复制到实例映像中。当写对象属性时,例如(obj2.value=10)时,会产生一个名为value的属性值,放在obj2对象的成员列表中。看图:

澳门新萄京官方网站 4

  可以发现,obj2仍然是一个指向原型的引用,在操作过程中也没有与原型相同大小的对象实例创建出来。这样,写操作并不导致大量的内存分配,因此内存的使用上就显得经济了。不同的是,obj2(以及所有的对象实例)需要维护一张成员列表。这个成员列表遵循两条规则:保证在读取时首先被访问到如果在对象中没有指定属性,则尝试遍历对象的整个原型链,直到原型为空或或找到该属性。原型链后面会讲。显然,三种方法中,读遍历是性能最优的。所以,JavaScript的原型继承是读遍历的。constructor熟悉C 的人看完最上面的对象的代码,肯定会疑惑。没有class关键字还好理解,毕竟有function关键字,关键字不一样而已。但是,构造函数呢?实际上,JavaScript也是有类似的构造函数的,只不过叫做构造器。在使用new运算符的时候,其实已经调用了构造器,并将this绑定为对象。例如,我们用以下的代码

  代码如下:

  var animal = Animal("wangwang");

  animal将是undefined。有人会说,没有返回值当然是undefined。那如果将Animal的对象定义改一下:

  代码如下:

  function Animal(name) {

  this.name = name;

  return this;

  }

  猜猜现在animal是什么?

  此时的animal变成window了,不同之处在于扩展了window,使得window有了name属性。这是因为this在没有指定的情况下,默认指向window,也即最顶层变量。只有调用new关键字,才能正确调用构造器。那么,如何避免用的人漏掉new关键字呢?我们可以做点小修改:

  代码如下:

  function Animal(name) {

  if(!(this instanceof Animal)) {

  return new Animal(name);

  }

  this.name = name;

  }

  这样就万无一失了。构造器还有一个用处,标明实例是属于哪个对象的。我们可以用instanceof来判断,但instanceof在继承的时候对祖先对象跟真正对象都会返回true,所以不太适合。constructor在new调用时,默认指向当前对象。

  代码如下:

  console.log(Animal.prototype.constructor === Animal); // true

  我们可以换种思维:prototype在函数初始时根本是无值的,实现上可能是下面的逻辑

  // 设定__proto__是函数内置的成员,get_prototyoe()是它的方法

  代码如下:

  var __proto__ = null;

  function get_prototype() {

  if(!__proto__) {

  __proto__ = new Object();

  __proto__.constructor = this;

  }

  return __proto__;

  }

  这样的好处是避免了每声明一个函数都创建一个对象实例,节省了开销。constructor是可以修改的,后面会讲到。基于原型的继承继承是什么相信大家都差不多知道,就不秀智商下限了。

  JS的继承有好几种,这里讲两种

  1. 方法一这种方法最常用,安全性也比较好。我们先定义两个对象

  代码如下:

  function Animal(name) {

  this.name = name;

  }

  function Dog(age) {

  this.age = age;

  }

  var dog = new Dog(2);

  要构造继承很简单,将子对象的原型指向父对象的实例(注意是实例,不是对象)

  代码如下:

  Dog.prototype = new Animal("wangwang");

  这时,dog就将有两个属性,name和age。而如果对dog使用instanceof操作符

  代码如下:

  console.log(dog instanceof Animal); // true

  console.log(dog instanceof Dog); // false

  这样就实现了继承,但是有个小问题

  代码如下:

  console.log(Dog.prototype.constructor === Animal); // true

  console.log(Dog.prototype.constructor === Dog); // false

  可以看到构造器指向的对象更改了,这样就不符合我们的目的了,我们无法判断我们new出来的实例属于谁。因此,我们可以加一句话:

  代码如下:

  Dog.prototype.constructor = Dog;

  再来看一下:

  复制代码 代码如下:

  console.log(dog instanceof Animal); // false

  console.log(dog instanceof Dog); // true

  done。这种方法是属于原型链的维护中的一环,下文将详细阐述。2. 方法二这种方法有它的好处,也有它的弊端,但弊大于利。先看代码

  代码如下:

  function Animal(name) {

  this.name = name;

  }

  Animal.prototype.setName = function(name) {

  this.name = name;

  }

  function Dog(age) {

  this.age = age;

  }

  Dog.prototype = Animal.prototype;

  这样就实现了prototype的拷贝。

  这种方法的好处就是不需要实例化对象(和方法一相比),节省了资源。弊端也是明显,除了和上文一样的问题,即constructor指向了父对象,还只能复制父对象用prototype声明的属性和方法。也即是说,上述代码中,Animal对象的name属性得不到复制,但能复制setName方法。最最致命的是,对子对象的prototype的任何修改,都会影响父对象的prototype,也就是两个对象声明出来的实例都会受到影响。所以,不推荐这种方法。

  原型链

  写过继承的人都知道,继承可以多层继承。而在JS中,这种就构成了原型链。上文也多次提到了原型链,那么,原型链是什么?一个实例,至少应该拥有指向原型的proto属性,这是JavaScript中的对象系统的基础。不过这个属性是不可见的,我们称之为“内部原型链”,以便和构造器的prototype所组成的“构造器原型链”(亦即我们通常所说的“原型链”)区分开。我们先按上述代码构造一个简单的继承关系:

  代码如下:

  function Animal(name) {

  this.name = name;

  }

  function Dog(age) {

  this.age = age;

  }

  var animal = new Animal("wangwang");

  Dog.prototype = animal;

  var dog = new Dog(2);

  提醒一下,前文说过,所有对象都是继承空的对象的。所以,我们就构造了一个原型链:

澳门新萄京官方网站 5

  我们可以看到,子对象的prototype指向父对象的实例,构成了构造器原型链。子实例的内部proto对象也是指向父对象的实例,构成了内部原型链。当我们需要寻找某个属性的时候,代码类似于

  代码如下:

  function getAttrFromObj(attr, obj) {

  if(typeof(obj) === "object") {

  var proto = obj;

  while(proto) {

  if(proto.hasOwnProperty(attr)) {

  return proto[attr];

  }

  proto = proto.__proto__;

  }

  }

  return undefined;

  }

  在这个例子中,我们如果在dog中查找name属性,它将在dog中的成员列表中寻找,当然,会找不到,因为现在dog的成员列表只有age这一项。接着它会顺着原型链,即.proto指向的实例继续寻找,即animal中,找到了name属性,并将之返回。假如寻找的是一个不存在的属性,在animal中寻找不到时,它会继续顺着.proto寻找,找到了空的对象,找不到之后继续顺着.proto寻找,而空的对象的.proto指向null,寻找退出。

  原型链的维护我们在刚才讲原型继承的时候提出了一个问题,使用方法一构造继承时,子对象实例的constructor指向的是父对象。这样的好处是我们可以通过constructor属性来访问原型链,坏处也是显而易见的。一个对象,它产生的实例应该指向它本身,也即是

  代码如下:

  (new obj()).prototype.constructor === obj;

  然后,当我们重写了原型属性之后,子对象产生的实例的constructor不是指向本身!这样就和构造器的初衷背道而驰了。我们在上面提到了一个解决方案:

  代码如下:

  Dog.prototype = new Animal("wangwang");

  Dog.prototype.constructor = Dog;

  看起来没有什么问题了。但实际上,这又带来了一个新的问题,因为我们会发现,我们没法回溯原型链了,因为我们没法寻找到父对象,而内部原型链的.proto属性是无法访问的。于是,SpiderMonkey提供了一个改良方案:在任何创建的对象上添加了一个名为__proto__的属性,该属性总是指向构造器所用的原型。这样,对任何constructor的修改,都不会影响__proto__的值,就方便维护constructor了。

  但是,这样又两个问题:

  __proto__是可以重写的,这意味着使用它时仍然有风险

  __proto__是spiderMonkey的特殊处理,在别的引擎(例如JScript)中是无法使用的。

  我们还有一种办法,那就是保持原型的构造器属性,而在子类构造器函数内初始化实例的构造器属性。

  代码如下:改写子对象

  代码如下:

  function Dog(age) {

  this.constructor = arguments.callee;

  this.age = age;

  }

  Dog.prototype = new Animal("wangwang");

  这样,所有子对象的实例的constructor都正确的指向该对象,而原型的constructor则指向父对象。虽然这种方法的效率比较低,因为每次构造实例都要重写constructor属性,但毫无疑问这种方法能有效解决之前的矛盾。ES5考虑到了这种情况,彻底的解决了这个问题:可以在任意时候使用Object.getPrototypeOf() 来获得一个对象的真实原型,而无须访问构造器或维护外部的原型链。因此,像上一节所说的寻找对象属性,我们可以如下改写:

  代码如下:

  function getAttrFromObj(attr, obj) {

  if(typeof(obj) === "object") {

  do {

  var proto = Object.getPrototypeOf(dog);

  if(proto[attr]) {

  return proto[attr];

  }

  }

  while(proto);

  }

  return undefined;

  }

  当然,这种方法只能在支持ES5的浏览器中使用。为了向后兼容,我们还是需要考虑上一种方法的。更合适的方法是将这两种方法整合封装起来,这个相信读者们都非常擅长,这里就不献丑了。

JavaScript是一门面向对象的语言。在JavaScript中有一句很经典的话,万物皆对象。既然是面向对象的,那就有面向对象...

JavaScript是一门面向对象的语言。在JavaScript中有一句很经典的话,万物皆对象。既然是面向对象的,那就有面向对象的三大特征:封装、继承、多态。这里讲的是JavaScript的继承,其他两个容后再讲。

javaScript:3天理解面向对象(2)


Javascript语言的继承机制一直很难被人理解。

1. 简单对象

既然是讲继承,自然是从最简单的对象说起:

JavaScript

var dog = { name: 'tom' }

1
2
3
var dog = {
  name: 'tom'
}

这便是对象直接量了。每一个对象直接量都是 Object 的子类,即

JavaScript

dog instanceof Object; // true

1
dog instanceof Object; // true

JavaScript的继承和C 的继承不大一样,C 的继承是基于类的,而JavaScript的继承是基于原型的。

prototype

理解:每个构造函数都有prototype原型属性,这个属性是对象类型,这个属性里面有两个属性constructor和proto;原型属性的constructor指向构造函数;(原型对象上面的属性proto我们今天先不考虑)实例对象的proto指向构造函数的原型;

根据案例来讲解:

function Person(name,age){
    this.name=name;
    this.age=age;
 }
 Person.prototype.showName=function(){
      console.log(this.name "helloWord!");
}
var p1=new Person("小白",18);
var p2=new Person("小黄",18);

它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。

2. 构造函数

JS 中的构造函数与普通函数并没有什么两样,只不过在调用时,前面加上了 new 关键字,就当成是构造函数了。

JavaScript

function Dog(name) { this.name = name; } var dog = new Dog('tom'); dog instanceof Dog; // true

1
2
3
4
5
6
7
function Dog(name) {
  this.name = name;
}
 
var dog = new Dog('tom');
 
dog instanceof Dog; // true

两个问题,第一,不加 new 关键字有什么后果?

那么 Dog 函数中的 this 在上下文(Context)中被解释为全局变量,具体在浏览器端的话是 window 对象,在 node 环境下是一个 global 对象。

第二,dog 的值是什么?很简单,undefined 。Dog 函数没有返回任何值,执行结束后,dog 的值自然是 undefined 。

关于 new 的过程,这里也顺便介绍一下,这个对后面理解原型(prototype)有很大的帮助:

  1. 创建一个空的对象,仅包含 Object 的属性和方法。
  2. 将 prototype 中的属性和方法创建一份引用,赋给新对象。
  3. 将 this 上的属性和方法新建一份,赋给新对象。
  4. 返回 this 对象,忽略 return 语句。

需要明确的是,prototype 上的属性和方法是实例间共享的,this 上的属性和方法是每个实例独有的。

现在问题来了。

这是一个标准的构造函数,接下来我们来分析一下,prototype这个属性;

Brendan Eich设计javascript之初是为了实现网页与浏览器之间交互的一种简单的脚本语言

3. 引入 prototype

现在为 Dog 函数加上 prototype,看一个例子:

JavaScript

function Dog(name) { this.name = name; this.bark = function() {}; } Dog.prototype.jump = function() {}; Dog.prototype.species = 'Labrador'; Dog.prototype.teeth = ['1', '2', '3', '4']; var dog1 = new Dog('tom'), dog2 = new Dog('jerry'); dog1.bark !== dog2.bark; // true dog1.jump === dog2.jump; // true dog1.teeth.push('5'); dog2.teeth; // ['1', '2', '3', '4', '5']

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Dog(name) {
  this.name = name;
  this.bark = function() {};
}
 
Dog.prototype.jump = function() {};
Dog.prototype.species = 'Labrador';
Dog.prototype.teeth = ['1', '2', '3', '4'];
 
var dog1 = new Dog('tom'),
    dog2 = new Dog('jerry');
 
dog1.bark !== dog2.bark; // true
dog1.jump === dog2.jump; // true
 
dog1.teeth.push('5');
dog2.teeth; // ['1', '2', '3', '4', '5']

看到有注释的那三行应该可以明白“引用”和“新建”的区别了。

那么我们经常说到的“原型链”到底是什么呢?这个术语出现在继承当中,它用于表示对象实例中的属性和方法来自于何处(哪个父类)。好吧,这是笔者的解释。

JavaScript

- Object bark: Dog/this.bark() name: 'tom' - __proto__: Object jump: Dog.prototype.jump() species: 'Labrador' teeth: Array[4] constructor: Dog() __proto__: Object

1
2
3
4
5
6
7
8
9
- Object
  bark: Dog/this.bark()
  name: 'tom'
- __proto__: Object
    jump: Dog.prototype.jump()
    species: 'Labrador'
   teeth: Array[4]
   constructor: Dog()
   __proto__: Object  

上面的是 dog1 的原型链,不知道够不够直观地描述“链”这个概念。

  1. 其中,bark 和 name 是定义在 this 中的,所以最顶层可以看到它俩。
  2. 然后,每一个对象都会有一个 __proto__澳门新萄京官方网站, 属性(IE 11 ),它表示定义在原型上的属性和方法,所以 jump、species 和 teeth 自然就在这儿了。
  3. 最后就一直向上找 __proto__ 中的属性和方法。

  4. 继承的几种实现


原型是什么?原型我们可以参照C 里的类,同样的保存了对象的属性和方法。例如我们写一个简单的对象

1.每个构造函数都有prototype这个原型属性;

证明:console.dir(Person);

澳门新萄京官方网站 6

从这张图片可以看出,每个构造函数都有一个prototype属性(红色的方框标记的),这个属性是对象类型(因为prototype的值是键值对,键值对是对象的标志)这个对象里面有两个属性一个是constructor(绿色的边框标记的)一个是proto(黑色边框标记的);

如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。

4.1 通过 call 或者 apply

继承在编程中有两种说法,一个叫 inherit,另一个是 extend 。前者是严格意义上的继承,即存在父子关系,而后者仅仅是一个类扩展了另一个类的属性和方法。那么 call 和 apply 就属于后者的范畴。怎么说?

JavaScript

function Animal(gender) { this.gender = gender; } function Dog(name, gender) { Animal.call(this, gender); this.name = name; } var dog = new Dog('tom', 'male'); dog instanceof Animal; // false

1
2
3
4
5
6
7
8
9
10
11
12
function Animal(gender) {
  this.gender = gender;
}
 
function Dog(name, gender) {
  Animal.call(this, gender);
  this.name = name;
}
 
var dog = new Dog('tom', 'male');
 
dog instanceof Animal; // false

虽然在 dog 对象中有 gender 属性,但 dog 却不是 Animal 类型。甚至,这种方式只能“继承”父类在 this 上定义的属性和方法,并不能继承 Animal.prototype 中的属性和方法。

复制代码 代码如下:

2.constructor属性指向构造函数;

证明:console.log(Person.prototype.constructor===Person);
输出的结果为:true;这就证明了,prototype的constructor属性指向,构造函数;


4.2 通过 prototype 实现继承

要实现继承,必须包含“原型”的概念。下面是很常用的继承方式。

JavaScript

function Dog(name) { Animal.call(this); } Dog.prototype = new Animal(); // 先假设 Animal 函数没有参数 Dog.prototype.constructor = Dog; var dog = new Dog('tom'); dog instanceof Animal; // true

1
2
3
4
5
6
7
8
9
10
function Dog(name) {
  Animal.call(this);
}
 
Dog.prototype = new Animal(); // 先假设 Animal 函数没有参数
Dog.prototype.constructor = Dog;
 
var dog = new Dog('tom');
 
dog instanceof Animal; // true

继承的结果有两个:一、获得父类的属性和方法;二、正确通过 instanceof 的测试。

prototype 也是对象,它是创建实例时的装配机,这个在前面有提过。new Animal() 的值包含 Animal 实例所有的属性和方法,既然它赋给了 Dog 的 prototype,那么 Dog 的实例自然就获得了父类的所有属性和方法。

并且,通过这个例子可以知道,改变 Dog 的 prototype 属性可以改变 instanceof 的测试结果,也就是改变了父类。

然后,为什么要在 Dog 的构造函数中调用 Animal.call(this)?

因为 Animal 中可能在 this 上定义了方法和函数,如果没有这句话,那么所有的这一切都会给到 Dog 的 prototype 上,根据前面的知识我们知道,prototype 中的属性和方法在实例间是共享的。

我们希望将这些属性和方法依然保留在实例自身的空间,而不是共享,因此需要重写一份。

3天理解面向对象,JavaScript的原型继承详解。至于为什么要修改 constructor,只能说是为了正确的显示原型链吧,它并不会影响 instanceof 的判断。或者有其他更深的道理我并不知道……

function Animal(name) {
    this.name = name;
}
Animal.prototype.setName = function(name) {
    this.name = name;
}
var animal = new Animal("wangwang");

3.proto,实例对象里面也有一个proto属性,这个属性指向构造函数的原型;

证明:console.log(p1.proto===Person.prototype);
输出的结果为:true;这就说明了,实例对象的属性proto,指向构造函数的原型;

我们从内存图的角度来说明这个Person这个对象;

澳门新萄京官方网站 7

好好的理解一下这个内存图;

javascript实现继承的方式

4.3 利用空对象实现继承

上面的继承方式已经近乎完美了,除了两点:

一、Animal 有构造参数,并且使用了这些参数怎么办?
二、在 Dog.prototype 中多了一份定义在 Animal 实例中冗余的属性和方法。

JavaScript

function Animal(name) { name.doSomething(); } function Dog(name) { Animal.call(this, name); } Dog.prototype = new Animal(); // 由于没有传入name变量,在调用Animal的构造函数时,会出错 Dog.prototype.constructor = Dog;

1
2
3
4
5
6
7
8
9
10
function Animal(name) {
  name.doSomething();
}
 
function Dog(name) {
  Animal.call(this, name);
}
 
Dog.prototype = new Animal(); // 由于没有传入name变量,在调用Animal的构造函数时,会出错
Dog.prototype.constructor = Dog;

这个问题可以通过一个空对象来解决(改自 Douglas Crockford)。

JavaScript

function DummyAnimal() {} DummyAnimal.prototype = Animal.prototype; Dog.prototype = new DummyAnimal(); Dog.prototype.constructor = Dog;

1
2
3
4
5
function DummyAnimal() {}
DummyAnimal.prototype = Animal.prototype;
 
Dog.prototype = new DummyAnimal();
Dog.prototype.constructor = Dog;

他的原始方法是下面的 object:

JavaScript

function object(o) { function F() {} F.prototype = o; return new F(); } Dog.prototype = object(Animal.prototype); Dog.prototype.constructor = Dog;

1
2
3
4
5
6
7
8
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
 
Dog.prototype = object(Animal.prototype);
Dog.prototype.constructor = Dog;

我们可以看到,这就是一个对象Animal,该对象有个属性name,有个方法setName。要注意,一旦修改prototype,比如增加某个方法,则该对象所有实例将同享这个方法。例如

Tab栏案例;

用面向对象的方式编程;

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <style type="text/css">
        #tab div{
            width: 200px;
            height: 200px;
            background: red;
            font-size: 30px;
            display: none;
        }
    </style>
</head>
<body>
    <div id="tab">
        <input type="button" value="苹果"/>
        <input type="button" value="橘子"/>
        <input type="button" value="香蕉"/>
        <div>苹果</div>
        <div>橘子</div>
        <div>香蕉</div>
    </div>
    <script>
        function Tab (id) {
            this.odiv=document.getElementById("tab");
            this.adiv=this.odiv.getElementsByTagName('div');
            this.ainput=this.odiv.getElementsByTagName('input');
        }
        Tab.prototype.innit=function  () {
            this.adiv[0].style.display='block';
            this.ainput[0].style.backgroundColor='orange';
            for (var i=0;i<this.ainput.length;i  ) {
                this.ainput[i].index=i;
                var _this=this;
                this.ainput[i].onclick=function  () {
                    _this.change(this);
                }
            }
            this.autoplay();
        }
        Tab.prototype.change=function  (obj) {
            for (var j=0;j<this.adiv.length;j  ) {
                this.ainput[j].style.backgroundColor='';
                this.adiv[j].style.display='none';
            }
            obj.style.backgroundColor='orange';
            this.adiv[obj.index].style.display='block';
        }
        Tab.prototype.autoplay=function  () {
            this.nowIdex=0;
            var that=this;
            setInterval(function  () {
                if(that.nowIdex===that.adiv.length-1){
                    that.nowIdex=0;
                }else{
                    that.nowIdex  ;
                }
                that.change(that.ainput[that.nowIdex]);
            },1000);
        }
        var tab=new Tab('tab');
        tab.innit();
    </script>
</body>
</html>

C 和Java语言都使用new命令,生成实例。

4.4 利用 __proto__ 实现继承

现在就只剩下一个问题了,如何把冗余属性和方法去掉?

其实,从第 3 小节介绍原型的时候就提到了 __proto__ 属性,instanceof 运算符是通过它来判断是否属于某个类型的。

所以我们可以这么继承:

JavaScript

function Dog() { Animal.call(this); } Dog.prototype = { __proto__: Animal.prototype, constructor: Dog };

1
2
3
4
5
6
7
8
function Dog() {
  Animal.call(this);
}
 
Dog.prototype = {
  __proto__: Animal.prototype,
  constructor: Dog
};

如果不考虑兼容性的话,这应该是从 OO 的角度来看最贴切的继承方式了。

复制代码 代码如下:

初识原型链

C

4.5 拷贝继承

这个方式也只能称之为 extend 而不是 inherit,所以也没必要展开说。

像 Backbone.Model.extend、jQuery.extend 或者 _.extend 都是拷贝继承,可以稍微看一下它们是怎么实现的。(或者等我自己再好好研究之后过来把这部分补上吧)

function Animal(name) {
    this.name = name;
}
var animal = new Animal("wangwang");

原型链是什么?

ClassName*object=new ClassName(param);

5. 个人小结

当我们在讨论继承的实现方式时,给我的感觉就像孔乙己在炫耀“茴香豆”的“茴”有几种写法一样。继承是 JS 中占比很大的一块内容,所以很多库都有自己的实现方式,它们并没有使用我认为的“最贴切”的方法,为什么?JS 就是 JS,它生来就设计得非常灵活,所以我们为什么不利用这个特性,而非得将 OO 的做法强加于它呢?

通过继承,我们更多的是希望获得父类的属性和方法,至于是否要保证严格的父类/子类关系,很多时候并不在乎,而拷贝继承最能体现这一点。对于基于原型的继承,会在代码中看到各种用 function 定义的类型,而拷贝继承更通用,它只是将一个对象的属性和方法拷贝(扩展)到另一个对象而已,并不关心原型链是什么。

当然,在我鼓吹拷贝继承多么多么好时,基于原型的继承自然有它不可取代的理由。所以具体问题得具体分析,当具体的使用场景没定下来时,就不存在最好的方法。

个人见解,能帮助大家更加理解继承一点就最好,如果有什么不对的,请多多指教!

1 赞 4 收藏 评论

澳门新萄京官方网站 8

这时animal只有name属性。如果我们加上一句,

原型链是有实例对象的属性和方法组成,通过protot链接到一起;
function Person(name,age){
      this.name=name;
      this.age=age;
}
Person.prototype.showAge=function(){
      console.log(this.name 'hello word !');
 }
 var p=new Person("zhangsan",18);

从以上的知识可以知道:

1).每个构造函数都有一个prototype属性,这个属性是一个对象,这个属性里面有两个属性一个是constructor另一个是proto;
2).原型中的constructor指向构造函数;
3).实例对象中的proto指向构造函数的原型;
证明:
console.log(Person.prototype.constructor===Person);//true;
console.log(P.proto===Person.prototype);//true;

java

复制代码 代码如下:

既然构造函数的原型有两个属性,一个是constructot(指向构造函数),一个是proto,那么proto指向哪里?

我们先来打印一下,console.dir(Person.prototype.proto);

澳门新萄京官方网站 9

然后我们在来打印一下,console.dir(Object.prototype);

澳门新萄京官方网站 10

从图中可以知道,Person.prototype.proto===Object.prototype;
console.log(Person.prototype.proto===Object.prototype);//true;
从这里可以知道,prototype下面的constructor指向构造函数,prototype中的proto
指向,Object.prototype;
所以,就产生了一条链式结构;
p->Person.prototype->Object.prototype->null;
var arr=[];
arr->arr.prototype->Object.prototype->null;
var obj={};
obj->Object.prototype->null;

Foo foo=new Foo();

Animal.prototype.setName = function(name) {
    this.name = name;
}

为什么Object.prototype.proto指向空呢??

澳门新萄京官方网站 11

大家看一下,Object.prototype上面没有proto属性,所以指向null;
证明:
console.dir(p.proto.proto.proto);

澳门新萄京官方网站 12

那么了解这写有什么用呢??
function Animal(name){
this.name=name;
}
Animal.prototype.showAge=function(){
console.log(this.name 'hello word');
}
function Dog(color){
this.color=color;
}
Dog.prototype=new Animal('小白');
var dog=new Dog('黄色');
console.log(dog.color);
console.log(dog.name);
dog.showAge();
为什么构造函数Animal上面的方法,构造函数Dog可以访问的到,我们画一张图来表示一下;

澳门新萄京官方网站 13

那我们把下面的代码在更改一下:

  function Animal(name){
  this.name=name;
}
Animal.prototype.showAge=function(){
    console.log(this.name 'hello word');
} 
function Dog(color){
    this.color=color;
}
Dog.prototype=new Animal('小白');
Dog.prototype.showAge=function(){
       console.log(this.name ':hellow word');
}
var dog=new Dog('黄色');

我在Dog的prototype上面加了一个方法,那么dog的实例能不能访问的到?

dog.showAge();

我们看一下内存图的变化;
![]

澳门新萄京官方网站 14

dog.showAge()是可以访问到的,因为Dog.prototype被指向为new Animal(),所以dog的showAge方法加在了new Animal()上面,new dog的实例通过proto可以访问到new Animal()上面。但是这样的话,我们就会发现,Dog.prototype.construcot===Animal;这样是不符合我们规定的;

那要是这样改动,内存图会有什么变化;

function Animal(name){
  this.name=name;
}
Animal.prototype.showAge=function(){
    console.log(this.name 'hello word');
} 
function Dog(color){
    this.color=color;
}
Dog.prototype=new Animal('小白');
Dog.prototype.showAge=function(){
       console.log(this.name ':hellow word');
}
Dog.prototype.constructor=Dog;
var dog=new Dog('黄色');  

澳门新萄京官方网站 15

代码我们在改动一下:

function Animal(name){
  this.name=name;
}
Animal.prototype.showAge=function(){
    console.log(this.name 'hello word');
} 
function Dog(color){
    this.color=color;
}
Dog.prototype=new Animal('小白');
Dog.prototype.showAge=function(){
       console.log(this.name ':hellow word');
}
Dog.prototype.constructor=Dog;
Object.prototype.showFn=function () {
      console.log(this.name ':1122222222');
};
var dog=new Dog('黄色'); 
Dog.prototype.constructor=Dog;

内存图发生了什么变化?

澳门新萄京官方网站 16

;
我们在来看一下这个案例,内存图的变化;

        function Animal (name,age) {
                this.name=name;
                this.age=age;
        }
        Animal.prototype={
          constructor:Animal,
            showAge:function  () {
                 console.log(this.name 'hello Word');
           },
          showName: function (){
                 console.log(this.name "我今年" this.age "岁了");
           }
        };
            var animal=new Animal('小白',19);
            animal.showAge();
            animal.showName();

澳门新萄京官方网站 17

JavaScript引入了new命令,但由于它没有"类"的概念。考虑到C 和Java使用new命令时,都会调用"类"的构造函数(constructor)。于是,Brendan Eich在设计JavaScript时做了一个简化,new命令后面跟的不是类,而是构造函数。

这时animal也会有setName方法。

继承;

构造函数和prototype对象之间的关系

继承本复制——从空的对象开始我们知道,JS的基本类型中,有一种叫做object,而它的最基本实例就是空的对象,即直接调用new Object()生成的实例,或者是用字面量{ }来声明。空的对象是“干净的对象”,只有预定义的属性和方法,而其他所有对象都是继承自空对象,因此所有的对象都拥有这些预定义的 属性与方法。原型其实也是一个对象实例。原型的含义是指:如果构造器有一个原型对象A,则由该构造器创建的实例都必然复制自A。由于实例复制自对象A,所以实例必然继承了A的所有属性、方法和其他性质。那么,复制又是怎么实现的呢?方法一:构造复制每构造一个实例,都从原型中复制出一个实例来,新的实例与原型占用了相同的内存空间。这虽然使得obj1、obj2与它们的原型“完全一致”,但也非常不经济——内存空间的消耗会急速增加。如图:

继承分为原型继承和构造函数继承;

function DOG(name){

澳门新萄京官方网站 18

原型继承;
          function Person (name) {
                    this.name=name;
                    this.score=[20,30,40,50];
                }
                function Student (age) {
                    this.age=age;
                }
             Person.prototype.showName=function(){
                  console.log(this.name ':下班回家很晚');
              }
                Student.prototype=new Person('tom');
                Student.prototype.showAge=function  () {
                    console.log(this.name ":下班回来晚了");
                }
                var stu=new Student(29);
                var stu1=new Student(35);
                stu.score.push(100);
                console.log(stu.score);
                console.log(stu1.score);
                stu.showAge();
                stu1.showAge();
                console.log(stu.name);
                console.log(stu1.name);

澳门新萄京官方网站 19

澳门新萄京官方网站 20

this.name=name;

方法二:写时复制这种策略来自于一致欺骗系统的技术:写时复制。这种欺骗的典型示例就是操作系统中的动态链接库(DDL),它的内存区总是写时复制的。如图:

原型继承的缺点:

1.原型继承中的要传的参数已经无法更改(我想让stu1获取name的属性为tom,stu2获取name的属性为Jerry,但是这样无法做到);
2.所继承的函数中的引用类型的数据被所有的实例,所共享;

this.species='犬科';

澳门新萄京官方网站 21

借用构造函数继承;
          function Person (name) {
                    this.name=name;
                    this.score=[20,30,40,50];
                }
            Person.prototype.showAge=function  () {
                console.log(this.name ':我已经完成le');
            }
            function Student (name,age) {
                    Person.call(this,name);
                    this.age=age;
                }
            var stu=new Student("Jerry",29);
                stu.score.push(100);
                console.log(stu.score);
                var stu1=new Student("Tom",35);
                console.log(stu1.score);
                console.log(stu.name);
                console.log(stu.age);
                console.log(stu1.name);
                console.log(stu1.age);
                stu.showAge();
                stu1.showAge();

澳门新萄京官方网站 22

澳门新萄京官方网站 23

}

我们只要在系统中指明obj1和obj2等同于它们的原型,这样在读取的时候,只需要顺着指示去读原型即可。当需要写对象(例如obj2)的属性时,我们就复制一个原型的映像出来,并使以后的操作指向该映像即可。如图:

借用构造函数继承的缺点:

1.无法继承构造函数原型上面的方法;

var dogA=new DOG('大毛');

澳门新萄京官方网站 24

最佳的继承方法:组合继承;
            function Person (name) {
                    this.name=name;
                    this.score=[20,30,40,50];
                }
            Person.prototype.showAge=function  () {
                console.log(this.name ':我已经完成le');
                }
            function Student (name,age) {
                    Person.call(this,name);
                    this.age=age;
                }
            Student.prototype=new Person();
            var stu=new Student("Jerry",29);
          stu.score.push(100);
            var stu1=new Student("Tom",35);

澳门新萄京官方网站 25

var dogB=new DOG('二毛');

这种方式的优点是我们在创建实例和读属性的时候不需要大量内存开销,只在第一次写的时候会用一些代码来分配内存,并带来一些代码和内存上的开销。但此后就不再有这种开销了,因为访问映像和访问原型的效率是一致的。不过,对于经常进行写操作的系统来说,这种方法并不比上一种方法经济。方法三:读遍历这种方法把复制的粒度从原型变成了成员。这种方法的特点是:仅当写某个实例的成员,将成员的信息复制到实例映像中。当写对象属性时,例如(obj2.value=10)时,会产生一个名为value的属性值,放在obj2对象的成员列表中。看图:澳门新萄京官方网站 26

对象全家福

澳门新萄京官方网站 27

1.每个函数都有一个prototype和proto;
2.如果这个函数是构造函数,那么主要用这个Prototype这个属性,这个属性是个对象,
默认有两个属性一个是constructor和proto;constructor指向这个构造函数,proto指向Object.prototype;
3.实力对象中的proto指向构造函数的原型;
4.如果这个函数是普通函数,那么proto指向Function.prototype,Function的 proto指向Object.prototype;
5.Function中的proto指向Function.prototype,也就是说,Function是Function的实例;
6.所有的函数都是Function的实例。

dogA.species='猫科';

可以发现,obj2仍然是一个指向原型的引用,在操作过程中也没有与原型相同大小的对象实例创建出来。这样,写操作并不导致大量的内存分配,因此内存的使用上就显得经济了。不同的是,obj2(以及所有的对象实例)需要维护一张成员列表。这个成员列表遵循两条规则:保证在读取时首先被访问到如果在对象中没有指定属性,则尝试遍历对象的整个原型链,直到原型为空或或找到该属性。原型链后面会讲。显然,三种方法中,读遍历是性能最优的。所以,JavaScript的原型继承是读遍历的。constructor熟悉C 的人看完最上面的对象的代码,肯定会疑惑。没有class关键字还好理解,毕竟有function关键字,关键字不一样而已。但是,构造函数呢?实际上,JavaScript也是有类似的构造函数的,只不过叫做构造器。在使用new运算符的时候,其实已经调用了构造器,并将this绑定为对象。例如,我们用以下的代码

对象的本质

无续的键值对的组合;

alert(dogB.species);// 显示"犬科

复制代码 代码如下:

用构造函数生成的每一个实例对象都有自己的属性和方法的副本,这不仅无法做到数据共享,也是极大的资源浪费。

var animal = Animal("wangwang");

考虑到这一点,Brendan Eich为构造函数设置一个prototype属性。这个属性包含一个对象(以下简称"prototype对象")

animal将是undefined。有人会说,没有返回值当然是undefined。那如果将Animal的对象定义改一下:

prototype对象和实例对象的关系

复制代码 代码如下:

function DOG(name){

function Animal(name) {
    this.name = name;
    return this;
}

this.name=name;

猜猜现在animal是什么?
此时的animal变成window了,不同之处在于扩展了window,使得window有了name属性。这是因为this在没有指定的情况下,默认指向window,也即最顶层变量。只有调用new关键字,才能正确调用构造器。那么,如何避免用的人漏掉new关键字呢?我们可以做点小修改:

}

复制代码 代码如下:

DOG.prototype={

function Animal(name) {
    if(!(this instanceof Animal)) {
        return new Animal(name);
    }
    this.name = name;
}

species:'犬科'

这样就万无一失了。构造器还有一个用处,标明实例是属于哪个对象的。我们可以用instanceof来判断,但instanceof在继承的时候对祖先对象跟真正对象都会返回true,所以不太适合。constructor在new调用时,默认指向当前对象。

};

复制代码 代码如下:

var dogA=new DOG('大毛');

console.log(Animal.prototype.constructor === Animal); // true

var dogB=new DOG('二毛');

我们可以换种思维:prototype在函数初始时根本是无值的,实现上可能是下面的逻辑

alert(dogA.species);// 犬科

// 设定__proto__是函数内置的成员,get_prototyoe()是它的方法

alert(dogB.species);// 犬科

复制代码 代码如下:

实例对象的属性和方法继承prototype对象

var __proto__ = null;
function get_prototype() {
    if(!__proto__) {
        __proto__ = new Object();
        __proto__.constructor = this;
    }
    return __proto__;
}

实例对象的_proto_属性的值就是它所对应的原型对象

这样的好处是避免了每声明一个函数都创建一个对象实例,节省了开销。constructor是可以修改的,后面会讲到。基于原型的继承继承是什么相信大家都差不多知道,就不秀智商下限了。

当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。

JS的继承有好几种,这里讲两种

澳门新萄京官方网站 28

  1. 方法一这种方法最常用,安全性也比较好。我们先定义两个对象


若想访问一个对象的原型,应该使用什么方法?

1、使用_proto_属性

每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。

对象__proto__属性的值就是它所对应的原型对象;

function DOG(name){

this.name=name;

}

DOG.prototype={

species:'犬科'

};

var dogA = new DOG('大毛');     

var dog B = new DOG('二毛');

dogA.__proto__==DOG.prototype;//true

dogB.__proto__==DOG.prototype;//true

对象的__proto__指向自己构造函数的prototype。obj.__proto__.__proto__...的原型链由此产生,包括我们的操作符instanceof正是通过探测obj.__proto__.__proto__... === Constructor.prototype来验证obj是否是Constructor的实例。

2、使用Object.getPrototypeOf()

Object.getPrototypeOf(dogA)==DOG.prototype;//true

Object.getPrototypeOf(dogB)==DOG.prototype;//true

使用__proto__是有争议的,而且是不鼓励的。 它从来没有被包括在EcmaScript语言规范中,但是现代浏览器实现了它, 无论如何。__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。它已被不推荐使用, 赞成使用Object.getPrototypeOf。


我们知道JS是单继承的,Object.prototype是原型链的顶端,所有对象从它继承了包括toString等等方法和属性。

Object本身是构造函数,继承了Function.prototype;Function也是对象,继承了Object.prototype。这里就有一个_鸡和蛋_的问题:

Object instanceof Function// true

Function instanceof Object// true

Function本身就是函数,Function.__proto__是标准的内置对象Function.prototype。

Function.prototype.__proto__是标准的内置对象Object.prototype。

澳门新萄京官方网站 29


参考文献:

Javascript继承机制的设计思想

从__proto__和prototype来深入理解JS对象和原型链

复制代码 代码如下:

function Animal(name) {
    this.name = name;
}
function Dog(age) {
    this.age = age;
}
var dog = new Dog(2);

要构造继承很简单,将子对象的原型指向父对象的实例(注意是实例,不是对象)

复制代码 代码如下:

Dog.prototype = new Animal("wangwang");

这时,dog就将有两个属性,name和age。而如果对dog使用instanceof操作符

复制代码 代码如下:

console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // false

这样就实现了继承,但是有个小问题

复制代码 代码如下:

console.log(Dog.prototype.constructor === Animal); // true
console.log(Dog.prototype.constructor === Dog); // false

可以看到构造器指向的对象更改了,这样就不符合我们的目的了,我们无法判断我们new出来的实例属于谁。因此,我们可以加一句话:

复制代码 代码如下:

Dog.prototype.constructor = Dog;

再来看一下:

复制代码 代码如下:

console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true

done。这种方法是属于原型链的维护中的一环,下文将详细阐述。2. 方法二这种方法有它的好处,也有它的弊端,但弊大于利。先看代码

复制代码 代码如下:

<pre name="code" class="javascript">function Animal(name) {
    this.name = name;
}
Animal.prototype.setName = function(name) {
    this.name = name;
}
function Dog(age) {
    this.age = age;
}
Dog.prototype = Animal.prototype;

这样就实现了prototype的拷贝。

这种方法的好处就是不需要实例化对象(和方法一相比),节省了资源。弊端也是明显,除了和上文一样的问题,即constructor指向了父对象,还只能复制父对象用prototype声明的属性和方法。也即是说,上述代码中,Animal对象的name属性得不到复制,但能复制setName方法。最最致命的是,对子对象的prototype的任何修改,都会影响父对象的prototype,也就是两个对象声明出来的实例都会受到影响。所以,不推荐这种方法。

原型链

写过继承的人都知道,继承可以多层继承。而在JS中,这种就构成了原型链。上文也多次提到了原型链,那么,原型链是什么?一个实例,至少应该拥有指向原型的proto属性,这是JavaScript中的对象系统的基础。不过这个属性是不可见的,我们称之为“内部原型链”,以便和构造器的prototype所组成的“构造器原型链”(亦即我们通常所说的“原型链”)区分开。我们先按上述代码构造一个简单的继承关系:

复制代码 代码如下:

function Animal(name) {
    this.name = name;
}
function Dog(age) {
    this.age = age;
}
var animal = new Animal("wangwang");
Dog.prototype = animal;
var dog = new Dog(2);

提醒一下,前文说过,所有对象都是继承空的对象的。所以,我们就构造了一个原型链:

澳门新萄京官方网站 30

我们可以看到,子对象的prototype指向父对象的实例,构成了构造器原型链。子实例的内部proto对象也是指向父对象的实例,构成了内部原型链。当我们需要寻找某个属性的时候,代码类似于

复制代码 代码如下:

function getAttrFromObj(attr, obj) {
    if(typeof(obj) === "object") {
        var proto = obj;
        while(proto) {
            if(proto.hasOwnProperty(attr)) {
                return proto[attr];
            }
            proto = proto.__proto__;
        }
    }
    return undefined;
}

在这个例子中,我们如果在dog中查找name属性,它将在dog中的成员列表中寻找,当然,会找不到,因为现在dog的成员列表只有age这一项。接着它会顺着原型链,即.proto指向的实例继续寻找,即animal中,找到了name属性,并将之返回。假如寻找的是一个不存在的属性,在animal中寻找不到时,它会继续顺着.proto寻找,找到了空的对象,找不到之后继续顺着.proto寻找,而空的对象的.proto指向null,寻找退出。

原型链的维护我们在刚才讲原型继承的时候提出了一个问题,使用方法一构造继承时,子对象实例的constructor指向的是父对象。这样的好处是我们可以通过constructor属性来访问原型链,坏处也是显而易见的。一个对象,它产生的实例应该指向它本身,也即是

复制代码 代码如下:

(new obj()).prototype.constructor === obj;

然后,当我们重写了原型属性之后,子对象产生的实例的constructor不是指向本身!这样就和构造器的初衷背道而驰了。我们在上面提到了一个解决方案:

复制代码 代码如下:

Dog.prototype = new Animal("wangwang");
Dog.prototype.constructor = Dog;

看起来没有什么问题了。但实际上,这又带来了一个新的问题,因为我们会发现,我们没法回溯原型链了,因为我们没法寻找到父对象,而内部原型链的.proto属性是无法访问的。于是,SpiderMonkey提供了一个改良方案:在任何创建的对象上添加了一个名为__proto__的属性,该属性总是指向构造器所用的原型。这样,对任何constructor的修改,都不会影响__proto__的值,就方便维护constructor了。

但是,这样又两个问题:

__proto__是可以重写的,这意味着使用它时仍然有风险

__proto__是spiderMonkey的特殊处理,在别的引擎(例如JScript)中是无法使用的。

我们还有一种办法,那就是保持原型的构造器属性,而在子类构造器函数内初始化实例的构造器属性。

代码如下:改写子对象

复制代码 代码如下:

function Dog(age) {
    this.constructor = arguments.callee;
    this.age = age;
}
Dog.prototype = new Animal("wangwang");

这样,所有子对象的实例的constructor都正确的指向该对象,而原型的constructor则指向父对象。虽然这种方法的效率比较低,因为每次构造实例都要重写constructor属性,但毫无疑问这种方法能有效解决之前的矛盾。ES5考虑到了这种情况,彻底的解决了这个问题:可以在任意时候使用Object.getPrototypeOf() 来获得一个对象的真实原型,而无须访问构造器或维护外部的原型链。因此,像上一节所说的寻找对象属性,我们可以如下改写:

复制代码 代码如下:

function getAttrFromObj(attr, obj) {
    if(typeof(obj) === "object") {
        do {
            var proto = Object.getPrototypeOf(dog);
            if(proto[attr]) {
                return proto[attr];
            }
        }
        while(proto);
    }
    return undefined;
}

当然,这种方法只能在支持ES5的浏览器中使用。为了向后兼容,我们还是需要考虑上一种方法的。更合适的方法是将这两种方法整合封装起来,这个相信读者们都非常擅长,这里就不献丑了。

您可能感兴趣的文章:

  • JavaScript继承与多继承实例分析
  • JavaScript实现多重继承的方法分析
  • 深入浅析javascript继承体系
  • JS继承与闭包及JS实现继承的三种方式
  • js中继承的几种用法总结(apply,call,prototype)
  • JavaScript是如何实现继承的(六种方式)
  • 深入了解javascript中的prototype与继承
  • Javascript基于对象三大特性(封装性、继承性、多态性)
  • javascript的函数、创建对象、封装、属性和方法、继承
  • Javascript 继承机制的实现
  • JavaScript继承定义与用法实践分析

本文由澳门新萄京官方网站发布于澳门新萄京赌场网址,转载请注明出处:3天理解面向对象,JavaScript的原型继承详解

关键词: