成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

前端基礎(chǔ):詳解面向?qū)ο?、?gòu)造函數(shù)、原型與原型鏈、繼承

MartinDai / 1439人閱讀

摘要:構(gòu)造函數(shù)創(chuàng)建對(duì)象為了能夠判斷實(shí)例與對(duì)象的關(guān)系,我們就使用構(gòu)造函數(shù)來搞定。像和這樣的原生構(gòu)造函數(shù),在運(yùn)行時(shí)自動(dòng)出現(xiàn)在執(zhí)行環(huán)境中。

大綱:

一、理解對(duì)象

1.1 屬性類型

1.2 屬性方法

二、創(chuàng)建對(duì)象

2.1 簡(jiǎn)單方式創(chuàng)建

2.2 工廠模式

2.3 構(gòu)造函數(shù)

2.4 原型

三、繼承

3.1 原型鏈

3.2 借用構(gòu)造函數(shù)

3.3 組合繼承(原型鏈+借用構(gòu)造函數(shù))

3.4 原型式繼承

3.5 寄生式繼承

3.6 寄生組合繼承

3.6 總結(jié)

四、ES6繼承

Class關(guān)鍵字

extends繼承

super關(guān)鍵字

原生構(gòu)造函數(shù)拓展

Mixin模式的實(shí)現(xiàn)

一、理解對(duì)象

ECMAScript中沒有類的概念,因此它的對(duì)象也與基于類的語言的對(duì)象有所不同。
ECMA-262把對(duì)象定義為“無序?qū)傩缘募?,其屬性可以包含基本值,?duì)象或者函數(shù)”。對(duì)象的每個(gè)屬性或方法都有一個(gè)名字,而每個(gè)名字映射到一個(gè)值。我們可以把ECMAScript的對(duì)象想象成散列表:無非就是一組鍵值對(duì),其值可以是數(shù)據(jù)或函數(shù)。
每個(gè)對(duì)象都是基于一個(gè)引用類型創(chuàng)建的,這個(gè)引用類型可以是原生類型,也可以是開發(fā)人員定義的類型。

1.1屬性類型

ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性。

1.1.1數(shù)據(jù)屬性

數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位置,在這個(gè)位置可以讀取和寫入值。數(shù)據(jù)屬性有4個(gè)描述其行為的特征。

[[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性。能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為true。

[[Enumerable]]:表示能否通過for-in循環(huán)返回屬性。直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為true。

[[Writable]]:表示能否修改屬性的值。直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)為true。

[[Value]]:包含這個(gè)屬性的數(shù)據(jù)值。讀取屬性值的時(shí)候,從這個(gè)位置讀;寫入屬性值的時(shí)候,把新值保存在這個(gè)位置。這個(gè)特性的默認(rèn)值為undefined。

要修改屬性默認(rèn)的特性,必須通過ES5的Object.defineProperty()方法。這個(gè)方法接收三個(gè)參數(shù):屬性所在的對(duì)象、屬性的名字和一個(gè)描述符對(duì)象。其中,描述符(descriptor)對(duì)象的屬性必須是:configurable、enumerable、writable和value。設(shè)置其中的一或多個(gè)值,可以更改對(duì)應(yīng)的特征值。

var person = {};
Object.defineProperty(person, "name", {
    writable: false,      //不能修改屬性的值....
    configurable: false,  //不能通過delete刪除屬性.....
    value: "Jason"        //寫入屬性值
});
console.log(person.name); //Jason
person.name = "Cor";
console.log(person.name); //Jason
delete person.name;
console.log(person.name); //Jason

注意,一旦把屬性設(shè)置為不可配置的,就不能再把它更改為可配置的了。此時(shí)再調(diào)用

Object.defineProperty()方法修改除writable之外的特性就會(huì)導(dǎo)致錯(cuò)誤。
var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Jason"
});
//拋出錯(cuò)誤
Object.defineProperty(person, "name", {
    comfogirable: true,    //這行代碼修改了特性導(dǎo)致報(bào)錯(cuò)
    value: "Cor"
});

在調(diào)用Object.defineProperty()方法時(shí),如果不指定configurable、enumerable和writable特性的默認(rèn)值都是false。

1.1.2訪問器屬性

訪問器屬性不包含數(shù)據(jù)值,它包含一對(duì)getter和setter函數(shù)(不過,這兩個(gè)函數(shù)都不是必須的)。在讀取訪問器屬性時(shí)回調(diào)去getter函數(shù),這個(gè)函數(shù)負(fù)責(zé)返回有效的值;在寫入訪問器屬性時(shí),會(huì)調(diào)用setter函數(shù)并傳入新值,這個(gè)函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù)。訪問器屬性有如下4個(gè)特性:

[[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為數(shù)據(jù)屬性。對(duì)于直接在對(duì)象上定義的屬性,這個(gè)特性默認(rèn)值為true。

[[Enumerable]]:表示能否通過for-in循環(huán)返回屬性。對(duì)于直接在對(duì)象上定義的屬性,這個(gè)特性的默認(rèn)值為true。

[[Get]]:在讀取屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為undefined。

[[Set]]:在寫入屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為undefined。

訪問器屬性不能直接定義,必須使用Object.defineProperty()方法來定義。
注意,一旦定義了取值函數(shù)get(或存值函數(shù)set),就不能將writable屬性設(shè)為true,或者同時(shí)定義value屬性,否則會(huì)報(bào)錯(cuò)。

    var book = {
        _year: 2004,
        edition: 1
    };
    
    Object.defineProperty(book, "year", {
        get: function() {
            return this._year;
        },
        set: function(newValue) {    //接受新值的參數(shù)
            if(newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    });
    book.year = 2005;            //寫入訪問器,會(huì)調(diào)用setter并傳入新值
    console.log(book.edition);  //2
var obj = {};

Object.defineProperty(obj, "p", {
  value: 123,
  get: function() { return 456; }
});
// TypeError: Invalid property.
// A property cannot both have accessors and be writable or have a value

Object.defineProperty(obj, "p", {
  writable: true,
  get: function() { return 456; }
});
// TypeError: Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute
1.2屬性方法

Object.getOwnPropertyDescriptor()

該方法可以獲取屬性描述對(duì)象。它的第一個(gè)參數(shù)是一個(gè)對(duì)象,第二個(gè)參數(shù)是一個(gè)字符串,對(duì)應(yīng)該對(duì)象的某個(gè)屬性名。注意,該方法只能用于對(duì)象自身的屬性,不能用于繼承的屬性。

var obj = { p: "a" };

Object.getOwnPropertyDescriptor(obj, "p")
// Object { value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Object.getOwnPropertyNames()

該方法返回一個(gè)數(shù)組,成員是參數(shù)對(duì)象自身的全部屬性的屬性名,不管該屬性是否可遍歷。下面例子中,obj.p1是可遍歷的,obj.p2是不可遍歷的。但是Object.getOwnPropertyNames會(huì)將它們都返回。

var obj = Object.defineProperties({}, {
  p1: { value: 1, enumerable: true },
  p2: { value: 2, enumerable: false }
});

Object.getOwnPropertyNames(obj)
// ["p1", "p2"]

與Object.keys的行為不同,Object.keys只返回對(duì)象自身的可遍歷屬性的全部屬性名。下面代碼中,數(shù)組自身的length屬性是不可遍歷的,Object.keys不會(huì)返回該屬性。第二個(gè)例子的Object.prototype也是一個(gè)對(duì)象,所以實(shí)例對(duì)對(duì)象都會(huì)繼承它,它自身的屬性都是不可遍歷的。

Object.keys([]) // []
Object.getOwnPropertyNames([]) // [ "length" ]

Object.keys(Object.prototype) // []
Object.getOwnPropertyNames(Object.prototype)
// ["hasOwnProperty",
//  "valueOf",
//  "constructor",
//  "toLocaleString",
//  "isPrototypeOf",
//  "propertyIsEnumerable",
//  "toString"]

Object.defineProperty(),Object.defineProperties()

Object.defineProperty()方法允許通過屬性描述對(duì)象,定義或修改一個(gè)屬性,然后返回修改后的對(duì)象。實(shí)例上面已經(jīng)介紹。
如果一次性定義或修改多個(gè)屬性,可以使用Object.defineProperties方法。

var obj = Object.defineProperties({}, {
  p1: { value: 123, enumerable: true },
  p2: { value: "abc", enumerable: true },
  p3: { get: function () { return this.p1 + this.p2 },
    enumerable:true,
    configurable:true
  }
});

obj.p1 // 123
obj.p2 // "abc"
obj.p3 // "123abc"

Object.prototype.propertyIsEnumerable()

實(shí)例對(duì)象的propertyIsEnumerable方法返回一個(gè)布爾值,用來判斷某個(gè)屬性是否可遍歷。

var obj = {};
obj.p = 123;

obj.propertyIsEnumerable("p") // true
obj.propertyIsEnumerable("toString") // false

二、創(chuàng)建對(duì)象 2.1 簡(jiǎn)單方式創(chuàng)建

我們可以通過new的方式創(chuàng)建一個(gè)對(duì)象,也可以通過字面量的形式創(chuàng)建一個(gè)簡(jiǎn)單的對(duì)象。

var obj = new Object();
或
var obj = {};
//為對(duì)象添加方法,屬性
var person = {};
person.name = "TOM";
person.getName = function() {
    return this.name;
}

// 也可以這樣
var person = {
    name: "TOM",
    getName: function() {
        return this.name;
    }
}

這種方式創(chuàng)建對(duì)象簡(jiǎn)單,但也存在一些問題:創(chuàng)建出來的對(duì)象無法實(shí)現(xiàn)對(duì)象的重復(fù)利用,并且沒有一種固定的約束,操作起來可能會(huì)出現(xiàn)這樣或者那樣意想不到的問題。如下面這種情況。

var a = new Object;
var b = new Object;
var c = new Object;
c[a]=a;
c[b]=b;
console.log(c[a]===a); //輸出什么 false
該題的詳細(xì)解析請(qǐng)參考文章一條面試題

2.2 工廠模式

當(dāng)我們需要?jiǎng)?chuàng)建一系列相似對(duì)象時(shí),顯然上面簡(jiǎn)單的對(duì)象創(chuàng)建方式已經(jīng)不可以了,這會(huì)使代碼中出現(xiàn)很對(duì)重復(fù)的編碼,造成代碼冗余難維護(hù)。就以person對(duì)象為例,假如我們?cè)趯?shí)際開發(fā)中,需要一個(gè)名字叫做TOM的person對(duì)象,同時(shí)還需要另外一個(gè)名為Jake的person對(duì)象,雖然它們有很多相似之處,但是我們不得不重復(fù)寫兩次。沒增加一個(gè)新的person對(duì)象,就重復(fù)一遍代碼,聽起來就是很崩潰的。

