《JavaScript 闯关记》之原型及原型链

原型链是一种机制,指的是 JavaScript 每一个对象都有一个内置的 __proto__ 属性指向建立它的组织函数的 prototype(原型)属性。原型链的作用是为了完成对象的继承,要邃晓原型链,须要先从函数对象constructornewprototype__proto__ 这五个观点入手。

函数对象

前面讲过,在 JavaScript 里,函数即对象,顺序可以随便操控它们。比方,可以把函数赋值给变量,或许作为参数通报给其他函数,也可以给它们设置属性,以至挪用它们的要领。下面示例代码对「平常对象」和「函数对象」进行了辨别。

平常对象:

var o1 = {};
var o2 = new Object();

函数对象:

function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

简朴的说,通常运用 function 关键字或 Function 组织函数建立的对象都是函数对象。而且,只需函数对象才具有 prototype (原型)属性。

constructor 组织函数

函数另有一种用法,就是把它作为组织函数运用。像 ObjectArray 如许的原生组织函数,在运转时会自动涌如今实行环境中。别的,也可以建立自定义的组织函数,从而自定义对象范例的属性和要领。以下代码所示:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);
    };
}

var person1 = new Person("Stone", 28, "Software Engineer");
var person2 = new Person("Sophie", 29, "English Teacher");

在这个例子中,我们建立了一个自定义组织函数 Person(),并经由历程该组织函数建立了两个平常对象 person1person2,这两个平常对象均包含3个属性和1个要领。

你应当注重到函数名 Person 运用的是大写字母 P。依据通例,组织函数一向都应当以一个大写字母开首,而非组织函数则应当以一个小写字母开首。这个做法自创自其他面向对象言语,主要是为了区分于 JavaScript 中的其他函数;因为组织函数自身也是函数,只不过可以用来建立对象罢了。

new 操作符

要建立 Person 的新实例,必需运用 new 操作符。以这类体式格局挪用组织函数现实上会阅历以下4个步骤:

  1. 建立一个新对象;

  2. 将组织函数的作用域赋给新对象(因而 this 就指向了这个新对象);

  3. 实行组织函数中的代码(为这个新对象增加属性);

  4. 返回新对象。

将组织函数看成函数

组织函数与其他函数的唯一区分,就在于挪用它们的体式格局差别。不过,组织函数毕竟也是函数,不存在定义组织函数的特别语法。任何函数,只需经由历程 new 操作符来挪用,那它就可以作为组织函数;而任何函数,如果不经由历程 new 操作符来挪用,那它跟平常函数也不会有什么两样。比方,前面例子中定义的 Person() 函数可以经由历程以下任何一种体式格局来挪用。

// 看成组织函数运用
var person = new Person("Stone", 28, "Software Engineer");
person.sayName(); // "Stone"

// 作为平常函数挪用
Person("Sophie", 29, "English Teacher"); // 增加到 window
window.sayName(); // "Sophie"

// 在另一个对象的作用域中挪用
var o = new Object();
Person.call(o, "Tommy", 3, "Baby");
o.sayName(); // "Tommy"

这个例子中的前两行代码展现了组织函数的典范用法,即运用 new 操作符来建立一个新对象。接下来的两行代码展现了不运用 new 操作符挪用 Person() 会涌现什么效果,属性和要领都被增加给 window 对象了。当在全局作用域中挪用一个函数时,this 对象老是指向 Global 对象(在浏览器中就是 window 对象)。因而,在挪用完函数今后,可以经由历程 window 对象来挪用 sayName() 要领,而且还返回了 "Sophie" 。末了,也可以运用 call()(或许 apply())在某个特别对象的作用域中挪用 Person() 函数。这里是在对象 o 的作用域中挪用的,因而挪用后 o 就具有了一切属性和 sayName() 要领。

组织函数的题目

组织函数情势虽然好用,但也并不是没有瑕玷。运用组织函数的主要题目,就是每一个要领都要在每一个实例上从新建立一遍。在前面的例子中,person1person2 都有一个名为 sayName() 的要领,但那两个要领不是统一个 Function 的实例。因为 JavaScript 中的函数是对象,因而每定义一个函数,也就是实例化了一个对象。从逻辑角度讲,此时的组织函数也可以如许定义。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("console.log(this.name)"); // 与声明函数在逻辑上是等价的
}

从这个角度上来看组织函数,更轻易邃晓每一个 Person 实例都包含一个差别的 Function 实例(sayName() 要领)。说得邃晓些,以这类体式格局建立函数,虽然建立 Function 新实例的机制依旧是雷同的,然则差别实例上的同名函数是不相等的,以下代码可以证明这一点。

console.log(person1.sayName == person2.sayName);  // false

但是,建立两个完成一样使命的 Function 实例确切没有必要;何况有 this 对象在,基础不用在实行代码前就把函数绑定到特定对象上面。因而,大可像下面如许,经由历程把函数定义转移到组织函数外部来处理这个题目。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName(){
    console.log(this.name);
}

var person1 = new Person("Stone", 28, "Software Engineer");
var person2 = new Person("Sophie", 29, "English Teacher");

在这个例子中,我们把 sayName() 函数的定义转移到了组织函数外部。而在组织函数内部,我们将 sayName 属性设置成即是全局的 sayName 函数。如许一来,因为 sayName 包含的是一个指向函数的指针,因而 person1person2 对象就同享了在全局作用域中定义的统一个 sayName() 函数。如许做确切处理了两个函数做统一件事的题目,但是新题目又来了,在全局作用域中定义的函数现实上只能被某个对象挪用,这让全局作用域有点有名无实。而更让人没法接收的是,如果对象须要定义许多要领,那末就要定义许多个全局函数,因而我们这个自定义的援用范例就涓滴没有封装性可言了。幸亏,这些题目可以经由历程运用原型来处理。

prototype 原型

我们建立的每一个函数都有一个 prototype(原型)属性。运用原型的优点是可以让一切对象实例同享它所包含的属性和要领。换句话说,没必要在组织函数中定义对象实例的信息,而是可以将这些信息直接增加到原型中,以下面的例子所示。

function Person(){}

Person.prototype.name = "Stone";
Person.prototype.age = 28;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.sayName();   // "Stone"

var person2 = new Person();
person2.sayName();   // "Stone"

console.log(person1.sayName == person2.sayName);  // true

在此,我们将 sayName() 要领和一切属性直接增加到了 Personprototype 属性中,组织函数变成了空函数。纵然云云,也依旧可以经由历程挪用组织函数来建立新对象,而且新对象还会具有雷同的属性和要领。但与前面的例子差别的是,新对象的这些属性和要领是由一切实例同享的。换句话说,person1person2 接见的都是统一组属性和统一个 sayName() 函数。

邃晓原型对象

在默许状况下,一切原型对象都邑自动获得一个 constructor(组织函数)属性,这个属性包含一个指向 prototype 属性地点函数的指针。就拿前面的例子来讲,Person.prototype.constructor 指向 Person。而经由历程这个组织函数,我们还可继承为原型对象增加其他属性和要领。

虽然可以经由历程对象实例接见保留在原型中的值,但却不能经由历程对象实例重写原型中的值。如果我们在实例中增加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中建立该属性,该属性将会屏障原型中的谁人属性。来看下面的例子。

function Person(){}

Person.prototype.name = "Stone";
Person.prototype.age = 28;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Sophie";
console.log(person1.name);     // "Sophie",来自实例
console.log(person2.name);     // "Stone",来自原型

在这个例子中,person1name 被一个新值给屏障了。但不管接见 person1.name 还是接见 person2.name 都可以平常地返回值,即离别是 "Sophie"(来自对象实例)和 "Stone"(来自原型)。当接见 person1.name 时,须要读取它的值,因而就会在这个实例上搜刮一个名为 name 的属性。这个属性确切存在,因而就返回它的值而没必要再搜刮原型了。当接见 person2. name 时,并没有在实例上发现该属性,因而就会继承搜刮原型,效果在那里找到了 name 属性。

当为对象实例增加一个属性时,这个属性就会屏障原型中保留的同名属性;换句话说,增加这个属性只会阻挠我们接见原型中的谁人属性,但不会修正谁人属性。纵然将这个属性设置为 null ,也只会在实例中设置这个属性,而不会恢复其指向原型的衔接。不过,运用 delete 操作符则可以完整删除实例属性,从而让我们可以从新接见原型中的属性,以下所示。

function Person(){}

Person.prototype.name = "Stone";
Person.prototype.age = 28;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Sophie";
console.log(person1.name);     // "Sophie",来自实例
console.log(person2.name);     // "Stone",来自原型