var perTom = {
    name: "TOM",
    age: 20,
    getName: function() {
        return this.name
    }
};

var perJake = {
    name: "Jake",
    age: 22,
    getName: function() {
        return this.name
    }
}

我們可以使用工廠模式的方式解決這個(gè)問題。顧名思義,工廠模式就是我們提供一個(gè)模子,然后通過這個(gè)模子復(fù)制出我們需要的對(duì)象。需要多少,就復(fù)制多少。

var createPerson = function(name, age) {

    // 聲明一個(gè)中間對(duì)象,該對(duì)象就是工廠模式的模子
    var o = new Object();

    // 依次添加我們需要的屬性與方法
    o.name = name;
    o.age = age;
    o.getName = function() {
        return this.name;
    }

    return o;
}

// 創(chuàng)建兩個(gè)實(shí)例
var perTom = createPerson("TOM", 20);
var PerJake = createPerson("Jake", 22);

工廠模式幫助我們解決了重復(fù)代碼的問題,可以快速的創(chuàng)建對(duì)象。但是這種方式仍然存在兩個(gè)問題:沒有辦法識(shí)別對(duì)象實(shí)例的類型

var obj = {};
var foo = function() {}

console.log(obj instanceof Object);  // true
console.log(foo instanceof Function); // true
console.log(perTom instancceof (類名??));  //發(fā)現(xiàn)好像并不存在一個(gè)Person類

因此,在工廠模式的基礎(chǔ)上,我們需要使用構(gòu)造函數(shù)的方式來解決這個(gè)問題。

2.3 構(gòu)造函數(shù) 2.3.1 new關(guān)鍵字

在Javascript中,new關(guān)鍵字十分神奇,可以讓一個(gè)函數(shù)變的與眾不同??聪旅孢@個(gè)例子。

function demo() {
    console.log(this);
}

demo();  // window,嚴(yán)格模式下this指向undefined
new demo();  // demo

從這個(gè)例子我們可以看到,使用new之后,函數(shù)內(nèi)部發(fā)生了一些變化,this指向發(fā)生了改變。那么new關(guān)鍵字到底都做了什么事情呢?

// 先一本正經(jīng)的創(chuàng)建一個(gè)構(gòu)造函數(shù),其實(shí)該函數(shù)與普通函數(shù)并無區(qū)別
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    }
}

// 將構(gòu)造函數(shù)以參數(shù)形式傳入
function New(func) {

    // 聲明一個(gè)中間對(duì)象,該對(duì)象為最終返回的實(shí)例
    var res = {};
    if (func.prototype !== null) {

        // 將實(shí)例的原型指向構(gòu)造函數(shù)的原型
        res.__proto__ = func.prototype;
    }

    // ret為構(gòu)造函數(shù)執(zhí)行的結(jié)果,這里通過apply,將構(gòu)造函數(shù)內(nèi)部的this指向修改為指向?qū)嵗龑?duì)象res
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));

    // 當(dāng)我們?cè)跇?gòu)造函數(shù)中明確指定了返回對(duì)象時(shí),那么new的執(zhí)行結(jié)果就是該返回對(duì)象(即在構(gòu)造函數(shù)中明確寫了return this;)
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }

    // 如果沒有明確指定返回對(duì)象,則默認(rèn)返回res,這個(gè)res就是實(shí)例對(duì)象
    return res;
}

// 通過new聲明創(chuàng)建實(shí)例,這里的p1,實(shí)際接收的正是new中返回的res
var person1 = New(Person, "tom", 20);
console.log(person1.getName());

// 當(dāng)然,這里也可以判斷出實(shí)例的類型了
console.log(p1 instanceof Person); // true

JavaScript內(nèi)部會(huì)通過一些特殊處理,將var p1 = New(Person, ’tom’, 20);等效于var person1 = new Person(’tom’, 20); 我們熟悉的這種形式。具體是怎么處理的,暫時(shí)沒法作出解釋,需要更深入的了解原理。

當(dāng)構(gòu)造函數(shù)顯示的return,會(huì)出現(xiàn)什么情況?

我們先來列出幾種返回的情況,看一下返回什么結(jié)果:

//直接 return  
function A(){  
   return;  
}  
//返回 數(shù)字類型  
function B(){  
   return 123;  
}  
//返回 string類型  
function C(){  
   return "abcdef";  
}  
//返回 數(shù)組  
function D(){  
   return ["aaa", "bbb"];  
}  
//返回 對(duì)象  
function E(){  
   return {a: 2};  
}  
//返回 包裝類型  
function F(){  
   return new Number(123);  
}  

//結(jié)果是:
A {}  
B {}  
C {}  
["aaa", "bbb"]  
Object {a: 2}  
Number {[[PrimitiveValue]]: 123}  
A {}  

結(jié)合構(gòu)造函數(shù)我們來看一下結(jié)果:

function Super (a) {  
   this.a = a;  
   return 123;  
}  
Super.prototype.sayHello = function() {  
   alert("hello world");  
}  
function Super_ (a) {  
   this.a = a;  
   return {a: 2};  
}  
Super_.prototype.sayHello = function() {  
   alert("hello world");  
}  

new Super(1); 
new Super_(1);

//結(jié)果
Super {a: 1} 具有原型方法sayHello
Object {a: 2}

總結(jié)一下:在構(gòu)造函數(shù)中 return 基本類型不會(huì)影響構(gòu)造函數(shù)的值,而 return 對(duì)象類型 則會(huì)替代構(gòu)造函數(shù)返回該對(duì)象。

2.3.2 構(gòu)造函數(shù)創(chuàng)建對(duì)象

為了能夠判斷實(shí)例與對(duì)象的關(guān)系,我們就使用構(gòu)造函數(shù)來搞定。
像Object和Array這樣的原生構(gòu)造函數(shù),在運(yùn)行時(shí)自動(dòng)出現(xiàn)在執(zhí)行環(huán)境中。我們也可以創(chuàng)建自定義的構(gòu)造函數(shù),從而定義對(duì)象類型的屬性和方法。例如,

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.getName = function() {
        console.log(this.name);
    }
}
var person1 = new Person("Jason", 18, "WEB”);
var person2 = new Person("Cor", 19, "WEB");
console.log(person1.getName());   //Jason
console.log(person1 instanceof Person);  //true

構(gòu)造函數(shù)模式和工廠模式存在一下不同之處

沒有顯示的創(chuàng)建對(duì)象(new Object() 或者 var a = {})

直接將屬性和方法賦給this對(duì)象

沒有return語句

關(guān)于構(gòu)造函數(shù),如果你暫時(shí)不能夠理解new的具體實(shí)現(xiàn),就先記住下面這幾個(gè)結(jié)論:

與普通函數(shù)相比,構(gòu)造函數(shù)并沒有任何特別的地方,首字母大寫只是我們開發(fā)中的約定規(guī)定,用于區(qū)分普通函數(shù)

new關(guān)鍵字讓構(gòu)造函數(shù)擁有了與普通函數(shù)不同的許多特點(diǎn),new的過程中,執(zhí)行了下面的過程:

聲明一個(gè)中間對(duì)象,即實(shí)例對(duì)象

將該中間對(duì)象的原型指向構(gòu)造函數(shù)原型(res.__proto__ = func.prototype)

將構(gòu)造函數(shù)this,指向該中間對(duì)象

返回該中間對(duì)象,及返回實(shí)例對(duì)象

2.3.3 把構(gòu)造函數(shù)當(dāng)普通函數(shù)
//當(dāng)作構(gòu)造函數(shù)使用
var person = new Person("Jason", 18, "web");
person.getName();        //“Jason"

//作為普通函數(shù)調(diào)用
Person("Cor", 19, "web");    //添加到window
window.getName();        //“cor"

//在另一個(gè)對(duì)象的作用域中調(diào)用
var o = new Object();
Person.call(o, "Kristen", 22, "web");
o.getName();               //"kriten"

當(dāng)在全局作用域中調(diào)用一個(gè)函數(shù)時(shí),this對(duì)象總是指向Global對(duì)象(在瀏覽器中的window對(duì)象),最后使用了call() ( 或者apply() )在某個(gè)特殊對(duì)象的作用域中調(diào)用Person()函數(shù)。這里是在對(duì)象o的作用域調(diào)用的,因此調(diào)用后o就擁有了所有屬性和方法。

2.3.4 構(gòu)造函數(shù)的問題

構(gòu)造函數(shù)的主要問題:上述例子中,每一個(gè)getName方法實(shí)現(xiàn)的功能其實(shí)是一模一樣的,但是由于分別屬于不同的實(shí)例,就不得不一直不停的為getName分配空間。

person1.getName == person2.getName;  //false

我們對(duì)構(gòu)造函數(shù)稍加修改,在構(gòu)造函數(shù)內(nèi)部我們把getName屬性設(shè)置成等于全局的getName函數(shù)。由于構(gòu)造函數(shù)的getName屬性包含的是一個(gè)指向函數(shù)的指針,因此person1和person2對(duì)象就共享了在全局作用域中定義的同一個(gè)getName()函數(shù)。

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

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

var person1 = new Person("Jason", 18, "WEB");
var person2 = new Person("Cor", 19, "WEB”);
person1.getName == person2.getName;  //true
2.4 原型

我們創(chuàng)建的每一個(gè)函數(shù),都可以有一個(gè)prototype屬性,該屬性指向一個(gè)對(duì)象,這個(gè)對(duì)象就是我們說的原型對(duì)象。原型對(duì)象的用途是:包含所有可以由構(gòu)造函數(shù)實(shí)例共享的屬性和方法。按照字面理解就是,prototype就是由構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象的原型對(duì)象,使用原型對(duì)象的好處就是可以讓所有實(shí)例共享原型對(duì)象所包含的方法,屬性。

2.4.1 理解原型對(duì)象

上面說了,每一個(gè)函數(shù)創(chuàng)建的時(shí)候,都會(huì)依據(jù)某些規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型的對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。以上面的例子來說,也就是Person.prototype.constructor指向Person。
創(chuàng)建了自定義構(gòu)造函數(shù)之后,其原型對(duì)象默認(rèn)只會(huì)取得constructor屬性;至于其它方法,則都是從Object繼承而來的。當(dāng)調(diào)用構(gòu)造函數(shù)new一個(gè)新實(shí)例后(person1) ,實(shí)例都有一個(gè)__proto__屬性,該屬性指向構(gòu)造函數(shù)的原型對(duì)象(Person.prototype),通過這個(gè)屬性,讓實(shí)例對(duì)象也能夠訪問原型對(duì)象上的方法。因此,當(dāng)多有的實(shí)例都能夠通過__proto__訪問到原型對(duì)象時(shí),原型對(duì)象的方法與屬性就變成了共有方法與屬性。

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

Person.prototype.getName = function() {
    console.log(this.name);
}

var person1 = new Person("Jason", 20);
var person2 = new Person("Tim", 40);

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



ECMA-262第五版中管這個(gè)指針叫[[Prototype]],雖然在腳本中沒有標(biāo)準(zhǔn)的方式訪問[[Prototype]],單Firefox,Safari和Chrome在每個(gè)對(duì)象上都支持一個(gè)屬性__proto__;而在其他實(shí)現(xiàn)中,這個(gè)屬性對(duì)腳本則是完全不可見的。不過需要明確的真正重要一點(diǎn)就是,這個(gè)連接存在于實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間。
雖然所有的實(shí)現(xiàn)中都無法訪問到[[Prototype]],但是可以通過isPrototypeOf()方法來確定對(duì)象之間是否存在這種關(guān)系。從本質(zhì)上講,如果[[Prototype]]指向調(diào)用isPrototypeOf()方法的對(duì)象(Person.prototype),那么這個(gè)方法就會(huì)返回true。

console.log(Person.prototype.isPrototypeOf(person1))  //true
console.log(Person.prototype.isPrototypeOf(person2))  //true

ES5增加了一個(gè)新方法,叫Object.getPrototypeOf(),在所有支持的實(shí)現(xiàn)中,這個(gè)方法返回[[Prototype]]的值,可以方便的獲取一個(gè)對(duì)象的原型。

console.log(Object.getPrototypeOf(person1) == Person.prototype); //true
console.log(Object.getPrototypeOf(person1).getName());     //"Jason"
    
搜索機(jī)制:

當(dāng)我們?cè)L問對(duì)象的屬性或者方法時(shí),會(huì)優(yōu)先訪問實(shí)例對(duì)象自身的屬性和方法。當(dāng)代碼執(zhí)行到讀取對(duì)象的屬性a時(shí),都會(huì)執(zhí)行一次搜索。搜索首先從對(duì)象的實(shí)例本身開始。如果在實(shí)例中找到屬性a,則返回該屬性的值;如果沒找到,則繼續(xù)搜索之震驚指向的原型對(duì)象,在原型對(duì)象中查找屬性a,如果在原型中找到這個(gè)屬性,則返回該屬性。簡(jiǎn)單的說,就是會(huì)一層層搜索,若搜索到則返回,沒搜索到則繼續(xù)下層搜索。
雖然可以通過實(shí)例訪問原型的值,但是卻不能通過對(duì)象實(shí)例重寫原型的值。如果我們?yōu)樗砑恿艘粋€(gè)屬性,并且該屬性名和實(shí)例原型中的一個(gè)屬性同名,就會(huì)在實(shí)例中創(chuàng)建該屬性,該屬性戶屏蔽原型中的相同屬性。因?yàn)樗阉鞯臅r(shí)候,首先在實(shí)例本身搜索,查找到后直接返回實(shí)例中屬性的值,不會(huì)搜索到原型中。

function Person() {
}