delete person1.name;
console.log(person1.name);     // "Stone",来自原型

在这个修正后的例子中,我们运用 delete 操作符删除了 person1.name,之前它保留的 "Sophie" 值屏障了同名的原型属性。把它删除今后,就恢复了对原型中 name 属性的衔接。因而,接下来再挪用 person1.name 时,返回的就是原型中 name 属性的值了。

更简朴的原型语法

前面例子中每增加一个属性和要领就要敲一遍 Person.prototype。为削减没必要要的输入,也为了从视觉上更好地封装原型的功用,更罕见的做法是用一个包含一切属性和要领的对象字面量来重写悉数原型对象,以下面的例子所示。

function Person(){}

Person.prototype = {
    name : "Stone",
    age : 28,
    job: "Software Engineer",
    sayName : function () {
        console.log(this.name);
    }
};

在上面的代码中,我们将 Person.prototype 设置为即是一个以对象字面量情势建立的新对象。终究效果雷同,但有一个破例:constructor 属性不再指向 Person 了。前面曾引见过,每建立一个函数,就会同时建立它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在这里运用的语法,实质上完整重写了默许的 prototype 对象,因而 constructor 属性也就变成了新对象的 constructor 属性(指向 Object 组织函数),不再指向 Person 函数。此时,只管 instanceof 操作符还能返回准确的效果,但经由历程 constructor 已没法肯定对象的范例了,以下所示。

var friend = new Person();

console.log(friend instanceof Object);        // true
console.log(friend instanceof Person);        // true
console.log(friend.constructor === Person);    // false
console.log(friend.constructor === Object);    // true

在此,用 instanceof 操作符测试 ObjectPerson 依旧返回 true,但 constructor 属性则即是 Object 而不即是 Person 了。如果 constructor 的值真的很主要,可以像下面如许特地将它设置回恰当的值。

function Person(){}

Person.prototype = {
    constructor : Person,
    name : "Stone",
    age : 28,
    job: "Software Engineer",
    sayName : function () {
        console.log(this.name);
    }
};

以上代码特地包含了一个 constructor 属性,并将它的值设置为 Person ,从而确保了经由历程该属性可以接见到恰当的值。

注重,以这类体式格局重设 constructor 属性会致使它的 [[Enumerable]] 特征被设置为 true。默许状况下,原生的 constructor 属性是不可枚举的,因而如果你运用兼容 ECMAScript 5 的 JavaScript 引擎,可以试一试 Object.defineProperty()

function Person(){}

Person.prototype = {
    name : "Stone",
    age : 28,
    job : "Software Engineer",
    sayName : function () {
        console.log(this.name);
    }
}; 

// 重设组织函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型的动态性

因为在原型中查找值的历程是一次搜刮,因而我们对原型对象所做的任何修正都可以马上从实例上反应出来,纵然是先建立了实例后修正原型也还是云云。请看下面的例子。

var friend = new Person();

Person.prototype.sayHi = function(){
    console.log("hi");
};

friend.sayHi();   // "hi"(没有题目!)

以上代码先建立了 Person 的一个实例,并将其保留在 friend 中。然后,下一条语句在 Person.prototype 中增加了一个要领 sayHi()。纵然 person 实例是在增加新要领之前建立的,但它依旧可以接见这个新要领。其缘由可以归结为实例与原型之间的松懈衔接关联。当我们挪用 friend.sayHi() 时,起首会在实例中搜刮名为 sayHi 的属性,在没找到的状况下,会继承搜刮原型。因为实例与原型之间的衔接只不过是一个指针,而非一个副本,因而就可以在原型中找到新的 sayHi 属性并返回保留在那里的函数。

只管可以随时为原型增加属性和要领,而且修正可以马上在一切对象实例中反应出来,但如果是重写悉数原型对象,那末状况就不一样了。我们晓得,挪用组织函数时会为实例增加一个指向最初原型的 [[Prototype]] 指针,而把原型修正为别的一个对象就即是切断了组织函数与最初原型之间的联络。请记着:实例中的指针仅指向原型,而不指向组织函数。看下面的例子。

function Person(){}

var friend = new Person();

Person.prototype = {
    constructor: Person,
    name : "Stone",
    age : 28,
    job : "Software Engineer",
    sayName : function () {
        console.log(this.name);
    }
};