Person.prototype.name = "Jason";
Person.prototype.age = 29;
Person.prototype.job = "Web";
Person.prototype.getName = function() {
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();
person1.name = "Cor";
person1.getName();         //"Cor"
person2.getName();        //"Jason"

若想能夠訪問原型中的屬性,只要用delete操作符刪掉實(shí)例中的屬性即可。

delete person1.name;
person1.getName();       //"Jason"
    
hasOwnProperty()

該方法可以檢測(cè)一個(gè)屬性是存在在實(shí)例中,還是存在于原型中。這個(gè)方法繼承于Object,只有在給定屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回true

console.log(person1.hasOwnProperty("name"));        //false
person1.name = "Cor";
console.log(person1.hasOwnProperty("name"));        //true;
2.4.2 in操作符

in操作符有兩種使用方式:多帶帶使用和在for-in循環(huán)中使用。
在多帶帶使用時(shí),in操作符在通過對(duì)象能訪問到給定屬性時(shí)就會(huì)返回true,無論屬性存在于實(shí)例還是原型中。

console.log("name" in person1); true

in的這種特殊性做常用的場(chǎng)景之一,就是判斷當(dāng)前頁面是否在移動(dòng)端打開。

   isMobile = "ontouchstart" in document;

// 很多人喜歡用瀏覽器UA的方式來判斷,但并不是很好的方式
2.4.3 更簡(jiǎn)單的原型語法

可以用一個(gè)包含所有屬性和方法的對(duì)象字面量來重寫整個(gè)原型對(duì)象。

function Person(){}

Person.prototype = {
    name : "Jason",
    age : 29,
    job : "Web",
    getName : function() {
        console.log(this.name)
    }
};

用對(duì)象字面的方法和原來的方法會(huì)有區(qū)別:constructor屬性不再指向Person了。因?yàn)檫@種寫法,本質(zhì)上是修改了Person.prototype對(duì)象的引用,將引用從原來的默認(rèn)值修改為了這個(gè)新對(duì)象{}的引用,constructor屬性也變成了新對(duì)象{}的constructor屬性(指向Object構(gòu)造函數(shù)),不再指向Person。盡管instanceof操作符能返回正確的結(jié)果,但是constructor已經(jīng)無法確定對(duì)象的類型了。

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

如果construct的值很重要,我們可以像下面這樣特意將它設(shè)置回適當(dāng)?shù)闹怠?/p>

function Person(){}

Person.prototype = {
    constructor: Person,
    name : "Jason",
    age : 29,
    job : "Web",
    getName : function() {
        console.log(this.name)
    }
};
2.4.4 原型的動(dòng)態(tài)性

由于在原型中查找值的過程是一次搜索,因此我們?cè)谠蛯?duì)象上所做的任何修改都能夠立即從實(shí)例上反映出來——即使是先創(chuàng)建了實(shí)例后修改原型也一樣。下面這個(gè)例子中,friend實(shí)例是在添加sayHi方法之前創(chuàng)建的,但它仍然可以訪問新方法。這是因?yàn)閷?shí)例與原型之間只不過是一個(gè)指針,而非一個(gè)副本,因此就可以在原型中找到新的sayHi屬性并返回值。

var friend = new Person();

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

friend.sayHi();        //"hi"

但是,如果是通過{}這種重寫原型對(duì)象的情況,就和上邊不一樣了。因?yàn)閚ew實(shí)例時(shí),實(shí)例中的__proto__屬性指向的是最初原型,而把原型修改為新的對(duì)象{}就等于切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系,同時(shí)實(shí)例中仍然保存的是最初原型的指針,因此無法訪問到構(gòu)造函數(shù)的新原型中的屬性。請(qǐng)記?。?strong>實(shí)例只與原型有關(guān),與構(gòu)造函數(shù)無關(guān)。

function Person(){}

var friend = new Person();

Person.prototype = {
    constructor: Person,
    name : "Jason",
    age : 29,
    job : "Web",
    sayName : function() {
        console.log(this.name)
    }
};

friend.sayName();   //error,friend.sayName is not a function

如圖,重寫原型對(duì)象切斷了現(xiàn)有原型與任務(wù)之前已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;friend實(shí)例引用的仍然是最初的原型,因此訪問不到sayName屬性。
注意,若想使用對(duì)象字面量重寫原型,要在創(chuàng)建實(shí)例之前完成。

2.4.5 原生對(duì)象的原型

原型創(chuàng)建對(duì)象的重要性不僅體現(xiàn)在創(chuàng)建自定義對(duì)象方面,就連所有原生的引用類型都采用這種模式創(chuàng)建。所有原生引用類型(Object、Array、String,等等)都在其構(gòu)造函數(shù)的原型上定義了方法。

console.log(Array.prototype.sort);            //function(){…}        
console.log(String.prototype.substring);      //function(){...}

通過原生對(duì)象的原型,我們也可以自定義新的方法。

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

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

鞏固一下原型相關(guān)的知識(shí)點(diǎn),我們以window這個(gè)對(duì)象為例,來查看一下各個(gè)屬性值

三、繼承 3.1原型鏈

原型對(duì)象其實(shí)也是普通的對(duì)象。幾乎所有的對(duì)象都可能是原型對(duì)象,也可能是實(shí)例對(duì)象,而且還可以同時(shí)是原型對(duì)象與實(shí)例對(duì)象。這樣的一個(gè)對(duì)象,正是構(gòu)成原型鏈的一個(gè)節(jié)點(diǎn)。
我們知道所有的函數(shù)都有一個(gè)叫做toString的方法,那么這個(gè)方法到底是從哪來的呢?先聲明一個(gè)函數(shù):function add() {};,通過下圖來看一下這個(gè)函數(shù)的原型鏈情況。

其中add是Function對(duì)象的實(shí)例。而Function的原型對(duì)象同時(shí)又是Object原型的實(shí)例。這樣就構(gòu)成了一條原型鏈。原型鏈的訪問,其實(shí)跟作用域鏈有很大的相似之處,他們都是一次單向的查找過程。因此實(shí)例對(duì)象能夠通過原型鏈,訪問到處于原型鏈上對(duì)象的所有屬性與方法。這也是foo最終能夠訪問到處于Object原型對(duì)象上的toString方法的原因。
我們?cè)賮砜匆粋€(gè)例子:

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

//繼承了SuperType
//SubType的原型對(duì)象等于SubperType的實(shí)例,
//這樣SubType內(nèi)部就會(huì)有一個(gè)指向SuperType的指針從而實(shí)現(xiàn)繼承
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue());  //true

SubType繼承了superType,而繼承是通過創(chuàng)建SuperType實(shí)例,并將該實(shí)例賦給SubType.prototype實(shí)現(xiàn)的。原來存在于SuperType的實(shí)例中的所有屬性和方法,現(xiàn)在也存在于SubType.prototype中了。這個(gè)例子中的實(shí)例、構(gòu)造函數(shù)和原型之間的關(guān)系如圖:

注意,instance.constructor現(xiàn)在指向的是SuperType,是因?yàn)閟ubtype的原型指向了另一個(gè)對(duì)象SuperType的原型,而這個(gè)原型對(duì)象的constructor屬性指向的是SuperType。

    通過實(shí)現(xiàn)原型鏈,本質(zhì)上擴(kuò)展了前面說的原型搜索機(jī)制。在通過原型鏈實(shí)現(xiàn)繼承的情況下,搜索過程就得以沿著原型鏈繼續(xù)向上。那上面這個(gè)例子來說,調(diào)用instance.getSuperValue()會(huì)經(jīng)歷三個(gè)搜索步驟:

搜索instance實(shí)例;未搜到;

搜索SubType.prototype,未搜索到;

搜索SuperType.prototype,找到該屬性,返回值。

3.1.1 默認(rèn)原型

所有函數(shù)的默認(rèn)原型都是Object的實(shí)例,因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)布指針,指向Object.prototype。這也正是所有自定義類型都會(huì)繼承toString()、valueOf()等默認(rèn)方法的根本原因。所以上面的例子展示的原型鏈中還應(yīng)該包含另一個(gè)繼承層次。

3.1.2 確定原型和實(shí)例的關(guān)系

有兩種方式可以確認(rèn):instanceof操作符以及isPrototypeOf()方法。

console.log(instance instanceof Object);    //true    
console.log(instance instanceof SuperType);    //true
console.log(instance instanceof SubType);    //true

console.log(Object.prototype.isPrototypeOf(instance));      //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance));   //true

由于原型鏈的關(guān)系,可以說instance是Object,SuperType,SubType中任何一個(gè)類型的實(shí)例,因此都返回true。同樣的,只要是原型鏈中出現(xiàn)過的原型,都可以說是該原型鏈所派生實(shí)例的原型。

3.1.3 謹(jǐn)慎定義方法

子類型(SubType)有時(shí)候需要重寫超類型(SuperType)中的某個(gè)方法,或者需要添加超類型中沒有的方法,但不管怎樣,給原型添加方法一定要在替換原型語句之后。避免出現(xiàn)2.4.4中出現(xiàn)的問題。
另外要注意,在通過原型鏈實(shí)現(xiàn)繼承時(shí),不能使用對(duì)象字面量創(chuàng)建原型方法,這樣做會(huì)重寫原型。

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

//繼承超類型
SubType.prototype = new SuperType();

//添加新方法
SubType.prototype.getSubValue = function(){
    return this.subproperty;
}

//重寫超類型中的方法
SubType.prototype.getSuperValue = function(){
    return false;
}

//使用字面量添加新方法,會(huì)導(dǎo)致上面代碼無效
SubType.prototype = {
    getSubValue : function(){
        return this.subproperty;
    },
    someOtherMethod : function(){
        return false;
    }
}
3.1.4 原型鏈的問題
function SuperType(){
    this.colors = ["red", "blue", "green"];
}

function SubType(){
}

SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);  //"red, blue, green, black"

var instance2 = new SubType();
console.log(instance2.colors);    //"red, blue, green, black"

這個(gè)例子中的SuperType構(gòu)造函數(shù)定義了一個(gè)colors屬性,該屬性包含一個(gè)數(shù)組(引用類型值)。SuperType的每個(gè)實(shí)例都會(huì)有各自包含自己數(shù)組的colors屬性。當(dāng)SubType通過原型鏈繼承了SuperType之后,SubType.prototype就變成了SuperType(),所以它也用用了一個(gè)colors屬性。就跟專門創(chuàng)建了一個(gè)SubType.prototype.colors屬性一樣。結(jié)果SubType得所有實(shí)例都會(huì)共享這一個(gè)colors屬性。
原型鏈的第二個(gè)問題是:在創(chuàng)建子類型的實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)。實(shí)際上,應(yīng)該說是沒有辦法在不影響所有對(duì)象實(shí)例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)。所以在實(shí)際運(yùn)用中很少會(huì)多帶帶使用原型鏈。

3.2 借用構(gòu)造函數(shù)

在子類型構(gòu)造函數(shù)中調(diào)用超類型構(gòu)造函數(shù)。函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對(duì)象,因此通過使用apply()和call()方法也可以在新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù)。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

function SubType(){
    //繼承了SuperType,同時(shí)還傳遞了參數(shù)
    SuperType.call(this, "Jason”);

    //執(zhí)行上邊這個(gè)語句,相當(dāng)于把SuperType構(gòu)造函數(shù)的屬性在這里復(fù)制了一份
    //this.name = "Jason”;
    //this.colors = ["red", "blue", "green"];

    //實(shí)例屬性
    this.age = 18;
}

var instance1 = new SubType();
instance1.colors. push("black");
console.log(instance1.colors);         //"red,blue,green,black"
console.log(instance1.name);        //"Jason"
console.log(instance1.age);            //18

var instance2 = new SubType();
console.log(instance2.colors);        //"red,blue,green"

為了確保SuperType構(gòu)造函數(shù)不會(huì)重寫子類型屬性,可以在調(diào)用超類型構(gòu)造函數(shù)之后,再添加需要在子類型的定義的私有屬性。
這個(gè)方式實(shí)現(xiàn)繼承仍然存在問題:

方法都在構(gòu)造函數(shù)中定義,每個(gè)實(shí)例創(chuàng)建后都會(huì)為構(gòu)造函數(shù)的屬性分配自己的內(nèi)存,復(fù)用方法就無從談起。

而且,即使在超類型的原型中定義了公有屬性,但這些屬性對(duì)于子類型而言是不可見的,所以采用這種方式繼承,就要把所有屬性寫在構(gòu)造函數(shù)中

所以這種方式在實(shí)際開發(fā)中是很少多帶帶這樣使用的。

3.3 組合繼承(原型鏈+借用構(gòu)造函數(shù))

組合繼承(combination inheritance),有時(shí)候也叫做經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的繼承方式組合到一塊,從而發(fā)揮二者的長(zhǎng)處的一種繼承方式。其背后的思路是使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。這樣既可以通過在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又可以在構(gòu)造函數(shù)中定義方法保證每個(gè)實(shí)例都有自己的私有屬性。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

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

function SubType(name, age){
    //繼承屬性
    SuperType.call(this, name);
    
    this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
};

var instance1 = new SubType("Jason", 18);
instance1.colors.push("black");
console.log(instance1.colors);    //"red,blue,green,black"
instance1.sayName();            //"Jason"
instance1.sayAge();                //18

var instance2 = new SubType("Cor", 20);
console.log(instance2.colors)    //"red,blue,green"
instance2.sayName();            //"Cor"
instance2.sayAge();                //20
    
組合繼承的問題

組合繼承雖然是現(xiàn)在javascript最常用的繼承模式,但是它也有不足。組合繼承最大的問題就是無論什么情況下,都會(huì)調(diào)用兩次超類型的構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)的內(nèi)部。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "bule", "grenn"];
}

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

function SubType(name, age){
    SuperType.call(this, name);          //第二次調(diào)用SuperType
 
    this.age = age;
}

SubType.prototype = new SuperType();     //第一次調(diào)用SuperType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

有注釋的兩行代碼是調(diào)用SuperType構(gòu)造函數(shù)的代碼,第一次調(diào)用SuperType構(gòu)造函數(shù)時(shí),SubType.prototype會(huì)有SuperType的實(shí)例屬性。第二次調(diào)用SuperType的構(gòu)造函數(shù)時(shí)SubType會(huì)在構(gòu)造函數(shù)中添加了SuperType的實(shí)例屬性。當(dāng)創(chuàng)建SubType的實(shí)例它的[[Prototype]]和自身上都有相同屬性。根據(jù)搜索機(jī)制自身的屬性就會(huì)屏蔽SubType原型對(duì)象上的屬性。等于原型對(duì)象上的屬性是多余的了。如圖所示,有兩組name和colors屬性:一組在實(shí)例上,一組在Subtype原型中。這就是調(diào)用兩次構(gòu)造函數(shù)的結(jié)果。

3.4 原型式繼承

基本思想是借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。

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

在object()函數(shù)內(nèi)部,先創(chuàng)建了一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回了這個(gè)臨時(shí)構(gòu)造函數(shù)的一個(gè)新實(shí)例。從本質(zhì)上講,object()對(duì)傳入其中的對(duì)象執(zhí)行了一次復(fù)制。
在ECMAScript5通過新增Object.create()方法規(guī)范了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù):第一個(gè)用于做新對(duì)象原型的對(duì)象;第二個(gè)參數(shù)可選,為新對(duì)象定義額外的屬性的對(duì)象。在傳入一個(gè)參數(shù)的情況下,

Object.create()和object()方法的行為相同。
var person = {
    name: "Jason",
    friends: ["Cor", "Court", "Sam"]
};

var anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
yetAnotherPerson.friends.push("Barbie");

console.log(yetAnotherPerson.name);    //"Greg"
console.log(anotherPerson.name);       //"Jason"
console.log(person.friends);           //"Cor,Court,Sam,Rob,Barbie"
console.log(anotherPerson.__proto__);               //"Cor,Court,Sam,Rob,Barbie"

這種原型式繼承,要求必須有一個(gè)對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ),把這個(gè)它傳遞給object()對(duì)象,然后再根據(jù)需求對(duì)得到的對(duì)象加以修改。在這個(gè)例子中,person對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ),把它傳入object()函數(shù)后返回一個(gè)新對(duì)象。這個(gè)新對(duì)象是將person作為原型,這意味著person.friend不僅屬于person,而且被anotherPerson和yetAnotherPerson共享。

3.5 寄生式繼承

寄生式(parasitic)繼承是與原型式繼承緊密相關(guān)的一種思路。寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來增強(qiáng)對(duì)象,最后再像真的是它做了所有工作一樣返回對(duì)象。

function createAnother(original){
    var clone = object(original);     //通過調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
    clone.sayHi = function(){         //以某種方式來增強(qiáng)這個(gè)對(duì)象
         alert("Hi");
    };
    return clone;                     //返回這個(gè)對(duì)象
}