friend.sayName();   // Uncaught TypeError: friend.sayName is not a function

在这个例子中,我们先建立了 Person 的一个实例,然后又重写了其原型对象。然后在挪用 friend.sayName() 时发生了毛病,因为 friend 指向的是重写前的原型对象,个中并不包含以该名字定名的属性。

原生对象的原型

原型的主要性不仅体如今建立自定义范例方面,就连一切原生的援用范例,都是采纳这类情势建立的。一切原生援用范例(ObjectArrayString,等等)都在其组织函数的原型上定义了要领。比方,在 Array.prototype 中可以找到 sort() 要领,而在 String.prototype 中可以找到 substring() 要领,以下所示。

console.log(typeof Array.prototype.sort);       // "function"
console.log(typeof String.prototype.substring); // "function"

经由历程原生对象的原型,不仅可以获得一切默许要领的援用,而且也可以定义新要领。可以像修正自定义对象的原型一样修正原生对象的原型,因而可以随时增加要领。下面的代码就给基础包装范例 String 增加了一个名为 startsWith() 的要领。

String.prototype.startsWith = function (text) {
    return this.indexOf(text) === 0;
};

var msg = "Hello world!";
console.log(msg.startsWith("Hello"));   // true

这里新定义的 startsWith() 要领会在传入的文本位于一个字符串开始时返回 true。既然要领被增加给了 String.prototype ,那末当前环境中的一切字符串就都可以挪用它。因为 msg 是字符串,而且背景会挪用 String 基础包装函数建立这个字符串,因而经由历程 msg 就可以挪用 startsWith() 要领。

只管可以如许做,但我们不推荐在产品化的顺序中修正原生对象的原型。如果因某个完成中缺乏某个要领,就在原生对象的原型中增加这个要领,那末当在另一个支撑该要领的完成中运转代码时,就可以够会致使定名争执。而且,如许做也能够会心外埠重写原生要领。

原型对象的题目

原型情势也不是没有瑕玷。起首,它省略了为组织函数通报初始化参数这一环节,效果一切实例在默许状况下都将获得雷同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大题目。原型情势的最大题目是由其同享的本性所致使的。

原型中一切属性是被许多实例同享的,这类同享关于函数异常适宜。关于那些包含基础值的属性倒也说得过去,毕竟(如前面的例子所示),经由历程在实例上增加一个同名属性,可以隐蔽原型中的对应属性。但是,关于包含援用范例值的属性来讲,题目就比较突出了。来看下面的例子。

function Person(){}

Person.prototype = {
    constructor: Person,
    name : "Stone",
    age : 28,
    job : "Software Engineer",
    friends : ["ZhangSan", "LiSi"],
    sayName : function () {
        console.log(this.name);
    }
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push("WangWu");

console.log(person1.friends);    // "ZhangSan,LiSi,WangWu"
console.log(person2.friends);    // "ZhangSan,LiSi,WangWu"
console.log(person1.friends === person2.friends);  // true

在此,Person.prototype 对象有一个名为 friends 的属性,该属性包含一个字符串数组。然后,建立了 Person 的两个实例。接着,修正了 person1.friends 援用的数组,向数组中增加了一个字符串。因为 friends 数组存在于 Person.prototype 而非 person1 中,所以方才提到的修正也会经由历程 person2.friends(与 person1.friends 指向统一个数组)反应出来。如果我们的初志就是像如许在一切实例中同享一个数组,那末对这个效果我没有话可说。但是,实例平常都是要有属于本身的悉数属性的。

组织函数和原型连系

所以,组织函数用于定义实例属性,而原型用于定义要领和同享的属性。效果,每一个实例都邑有本身的一份实例属性的副本,但同时又同享着对要领的援用,最大限制地节省了内存。下面的代码重写了前面的例子。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["ZhangSan", "LiSi"];
}

Person.prototype = {
    constructor : Person,
    sayName : function(){
        console.log(this.name);
    }
}

var person1 = new Person("Stone", 28, "Software Engineer");
var person2 = new Person("Sophie", 29, "English Teacher");

person1.friends.push("WangWu");
console.log(person1.friends);    // "ZhangSan,LiSi,WangWu"
console.log(person2.friends);    // "ZhangSan,LiSi"
console.log(person1.friends === person2.friends);    // false
console.log(person1.sayName === person2.sayName);    // true