//使用createAnother
var person = {
    name: "Jason",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();     //"hi"

這個(gè)例子中的代碼基于person對(duì)象返回了一個(gè)新對(duì)象——anotherPerson。新對(duì)象不僅具有person的所有屬性和方法,而且還有自己的sayHi()方法。

3.6 寄生組合式繼承

所謂寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承實(shí)例屬性,通過寄生式繼承方式來繼承原型屬性。其基本思路就是:不必為指定子類型的原型二調(diào)用超類型的構(gòu)造函數(shù),我們需要的只是超類型原型的一個(gè)副本而已。本質(zhì)上就是,使用寄生式繼承超類型的原型,然后將結(jié)果指定給子類型的原型。寄生組合式繼承的基本模式如下:

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);   //創(chuàng)建對(duì)象
    prototype.constructor = subType;               //增強(qiáng)對(duì)象
    subType.prototype = prototype                  //指定對(duì)象
}

這個(gè)實(shí)例的inheritPrototype()函數(shù)實(shí)現(xiàn)了寄生組合式繼承的最簡(jiǎn)單形式。這個(gè)函數(shù)接受兩個(gè)參數(shù):子類型構(gòu)造函數(shù)和超類型構(gòu)造函數(shù)。在函數(shù)內(nèi)部,第一步是創(chuàng)建超類型原型的一個(gè)副本。第二步是為創(chuàng)建的副本添加constructor屬性,從而彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor屬性。最后一步,將新創(chuàng)建的對(duì)象(即副本)賦值給子類型的原型。

function object(o){
    function F(){}    //創(chuàng)建個(gè)臨時(shí)構(gòu)造函數(shù)
    F.prototype = o;  //superType.prototype
    return new F();   //返回實(shí)例
}

function inheritPrototype(subType, superType){
    /*  創(chuàng)建對(duì)象
        傳入超類型的原型,通過臨時(shí)函數(shù)進(jìn)行淺復(fù)制,F(xiàn).prototype的指針就指向superType.prototype,在返回new F()    
    */
    var prototype = object(superType.prototype);   
    prototype.constructor = subType;               //增強(qiáng)對(duì)象
    /*  指定對(duì)象
        子類型的原型等于F類型的實(shí)例,當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例會(huì)包含一個(gè)[[prototype]]的指針指向構(gòu)造函數(shù)的原型對(duì)象,所以subType.prototype指向了超類型的原型對(duì)象這樣實(shí)現(xiàn)了繼承,因?yàn)闃?gòu)造函數(shù)F沒有屬性和方法這樣就子類型的原型中就不會(huì)存在超類型構(gòu)造函數(shù)的屬性和方法了。
    */
    subType.prototype = prototype                  //new F();
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "bule", "grenn"];
}

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

function SubType(name, age){
    SuperType.call(this, name);          
 
    this.age = age;
}

inheritPrototype(SubType, SuperType);
//即等價(jià)于:
SubType.prototype = Object.create(Super.Prototype);
SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function(){
    console.log(this.age);
}

var ins1 = new SubType("Jason", 18);

3.7 總結(jié)

這里我們對(duì)六種繼承方式的基本思想,具體實(shí)現(xiàn),優(yōu)缺點(diǎn)做一個(gè)簡(jiǎn)單的總結(jié),鞏固一下我們上面學(xué)到的知識(shí)。
繼承方式:原型鏈繼承
基本思想:利用原型鏈來實(shí)現(xiàn)繼承,超類的一個(gè)實(shí)例作為子類的原型
具體實(shí)現(xiàn):

// 子類
function Sub(){ 
    this.property = ‘Sub Property’;
}
Sub.prototype = new Super();
// 注意這里new Super()生成的超類對(duì)象并沒有constructor屬性,故需添加上
Sub.prototype.constructor = Sub;

優(yōu)缺點(diǎn):
優(yōu)點(diǎn):

簡(jiǎn)單明了,容易實(shí)現(xiàn)

實(shí)例是子類的實(shí)例,實(shí)際上也是父類的一個(gè)實(shí)例

父類新增原型方法/原型屬性,子類都能訪問到

缺點(diǎn):

所有子類的實(shí)例的原型都共享同一個(gè)超類實(shí)例的屬性和方法

在創(chuàng)建子類型的實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)。實(shí)際上,應(yīng)該說是沒有辦法在不影響所有對(duì)象實(shí)例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)

繼承方式:借用構(gòu)造函數(shù)
基本思想:通過使用call、apply方法可以在新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù),用父類的構(gòu)造函數(shù)來增加子類的實(shí)例
具體實(shí)現(xiàn):

// 子類 
function Sub(){ 
    Super.call(this);
    this.property = "Sub Property’;
}

優(yōu)缺點(diǎn):
優(yōu)點(diǎn):

簡(jiǎn)單明了,直接繼承超類構(gòu)造函數(shù)的屬性和方法

可以傳遞參數(shù)

缺點(diǎn):

無法繼承原型鏈上的屬性和方法

實(shí)例只是子類的實(shí)例,不是父類的實(shí)例

繼承方式:組合繼承
基本思想:利用構(gòu)造繼承和原型鏈組合。使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,用借用構(gòu)造函數(shù)模式實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。這樣既通過在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又能保證每個(gè)實(shí)例都有自己的屬性
具體實(shí)現(xiàn):

// 子類
function Sub(){
  Super.call(this);
  this.property = "Sub Property’;
}
Sub.prototype = new Super();
// 注意這里new Super()生成的超類對(duì)象并沒有constructor屬性,故需添加上
Sub.prototype.constructor = Sub;

優(yōu)缺點(diǎn):
優(yōu)點(diǎn):

解決了構(gòu)造函數(shù)的兩個(gè)問題

既是父類實(shí)例,也是子類實(shí)例

缺點(diǎn):

調(diào)用兩次超類型的構(gòu)造函數(shù),導(dǎo)致子類上擁有兩份超類屬性:一份在子類實(shí)例中,一份在子類原型上,且搜索時(shí)實(shí)例中屬性屏蔽了原型中的同名屬性

繼承方式:原型式繼承
基本思想:采用原型式繼承并不需要定義一個(gè)類,傳入?yún)?shù)obj,生成一個(gè)繼承obj對(duì)象的對(duì)象
具體實(shí)現(xiàn):

function object(obj){ 
    function F(){}; 
    F.prototype = obj; 
    return new F();
}

優(yōu)缺點(diǎn):
優(yōu)點(diǎn):

直接通過對(duì)象生成一個(gè)繼承該對(duì)象的對(duì)象

缺點(diǎn):

不是類式繼承,而是原型式繼承,缺少了類的概念

繼承方式:寄生式繼承
基本思想:創(chuàng)建一個(gè)僅僅用于封裝繼承過程的函數(shù),然后在內(nèi)部以某種方式增強(qiáng)對(duì)象,最后返回對(duì)象
具體實(shí)現(xiàn):

function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}
function createSubObj(superInstance){
  var clone = object(superInstance);
  clone.property = "Sub Property’;
  return clone;
}

優(yōu)缺點(diǎn):
優(yōu)點(diǎn):

原型式繼承的一種拓展

缺點(diǎn):

依舊沒有類的概念

繼承方式:寄生組合式繼承
基本思想:通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法,不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),只需要超類型的一個(gè)副本。本質(zhì)上,就是使用寄生式繼承來繼承超類型的原型,然后再將結(jié)果指定給子類型的原型
具體實(shí)現(xiàn):

function inheritPrototype(Super,Sub){
  var superProtoClone = Object.Create(Super.prototype);
  superProtoClone.constructor = Sub;
  Sub.prototype =  superProtoClone;
}
function Sub(){
  Super.call(this);
  Sub.property = "Sub Property’;
}
inheritPrototype(Super,Sub);

優(yōu)缺點(diǎn):
優(yōu)點(diǎn):

完美實(shí)現(xiàn)繼承,解決了組合式繼承帶兩份屬性的問題

缺點(diǎn):

過于繁瑣,故不如組合繼承

四、ES6繼承 4.1 Class關(guān)鍵字

ES6中通過class關(guān)鍵字定義類。

class Parent {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}

// 經(jīng)babel轉(zhuǎn)碼之后,代碼是:
"use strict";