在这个例子中,实例属性都是在组织函数中定义的,而由一切实例同享的属性 constructor 和要领 sayName() 则是在原型中定义的。而修正了 person1.friends(向个中增加一个新字符串),并不会影响到 person2.friends,因为它们离别援用了差别的数组。

这类组织函数与原型混成的情势,是如今在 JavaScript 中运用最普遍、认同度最高的一种建立自定义范例的要领。可以说,这是用来定义援用范例的一种默许情势。

__proto__

为何在组织函数的 prototype 中定义了属性和要领,它的实例中就可以接见呢?

那是因为当挪用组织函数建立一个新实例后,该实例的内部将包含一个指针 __proto__,指向组织函数的原型。Firefox、Safari 和 Chrome 的每一个对象上都有这个属性 ,而在其他浏览器中是完整不可见的(为了确保浏览器兼容性题目,不要直接运用 __proto__ 属性,此处只为诠释原型链而演示)。让我们来看下面代码和图片:

《《JavaScript 闯关记》之原型及原型链》

图中展现了 Person 组织函数、Person 的原型属性以及 Person 现有的两个实例之间的关联。在此,Person.prototype.constructor 指回了 PersonPerson.prototype 中除了包含 constructor 属性以外,还包含厥后增加的其他属性。别的,要分外注重的是,虽然这两个实例都不包含属性和要领,但我们却可以挪用 person1.sayName()。这是因为内部指针 __proto__ 指向 Person.prototype,而在 Person.prototype 中能找到 sayName() 要领。

我们来证明一下,__proto__ 是否是真的指向 Person.prototype 的?以下代码所示:

function Person(){}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

既然,__proto__ 确切是指向 Person.prototype,那末运用 new 操作符建立对象的历程可以演变为,为实例对象的 __proto__ 赋值的历程。以下代码所示:

function Person(){}

// var person = new Person(); 
// 上一行代码等同于以下历程 ==> 
var person = {};
person.__proto__ = Person.prototype;
Person.call(person);

这个例子中,我先建立了一个空对象 person,然后把 person.__proto__ 指向了 Person 的原型对象,便继承了 Person 原型对象中的一切属性和要领,末了又以 person 为作用域实行了 Person 函数,person 便就具有了 Person 的一切属性和要领。这个历程和 var person = new Person(); 完整一样。

简朴来讲,当我们接见一个对象的属性时,如果这个属性不存在,那末就会去 __proto__ 里找,这个 __proto__ 又会有本身的 __proto__,因而就如许一向找下去,直到找到为止。在找不到的状况下,搜刮历程老是要一环一环地前行到原型链末尾才会停下来。

原型链