var _createClass = function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }

    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
}();

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var Parent = function () {
    function Parent(name, age) {
        _classCallCheck(this, Parent);

        this.name = name;
        this.age = age;
    }

    _createClass(Parent, [{
        key: "speakSomething",
        value: function speakSomething() {
            console.log("I can speek chinese");
        }
    }]);

    return Parent;
}();

可以看出類的底層還是通過構(gòu)造函數(shù)去創(chuàng)建的。
注意一點(diǎn),通過ES6創(chuàng)建的類,是不允許直接調(diào)用的。即在ES5中,可以直接運(yùn)行構(gòu)造函數(shù)Parent()。但是在ES6中就不行,在轉(zhuǎn)碼的構(gòu)造函數(shù)中有 _classCallCheck(this, Parent);語句,防止通過構(gòu)造函數(shù)直接運(yùn)行。直接在ES6運(yùn)行Parent(),報(bào)錯(cuò)Class constructor Parent cannot be invoked without ‘new’,轉(zhuǎn)碼后報(bào)錯(cuò)Cannot call a class as a function。
轉(zhuǎn)碼中_createClass方法,它調(diào)用Object.defineProperty方法去給新創(chuàng)建的Parent添加各種屬性。defineProperties(Constructor.prototype, protoProps)是給原型添加屬性。如果你有靜態(tài)屬性,會(huì)直接添加到構(gòu)造函數(shù)上defineProperties(Constructor, staticProps)。

4.2 extends繼承

Class可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,這比ES5的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。

class Parent {
    static height = 12
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}
Parent.prototype.color = "yellow"


//定義子類,繼承父類
class Child extends Parent {
    static width = 18
    constructor(name,age){
        super(name,age);
    }
    coding(){
        console.log("I can code JS");
    }
}

var c = new Child("job",30);
c.coding()

轉(zhuǎn)碼之后的代碼變成了這樣

"use strict";

var _createClass = function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }

    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
}();

function _possibleConstructorReturn(self, call) {
    if (!self) {
        throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called");
    }
    return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var Parent = function () {
    function Parent(name, age) {
        _classCallCheck(this, Parent);

        this.name = name;
        this.age = age;
    }

    _createClass(Parent, [{
        key: "speakSomething",
        value: function speakSomething() {
            console.log("I can speek chinese");
        }
    }]);

    return Parent;
}();
Parent.height = 12;  //注意,該方法并不在轉(zhuǎn)碼后的構(gòu)造函數(shù)function Parent中,
Parent.prototype.color = "yellow";

//定義子類,繼承父類

var Child = function (_Parent) {
    _inherits(Child, _Parent);

    function Child(name, age) {
        _classCallCheck(this, Child);

        return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age));
    }

    _createClass(Child, [{
        key: "coding",
        value: function coding() {
            console.log("I can code JS");
        }
    }]);

    return Child;
}(Parent);

Child.width = 18;


var c = new Child("job", 30);
c.coding();

可以看到,構(gòu)造類的方法沒變,只是添加了_inherits核心方法來實(shí)現(xiàn)繼承,我們來重點(diǎn)分析一個(gè)這個(gè)方法做了什么。

首先判斷父類的實(shí)例

然后執(zhí)行

subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
        value: subClass,
        enumerable: false,
        writable: true,
        configurable: true
    }
});

//這段代碼翻譯一下就是
function F(){}
F.prototype = superClass.prototype
subClass.prototype = new F()
subClass.prototype.constructor = subClass

最后,subClass.__proto__ = superClass。

_inherits方法的核心思想,總結(jié)一下就是下面這兩句話:

subClass.prototype.__proto__ = superClass.prototype
subClass.__proto__ = superClass

那為什么這樣一倒騰,它就實(shí)現(xiàn)了繼承了呢?
首先 subClass.prototype.__proto__ = superClass.prototype保證了c instanceof Parent是true,Child的實(shí)例可以訪問到父類的屬性,包括內(nèi)部屬性,以及原型屬性。其次,subClass.__proto__ = superClass,保證了Child.height也能訪問到,也就是靜態(tài)方法。

class Parent {}
class Child extends Parent {}

// for static propertites and methods
alert(Child.__proto__ === Parent); // true

// and the next step is Function.prototype
alert(Parent.__proto__ === Function.prototype); // true

// that"s in addition to the "normal" prototype chain for object methods
alert(Child.prototype.__proto__ === Parent);
在內(nèi)置對(duì)象中沒有靜態(tài)繼承

請(qǐng)注意,內(nèi)置類沒有靜態(tài) [[Prototype]] 引用。例如,Object 具有 Object.defineProperty,Object.keys等方法,但 Array,Date 不會(huì)繼承它們。

Date 和 Object 之間毫無關(guān)聯(lián),他們獨(dú)立存在,不過 Date.prototype 繼承于 Object.prototype,僅此而已。
造成這個(gè)情況是因?yàn)?JavaScript 在設(shè)計(jì)初期沒有考慮使用 class 語法和繼承靜態(tài)方法。

4.3 super關(guān)鍵字

super這個(gè)關(guān)鍵字,既可以當(dāng)函數(shù)使用,也可以當(dāng)對(duì)象使用。在這兩種情況下,它的用法完全不同。

第一種情況,super作為函數(shù)調(diào)用時(shí)代表父類的構(gòu)造函數(shù)。ES6要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super函數(shù)。

子類B的構(gòu)造函數(shù)之中的super()代表調(diào)用父類的構(gòu)造函數(shù),必須執(zhí)行,否則會(huì)報(bào)錯(cuò)。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

注意,super雖然代表了父類A的構(gòu)造函數(shù),但是返回的是子類B的實(shí)例,即super內(nèi)部的this指的是B的實(shí)例,因此super()在這里相當(dāng)于A.prototype.constructor.call(this)。
new.target指向當(dāng)前正在執(zhí)行的函數(shù)??梢钥吹?,在super()執(zhí)行時(shí),它指向的是子類B的構(gòu)造函數(shù),而不是父類A的構(gòu)造函數(shù)。也就是說,super()內(nèi)部的this指向的是B。

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

注意,作為函數(shù)時(shí),super()只能用在子類的構(gòu)造函數(shù)之中,用在其他地方會(huì)報(bào)錯(cuò)。

class A {}

class B extends A {
  m() {
    super(); // 報(bào)錯(cuò)
  }
}

第二種情況,super作為對(duì)象時(shí),在普通方法中,指向父類的原型對(duì)象;在靜態(tài)方法中,指向父類。

class A {
  constructor() {
    this.x = 2;
    this.y = 8;
  }
  p() {
    console.log(this.x);
  },
}
A.prototype.z = 10;

class B extends A {
  constructor() {
    super();
    this.x = 5;
    console.log(super.p());  //5;
  }
  getY() {
    return super.y;
  }
  getZ() {
    return super.z;
  }
}

let b = new B();
b.getY();  //undefined
b.getZ();  //10

在普通方法中,super指向A.prototype,所以super.p()就相當(dāng)于A.prototype.p()。但是這里需要注意兩點(diǎn):

super指向的是父類原型,所以定義在父類實(shí)例上的方法或?qū)傩?,無法通過super調(diào)用。所以在B類的getY()方法中調(diào)用super.y獲取不到值。但是定義在父類原型上的方法就可以獲取到,如getZ()方法中。

ES6規(guī)定,在子類普通方法中通過super調(diào)用父類的方法時(shí),方法內(nèi)部的this指向當(dāng)前子類的實(shí)例。在B類中調(diào)用super.p(),this指向B類實(shí)例,輸出的結(jié)果為5。super.p()實(shí)際上執(zhí)行的是super.p.call(this)。

由于this指向子類實(shí)例,所以如果通過super對(duì)某個(gè)屬性賦值,這時(shí)super就是this,賦值的屬性會(huì)變成子類實(shí)例的屬性。

class A {
  constructor() {
    this.x = 1;
  }
}
class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined,super獲取不到父類的實(shí)例屬性
    console.log(this.x); // 3
  }
}

在靜態(tài)方法中,super作為對(duì)象指向父類,而不是父類的原型。另外,在子類的靜態(tài)方法中通過super調(diào)用父類的方法時(shí),方法內(nèi)部的this指向當(dāng)前的子類,而不是子類的實(shí)例。

class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3;
B.m() // 3

注意,

使用super的時(shí)候,必須顯式指定是作為函數(shù)、還是作為對(duì)象使用,否則會(huì)報(bào)錯(cuò)。

    class A {}
    
    class B extends A {
        constructor() {
            super();
            console.log(super); // 報(bào)錯(cuò)
        }
    }

由于對(duì)象總是繼承其他對(duì)象的,所以可以在任意一個(gè)對(duì)象中,使用super關(guān)鍵字。

    var obj = {
        toString() {
            return "MyObject: " + super.toString();
        }
     };
    
    obj.toString(); // MyObject: [object Object]
在內(nèi)置對(duì)象中沒有靜態(tài)繼承

內(nèi)置類沒有靜態(tài) __proto__引用。例如,Object 具有 Object.defineProperty,Object.keys等方法,但 Array,Date 不會(huì)繼承它們。
Date 和 Object 之間毫無關(guān)聯(lián),他們獨(dú)立存在,不過 Date.prototype 繼承于 Object.prototype,僅此而已。
造成這個(gè)情況是因?yàn)?JavaScript 在設(shè)計(jì)初期沒有考慮使用 class 語法和繼承靜態(tài)方法。

4.4 原生構(gòu)造函數(shù)拓展

原生構(gòu)造函數(shù)是指語言內(nèi)置的構(gòu)造函數(shù),通常用來生成數(shù)據(jù)結(jié)構(gòu)。ECMAScript的原生構(gòu)造函數(shù)大致有下面這些。

Boolean()

Number()

String()

Array()

Date()

Function()

RegExp()

Error()

Object()

以前,原生構(gòu)造函數(shù)是無法繼承的。比如,不能自己定義一個(gè)Array的子類。

function MyArray() {
  Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
  }
});

var colors = new MyArray();
colors[0] = "red";
colors.length  // 0
colors.length = 0;
colors[0]  // "red"

上面這個(gè)例子中定義了一個(gè)繼承Array的MyArray類。但是,我們看到,這個(gè)類的行為與Array完全不一致。
之所以會(huì)發(fā)生這種情況,是因?yàn)樽宇悷o法獲得原生構(gòu)造函數(shù)的內(nèi)部屬性,通過Array.apply()或者分配給原型對(duì)象都不行。原生構(gòu)造函數(shù)會(huì)忽略apply方法傳入的this,也就是說,原生構(gòu)造函數(shù)的this無法綁定,導(dǎo)致拿不到內(nèi)部屬性。
ES5 是先新建子類的實(shí)例對(duì)象this,再將父類的屬性添加到子類上,由于父類的內(nèi)部屬性無法獲取,導(dǎo)致無法繼承原生的構(gòu)造函數(shù)。比如,Array構(gòu)造函數(shù)有一個(gè)內(nèi)部屬性[[DefineOwnProperty]],用來定義新屬性時(shí),更新length屬性,這個(gè)內(nèi)部屬性無法在子類獲取,導(dǎo)致子類的length屬性行為不正常。
ES6 允許繼承原生構(gòu)造函數(shù)定義子類,因?yàn)?ES6 是先新建父類的實(shí)例對(duì)象this,然后再用子類的構(gòu)造函數(shù)修飾this,使得父類的所有行為都可以繼承。下面是一個(gè)繼承Array的例子。

class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined

extends關(guān)鍵字不僅可以用來繼承類,還可以用來繼承原生的構(gòu)造函數(shù)。 Array,Map 等內(nèi)置類也可以擴(kuò)展。

class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined

注意,繼承Object的子類,有一個(gè)行為差異。

class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}
var o = new NewObj({attr: true});
o.attr === true  // false

上面代碼中,NewObj繼承了Object,但是無法通過super方法向父類Object傳參。這是因?yàn)?ES6 改變了Object構(gòu)造函數(shù)的行為,一旦發(fā)現(xiàn)Object方法不是通過new Object()這種形式調(diào)用,ES6 規(guī)定Object構(gòu)造函數(shù)會(huì)忽略參數(shù)。

4.5 Mixin模式的實(shí)現(xiàn)

Mixin 指的是多個(gè)對(duì)象合成一個(gè)新的對(duì)象,新對(duì)象具有各個(gè)組成成員的接口。它的最簡(jiǎn)單實(shí)現(xiàn)如下。

const a = {
  a: "a"
};
const b = {
  b: "b"
};
const c = {...a, ...b}; // {a: "a", b: "b’}

上面代碼中,c對(duì)象是a對(duì)象和b對(duì)象的合成,具有兩者的接口。
下面是一個(gè)更完備的實(shí)現(xiàn),將多個(gè)類的接口“混入”(mix in)另一個(gè)類。

function mix(...mixins) {
  class Mix {
    constructor() {
      for (let mixin of mixins) {
        copyProperties(this, new mixin()); // 拷貝實(shí)例屬性
      }
    }
  }

  for (let mixin of mixins) {
    copyProperties(Mix, mixin); // 拷貝靜態(tài)屬性
    copyProperties(Mix.prototype, mixin.prototype); // 拷貝原型屬性
  }

  return Mix;
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"
      && key !== "prototype"
      && key !== "name"
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

上面代碼的mix函數(shù),可以將多個(gè)對(duì)象合

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109718.html

相關(guān)文章

  • 前端基礎(chǔ)進(jìn)階(九):詳解面向對(duì)象構(gòu)造函數(shù)、原型原型

    摘要:我們通過一個(gè)簡(jiǎn)單的例子與圖示,來了解構(gòu)造函數(shù),實(shí)例與原型三者之間的關(guān)系。而原型對(duì)象的指向構(gòu)造函數(shù)。于是根據(jù)構(gòu)造函數(shù)與原型的特性,我們就可以將在構(gòu)造函數(shù)中,通過聲明的屬性與方法稱為私有變量與方法,它們被當(dāng)前被某一個(gè)實(shí)例對(duì)象所獨(dú)有。 showImg(https://segmentfault.com/img/remote/1460000008593382); 如果要我總結(jié)一下學(xué)習(xí)前端以來我遇...

    Tony_Zby 評(píng)論0 收藏0
  • 詳解javascript的類

    摘要:原文地址詳解的類博主博客地址的個(gè)人博客從當(dāng)初的一個(gè)彈窗語言,一步步發(fā)展成為現(xiàn)在前后端通吃的龐然大物。那么,的類又該怎么定義呢在面向?qū)ο缶幊讨?,類是?duì)象的模板,定義了同一組對(duì)象又稱實(shí)例共有的屬性和方法。這個(gè)等同于的屬性現(xiàn)已棄用。。 前言 生活有度,人生添壽。 原文地址:詳解javascript的類 博主博客地址:Damonare的個(gè)人博客 ??Javascript從當(dāng)初的一個(gè)彈窗語言,一...

    hufeng 評(píng)論0 收藏0
  • 詳解javascript的類

    摘要:原文地址詳解的類博主博客地址的個(gè)人博客從當(dāng)初的一個(gè)彈窗語言,一步步發(fā)展成為現(xiàn)在前后端通吃的龐然大物。那么,的類又該怎么定義呢在面向?qū)ο缶幊讨?,類是?duì)象的模板,定義了同一組對(duì)象又稱實(shí)例共有的屬性和方法。這個(gè)等同于的屬性現(xiàn)已棄用。。 前言 生活有度,人生添壽。 原文地址:詳解javascript的類 博主博客地址:Damonare的個(gè)人博客 ??Javascript從當(dāng)初的一個(gè)彈窗語言,一...

    marek 評(píng)論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.32 - 七夕將至,你的“對(duì)象”還好嗎?

    摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對(duì)象。對(duì)象就是數(shù)據(jù),對(duì)象本身不包含方法。類是相似對(duì)象的描述,稱為類的定義,是該類對(duì)象的藍(lán)圖或原型。在中,對(duì)象通過對(duì)類的實(shí)體化形成的對(duì)象。一類的對(duì)象抽取出來。注意中,對(duì)象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...

    李昌杰 評(píng)論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.32 - 七夕將至,你的“對(duì)象”還好嗎?

    摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對(duì)象。對(duì)象就是數(shù)據(jù),對(duì)象本身不包含方法。類是相似對(duì)象的描述,稱為類的定義,是該類對(duì)象的藍(lán)圖或原型。在中,對(duì)象通過對(duì)類的實(shí)體化形成的對(duì)象。一類的對(duì)象抽取出來。注意中,對(duì)象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...

    Lyux 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<