JavaScript 中形貌了原型链的观点,并将原型链作为完成继承的主要要领。其基础思想是应用原型让一个援用范例继承另一个援用范例的属性和要领。简朴回忆一下组织函数、原型和实例的关联:每一个组织函数都有一个原型对象,原型对象都包含一个指向组织函数的指针,而实例都包含一个指向原型对象的内部指针。以下图所示:(图源:segmentfault.com,作者:manxisuo

《《JavaScript 闯关记》之原型及原型链》

那末,如果我们让原型对象即是另一个范例的实例,效果会怎样呢?明显,此时的原型对象将包含一个指向另一个原型的指针,响应地,另一个原型中也包含着一个指向另一个组织函数的指针。如果另一个原型又是另一个范例的实例,那末上述关联依旧建立,云云层层递进,就构成了实例与原型的链条。这就是所谓原型链的基础观点。

上面这段话比较绕口,代码更轻易邃晓,让我们来看看完成原型链的基础情势。以下代码所示:

function Father(){
    this.value = true;
}
Father.prototype.getValue = function(){
    return this.value;
};

function Son(){
    this.value2 = false;
}

// 继承了 Father
Son.prototype = new Father();

Son.prototype.getValue2 = function (){
    return this.value2;
};

var son = new Son();
console.log(son.getValue());  // true

以上代码定义了两个范例:FatherSon。每一个范例离别有一个属性和一个要领。它们的主要区分是 Son 继承了 Father,而继承是经由历程建立 Father 的实例,并将该实例赋给 Son.prototype 完成的。完成的实质是重写原型对象,代之以一个新范例的实例。换句话说,本来存在于 Father 的实例中的一切属性和要领,如今也存在于 Son.prototype 中了。在确立了继承关联今后,我们给 Son.prototype 增加了一个要领,如许就在继承了 Father 的属性和要领的基础上又增加了一个新要领。

我们再用 __proto__ 重写上面代码,更便于人人的邃晓:

function Father(){
    this.value = true;
}
Father.prototype.getValue = function(){
    return this.value;
};

function Son(){
    this.value2 = false;
}

// 继承了 Father
// Son.prototype = new Father(); ==>
Son.prototype = {};
Son.prototype.__proto__ = Father.prototype;
Father.call(Son.prototype);

Son.prototype.getValue2 = function (){
    return this.value2;
};

// var son = new Son(); ==>
var son = {};
son.__proto__ = Son.prototype;
Son.call(son);

console.log(son.getValue()); // true
console.log(son.getValue === son.__proto__.__proto__.getValue); // true 

从以上代码可以看出,实例 son 挪用 getValue() 要领,现实是经过了 son.__proto__.__proto__.getValue 的历程的,个中 son.__proto__ 即是 Son.prototype,而 Son.prototype.__proto__ 又即是 Father.prototype,所以 son.__proto__.__proto__.getValue 实在就是 Father.prototype.getValue

事实上,前面例子中展现的原型链还少一环。我们晓得,一切援用范例缄默都继承了 Obeject,而这个继承也是经由历程原型链完成的。人人要记着,一切函数的默许原型都是 Object 的实例,因而默许原型都邑包含一个内部指针 __proto__,指向 Object.prototype。这也恰是一切自定义范例都邑继承 toString()valueOf() 等默许要领的基础缘由。

下图展现了原型链完成继承的悉数历程。(图源:segmentfault.com,作者:manxisuo

《《JavaScript 闯关记》之原型及原型链》

上图中,pprototype 属性,[p]__proto__ 指对象的原型,[p] 构成的链(虚线部份)就是原型链。从图中可以得出以下信息:

  • Object.prototype 是顶级对象,一切对象都继承自它。

  • Object.prototype.__proto__ === null ,申明原型链到 Object.prototype 停止。

  • Function.__proto__ 指向 Function.prototype

关卡

依据形貌写出对应的代码。

// 应战一
// 1.定义一个组织函数 Animal,它有一个 name 属性,以及一个 eat() 原型要领。
// 2.eat() 的要领体为:console.log(this.name + " is eating something.")。
// 3.new 一个 Animal 的实例 tiger,然后挪用 eat() 要领。
// 4.用 __proto__ 模仿 new Animal() 的历程,然后挪用 eat() 要领。

var Animal = function(name){
    // 待补充的代码
};

var tiger = new Animal("tiger");
// 待补充的代码

var tiger2 = {};
// 待补充的代码
// 应战二
// 1.定义一个组织函数 Bird,它继承自 Animal,它有一个 name 属性,以及一个 fly() 原型要领。
// 2.fly() 的要领体为:console.log(this.name + " want to fly higher.");。
// 3.new 一个 Bird 的实例 pigeon,然后挪用 eat() 和 fly() 要领。
// 4.用 __proto__ 模仿 new Bird() 的历程,然后用代码诠释 pigeon2 为何能挪用 eat() 要领。

var Bird = function(name){
      // 待补充的代码
}

var pigeon = new Bird("pigeon");
// 待补充的代码

var pigeon2 = {};
// 待补充的代码
// 应战三
// 1.定义一个组织函数 Swallow,它继承自 Bird,它有一个 name 属性,以及一个 nesting() 原型要领。
// 2.nesting() 的要领体为:console.log(this.name + " is nesting now.");。
// 3.new 一个 Swallow 的实例 yanzi,然后挪用 eat()、fly() 和 nesting() 要领。
// 4.用 __proto__ 模仿 new Swallow() 的历程,然后用代码诠释 yanzi2 为何能挪用 eat() 要领。

var Swallow = function(name){
      // 待补充的代码
}

var yanzi = new Swallow("yanzi");
// 待补充的代码

var yanzi2 = {};
// 待补充的代码

更多

关注微信民众号「劼哥舍」复兴「答案」,猎取关卡详解。
关注 https://github.com/stone0090/javascript-lessons,猎取最新动态。

    原文作者:劼哥stone
    原文地址: https://segmentfault.com/a/1190000007869506
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