国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript知識(shí)點(diǎn)總結(jié)(三)

arashicage / 1428人閱讀

摘要:對(duì)象對(duì)象創(chuàng)建繼承早期創(chuàng)建對(duì)象的方式對(duì)象字面量創(chuàng)建方式亦可換成因?yàn)橹赶虍?dāng)前對(duì)象的兩種屬性數(shù)據(jù)屬性和訪問(wèn)器屬性數(shù)據(jù)屬性數(shù)據(jù)屬性包含個(gè)數(shù)據(jù)值的位置這個(gè)位置可以讀取和寫(xiě)入值名稱描述表示能否通過(guò)刪除屬性而重新定義屬性能否修改屬性的特性或者能否把屬性修

JavaScript OOP, 對(duì)象, 對(duì)象創(chuàng)建, 繼承
//早期創(chuàng)建對(duì)象的方式
var jonslike = new Object();
jonslike.name = "jon";
jonslike.like = "wow";

jonslike.saylike = function(){
    console.log(this.name);
};

//對(duì)象字面量創(chuàng)建方式
var jonslike = {
    name : "jon",
    like : "wow",
    saylike : function(){
        console.log(jonslike.like); //亦可換成this, 因?yàn)橹赶虍?dāng)前對(duì)象
        console.log(this.like); //wow
    }
};

ES的兩種屬性, 數(shù)據(jù)屬性訪問(wèn)器屬性

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

數(shù)據(jù)屬性包含1個(gè)數(shù)據(jù)值的位置, 這個(gè)位置可以讀取和寫(xiě)入值

名稱 描述
[[ Configurable ]] 表示能否通過(guò)delete刪除屬性而重新定義屬性, 能否修改屬性的特性, 或者能否把屬性修改為訪問(wèn)器屬性, 默認(rèn)值true
[[ Enumerable ]] 表示能否通過(guò)for-in循環(huán)返回屬性, 默認(rèn)值true
[[ Writable ]] 表示能否修改屬性的值, 默認(rèn)值true
[[ Value ]] 包含這個(gè)屬性的數(shù)據(jù)值. 讀取屬性值的時(shí)候, 從這個(gè)位置讀; 寫(xiě)入屬性值的時(shí)候, 把新值保存在這個(gè)位置, 默認(rèn)值undefined
var person = {
    name : Jon,  //value值變成Jon
};
修改數(shù)據(jù)屬性的默認(rèn)特性 : 使用Object.defineProperty()方法.

接收3個(gè)參數(shù), 屬性所在的對(duì)象, 屬性的名字, 一個(gè)描述符對(duì)象

描述符對(duì)象的屬性必須是 : configurable, enumerable, writable, value

//設(shè)置為不可寫(xiě) :
var person = {};
person.name = "fire";
    Object.defineProperty(person, "name", {
        writable : false, //設(shè)置為只讀, 不可寫(xiě)
        value : "jon"
    });

person.name = "mark";
alert(person.name); //輸出還是jon
//設(shè)置為不可配置 :
var person = {};
Object.defineProperty(person, "name" , {
    configurable : false, //設(shè)置為不可配置
    value : "jon",
});

person.name = "mark"; //無(wú)效!
delete person.name; //刪除無(wú)效!
alert(person.name); //依然能輸出jon

//NOTE : 配置configurable為false時(shí), 其他3各特性也有相應(yīng)的限制
訪問(wèn)器屬性

訪問(wèn)器屬性不包括數(shù)據(jù)值;

包含一對(duì)getter 與 setter 函數(shù) (非必須)

讀取訪問(wèn)器屬性時(shí), 會(huì)調(diào)用getter函數(shù),該函數(shù)負(fù)責(zé)返回有效的值;

寫(xiě)入訪問(wèn)器屬性時(shí), 會(huì)調(diào)用setter函數(shù),該函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù);

名稱 描述
[[ Configurable ]] 表示能否通過(guò)delete刪除屬性而重新定義屬性, 能否修改屬性的特性, 或者能否把屬性修改為訪問(wèn)器屬性, 默認(rèn)值true
[[ Enumerable ]] 表示能否通過(guò)for-in循環(huán)返回屬性, 默認(rèn)值true
[[ Get ]] 在讀取屬性時(shí)調(diào)用的函數(shù), 默認(rèn)值undefined
[[ Set ]] 在寫(xiě)入屬性時(shí)調(diào)用的函數(shù), 默認(rèn)值undefined

定義訪問(wèn)器屬性 : 使用Object.defineProperty()方法.

//創(chuàng)建book對(duì)象,
var book = {
  //定義兩個(gè)默認(rèn)的屬性, _year和edition, 下劃線定義的屬性表示只能通過(guò)對(duì)象方法訪問(wèn)
    _year : 2004,
    edition : 1
};

Object.defineProperty(book, "year", {
    get : function(){
        return this._year;
    },
    set : function(){
        if(newValue > 2004){
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    },
});

book.year = 2005;
alert(book.edition);  //2

定義多個(gè)屬性 : defineProperties()

可以通過(guò)描述符一次過(guò)定義多個(gè)屬性

接收兩個(gè)參數(shù) :

要添加和修改其屬性的對(duì)象

第二個(gè)對(duì)象的屬性與第一個(gè)對(duì)象中要添加或修改的屬性一一對(duì)應(yīng).

var book = {};
Object.defineProperties(book, {
    _year : {
        value : 2004,
    },
    edition : {
        value : 1,
    },
    year : {
        get : funciton(){
            return this._year,
        },
        set : function(){
            if(newValue > 2004){
                this._year = newValue,
                this.edition += newValue - 2004,
                }
            }
        }
});

讀取屬性的特性 : 使用Object.getOwnPropertyDescriptor();

可以取得給定屬性的描述符

接收兩個(gè)參數(shù) :

屬性所在的對(duì)象, 要讀取其描述符的屬性名稱

返回值 : 一個(gè)對(duì)象, 如果返回的對(duì)象是訪問(wèn)器屬性, 則這個(gè)對(duì)象的屬性有configurable, enumerable, get, set; 如果返回的對(duì)象是數(shù)據(jù)屬性, 則這個(gè)對(duì)象的屬性有configurable, enumerable, writable, value

var book = {};
Object.defineProperties(book, {
    _year : {
        value : 2004,
    },
    edition : {
        value : 1,
    },
    year : {
        get : funciton(){
            return this._year,
        },
        set : function(){
            if(newValue > 2004){
                this._year = newValue,
                this.edition += newValue - 2004,
                }
            }
        }
});

var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
//數(shù)據(jù)屬性
alert(descriptor.value);  //2004(最初的值)
alert(descriptor.configurable);  //false(最初的值)
alert(typeof descriptor.get);  // undefined

var descriptor = Object.getOwnPropertyDescriptor(book, year);
//訪問(wèn)器屬性
alert(descriptor.value);  // undefined(訪問(wèn)器沒(méi)有value屬性)
alert(descriptor.enumerable);  // false
alert(typeof descriptor.get);  // function(一個(gè)指向getter的指針)
創(chuàng)建對(duì)象 工廠模式

工廠模式抽象了創(chuàng)建具體對(duì)象的過(guò)程;

該模式?jīng)]有解決對(duì)象的識(shí)別問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)

function createPerson(name, age, job){
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayJob = function(){
      console.log(this.job);
  };
  return o;
}

var p1 = createPerson("Jon",25,"FrontEnd Developer");
var p2 = createPerson("Mark",24,"DBA");

p1.sayJob(); //FrontEnd Developer
p2.sayJob(); //DBA
構(gòu)造函數(shù)模式
function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayJob = function(){
      console.log(this.job);
  };
}

//使用new操作符創(chuàng)建Person的新實(shí)例
/*
調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下步驟 : 
1. 創(chuàng)建一個(gè)新對(duì)象;
2.將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象)
3.執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
4.返回新對(duì)象
*/
var p1 = new Person("Jon", 25, "FrontEnd Developer");
var p2 = new Person("Mark", 24, "DBA");

p1.sayJob(); //FrontEnd Developer
p2.sayJob(); //DBA

//新對(duì)象具有一個(gè)constructor(構(gòu)造函數(shù))屬性, 指向原創(chuàng)建的構(gòu)造函數(shù)(即Person)
console.log(p1.constructor == Person); //true
console.log(p2.constructor == Person); //true

//使用instanceof操作符檢測(cè)對(duì)象類型會(huì)更可靠
console.log(p1 instanceof Object); //Object是終極父類, 所以返回true
console.log(p1 instanceof Person); //p1是Person構(gòu)造函數(shù)的實(shí)例
console.log(p2 instanceof Object); //Object是終極父類, 所以返回true
console.log(p2 instanceof Person); //p2是Person構(gòu)造函數(shù)的實(shí)例

//構(gòu)造函數(shù)本身也是函數(shù), 所以可以當(dāng)做普通函數(shù)來(lái)調(diào)用(不使用new操作符調(diào)用)
Person("Martin", 27, "PHPer"); //添加到window對(duì)象(全局作用域中)
window.sayJob(); //PHPer

//在另一個(gè)對(duì)象的作用域調(diào)用(使用call()或者apply())
var o1 = new Object();
Person.call(o1, "Kiki", 23, "Singer");
o1.sayJob(); //Singer
原型模式

每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性, 是一個(gè)指針, 指向一個(gè)對(duì)象

對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法;

prototype就是通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的對(duì)象

使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法;

即 :?

不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息, 而是可以將這些信息直接添加到原型對(duì)象中;

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "f2e";
Person.prototype.sayName = function(){
  alert(this.name);
};

var p1 = new Person();
p1.sayName();  //jon

var p2 = new Person();
p2.sayName();  //jon

alert(p1.sayName == p2.sayName); //true
原型對(duì)象 :

無(wú)論何時(shí), 只要?jiǎng)?chuàng)建了一個(gè)新函數(shù), 就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性, 該屬性指向函數(shù)的 原型對(duì)象

即 : (新函數(shù)會(huì)創(chuàng)建一個(gè)prototype屬性指向原型對(duì)象)

默認(rèn)情況下, 所有原型對(duì)象會(huì)自動(dòng)獲得一個(gè)constructor構(gòu)造函數(shù)屬性, 該屬性包含指向prototype屬性所在函數(shù)的指針

即 : (所有原型對(duì)象獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,包含指向prototype屬性所在函數(shù)的指針)

function Person(){};  //這是(空)構(gòu)造函數(shù),會(huì)有一個(gè)prototype屬性,指向(下面的)原型對(duì)象

//Person.prototype : 這是(構(gòu)造函數(shù)的)原型對(duì)象, 會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性, 包含一個(gè)指向prototype屬性所在函數(shù)的指針,在這里即上面的Person()函數(shù); 即Person.prototype.constructor指向(上面的)Person()函數(shù)

//下面這些是(構(gòu)造函數(shù)的)原型對(duì)象的自定義屬性s
Person.prototype.name = "Jon"; 
Person.prototype.age = 25;
Person.prototype.job = "f2e";
Person.prototype.sayName = function(){
  alert(this.name);
};

//這是實(shí)例,內(nèi)部包含一個(gè)指針(內(nèi)部屬性) [[Prototype]], 指向構(gòu)造函數(shù)的原型對(duì)象(即上面的Person.prototype)
var p1 = new Person();
p1.sayName();  //jon

var p2 = new Person();
p2.sayName();  //jon

alert(p1.sayName == p2.sayName); //true

isPrototypeOf() : 確定是否為給定實(shí)例的原型

getPrototypeOf() [ES5] : 跟上面的功能一樣, 并且這方法可以返回原型對(duì)象給定屬性的值

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

var p1 = new Person();
p1.sayJob(); //FrontEnd Developer

//測(cè)試Person是否為p1的原型
console.log(Person.prototype.isPrototypeOf(p1)); //true

//如果支持ES5的getPrototypeOf()
if(Object.getPrototypeOf){
  //測(cè)試Person是否為p1的原型
  console.log(Object.getPrototypeOf(p1) == Person.prototype); //true
  //輸出p1的name屬性的值
  console.log(Object.getPrototypeOf(p1).name); //Jon
}
注意實(shí)例中添加的屬性如果和原型的屬性同名, 則會(huì)覆蓋原型的屬性值
function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

var p1 = new Person();
p1.job = "DBA";
p1.sayJob(); //DBA

delete操作符可以刪除實(shí)例的屬性

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

var p1 = new Person();
p1.job = "DBA";
p1.sayJob(); //返回自身添加的屬性, DBA

delete p1.job; //刪除p1的job屬性
p1.sayJob(); //返回原型的屬性, FrontEnd Developer

hasOwnProperty()可以檢查一個(gè)屬性是位于實(shí)例還是原型中, 屬于實(shí)例會(huì)返回true

in操作符會(huì)在對(duì)象能訪問(wèn)給定屬性時(shí)返回true,無(wú)論是實(shí)例還是原型 : (就是有這個(gè)屬性就會(huì)返回true)

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

var p1 = new Person();

console.log(p1.hasOwnProperty("name")); //實(shí)例中沒(méi)有自己定義的name屬性, 返回false
console.log("name" in p1); //true, p1中有name屬性(從Person中的name繼承而來(lái)的)

p1.name = "Mark"; //自己定義一個(gè)實(shí)例中的name屬性, 覆蓋原型繼承而來(lái)的name
console.log(p1.hasOwnProperty("name")); //實(shí)例中有自己定義的name屬性(Mark), 返回true
console.log("name" in p1); //true, p1中有name屬性(從Person中的name繼承而來(lái)的)

delete p1.name; //刪除p1實(shí)例的name屬性
console.log(p1.hasOwnProperty("name")); //p1的name屬性已經(jīng)被delete操作符刪除, 所以現(xiàn)在又沒(méi)了自身實(shí)例的name屬性, 所以返回false
console.log("name" in p1); //true, p1中有name屬性(從Person中的name繼承而來(lái)的)

可以同時(shí)使用hasOwnProperty()in操作符, 以確定給定的屬性是位于實(shí)例還是原型中 :

in操作符只要能訪問(wèn)給定屬性就返回true, hasOwnProperty()只在屬性屬于實(shí)例才返回true,

因此只要in操作符返回truehasOwnProperty()返回false, 就能確定給定的屬性是原型的屬性

//obj表示要傳入的實(shí)例名稱, name表示要測(cè)試的實(shí)例屬性
function hasPrototypeProperty(obj, name){
  //如果傳入的實(shí)例屬性name不屬于該實(shí)例obj(取反), 并且(&&)實(shí)例obj中有該傳入的屬性name, 則返回
  return !obj.hasOwnProperty(name) && (name in obj);
}

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

var p1 = new Person(hasPrototypeProperty(p1, "name"));
console.log(hasPrototypeProperty(p1, "job")); //p1中還沒(méi)有定義實(shí)例的job屬性, 只使用了原型繼承而來(lái)的job屬性, 所以返回true (hasOwnProperty()返回!false(取反false, 即true), in操作符返回true)

p1.job = "DBA"; //p1定義自身的實(shí)例屬性job
console.log(hasPrototypeProperty(p1, "job")); //false (!hasOwnProperty(job)為 !true,即false, in返回true)

使用for-in返回能通過(guò)對(duì)象訪問(wèn)的, 可枚舉的屬性(包括原型內(nèi)和實(shí)例內(nèi)的) :

var o = {
  name : "Jon",
  age : 25,
  saySth : function(){}
}

for(var prop in o){
  if(prop){
      console.log(prop);
  }
}

//name, age, saySth

Object.keys() [ES5]可獲得所有可枚舉的屬性 :

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

//獲得原型中所有可枚舉的屬性
var protoKeys = Object.keys(Person.prototype);
console.log(protoKeys); //"name", "age", "job", "sayJob"

var p1 = new Person;
p1.name = "Mark";
p1.nickname = "MM";
p1.age = 24;
p1.fakeAge = 21;
p1.job = "DBA";
p1.sayJob();

//如果通過(guò)實(shí)例調(diào)用, 則會(huì)得到該實(shí)例中所有可枚舉的屬性
var keys = Object.keys(p1);
console.log(keys); //"name", "nickname", "age", "fakeAge", "job"

Object.getOwnPropertyNames()可以得到所有無(wú)論是否可枚舉的屬性

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

//獲得原型中所有屬性(無(wú)論是否可枚舉)
var protoKeys = Object.getOwnPropertyNames(Person.prototype);
console.log(protoKeys); //"constructor", "name", "age", "job", "sayJob"

Object.keys()Object.getOwnPropertyNames()都可以替代for-in循環(huán) (IE9+, ...)

使用對(duì)象字面量來(lái)創(chuàng)建新對(duì)象

function Person(){}

//這種方式其實(shí)已經(jīng)重寫(xiě)了默認(rèn)的prototype對(duì)象, 此時(shí)constructor屬性已經(jīng)不再指向Person了, 而是指向了Object
Person.prototype = {
  name : "Jon",
  age : 25,
  job : "FrontEnd Developer",
  sayJob : function(){
  console.log(this.job);
  }
};

//所以此時(shí)雖然instanceof操作符還能返回正確的結(jié)果, 但constructor已經(jīng)無(wú)法確定對(duì)象的類型了
var f1 = new Person();
console.log(f1 instanceof Person); //true
console.log(f1 instanceof Object); //true
console.log(f1.constructor == Person); //false
console.log(f1.constructor == Object); //true


//如果constructor的值很重要, 可以像這樣把它設(shè)置回適當(dāng)?shù)闹?//(修改上面的Person.prototype)
Person.prototype = {
  constructor : Person, //顯式的把constructor設(shè)置為Person
  name : "Jon",
  age : 25,
  job : "FrontEnd Developer",
  sayJob : function(){
  console.log(this.job);
  }
};

//如果像上面一樣把constructor的值顯式的設(shè)置, 那么它會(huì)變成可枚舉, 即[[Enumerable]]的值會(huì)變?yōu)閠rue,  如果要把它設(shè)置回不可枚舉, 可以使用下面的ES5提供的新方法 : 
//重寫(xiě)整個(gè)示例
function Person(){}

Person.prototype = {
  name : "Jon",
  age : 25,
  job : "FrontEnd Developer",
  sayJob : function(){
  console.log(this.job);
  }
};

//重設(shè)構(gòu)造函數(shù)[ES5 only]
Object.defineProperty(Person.prototype, "constructor", {
  enumerable : false,
  value : Person
});

原型的動(dòng)態(tài)性

原型中查找值的方法是一次搜索, 所謂動(dòng)態(tài)性就是在原型對(duì)象上所有的修改都能立即從實(shí)例上反應(yīng)出來(lái), 即使是 先創(chuàng)建實(shí)例, 后修改原型 也是如此

function Person(){};

Person.prototype.name = "Jon";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Developer";
Person.prototype.sayJob = function(){
  console.log(this.job);
};

//創(chuàng)建原型實(shí)例
var p1 = new Person();

//創(chuàng)建實(shí)例后再創(chuàng)建原型方法
Person.prototype.sayAge = function(){
  console.log(this.age);
}

//調(diào)用后創(chuàng)建的原型方法
p1.sayAge(); //照樣能工作! 輸出25
//但不能在創(chuàng)建原型實(shí)例后, 重寫(xiě)整個(gè)原型對(duì)象
function Person(){}

//創(chuàng)建原型實(shí)例
var p1 = new Person();

//此時(shí)再定義Person的原型對(duì)象
Person.prototype = {
  constructor : Person,
  name : "Jon",
  job : "FrontEnd Developer",
  sayJob : function(){
      console.log(this.job);
  }
};

//記住, 實(shí)例的指針[[ prototype ]]僅指向原型, 而不指向構(gòu)造函數(shù)
p1.sayJob(); //出錯(cuò)! Uncaught TypeError: p1.sayJob is not a function

原生對(duì)象的原型

所有原生的引用類型(Object, Array, String, etc...), 都是使用這種原型模式創(chuàng)建的, 都在其構(gòu)造函數(shù)上定義了方法

通過(guò)原生對(duì)象的原型, 不僅可以取得所有默認(rèn)方法的引用, 而且也可以定義新方法. 可以像修改自定義對(duì)象的原型一樣修改原生對(duì)象的原型: 即可以隨時(shí)添加方法(但不推薦) :

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

//為原生引用類型String添加方法(不推薦) : 
String.prototype.startsWith = function(text){
  return this.indexOf(text) == 0;
}

var s1 = "Hi Jon";
console.log(s1.startsWith("Hi")); //true

原型模式的問(wèn)題 :

省略了為構(gòu)造函數(shù)初始化參數(shù)的環(huán)節(jié), 導(dǎo)致所有新建的實(shí)例都會(huì)取得相同的默認(rèn)值

最大的問(wèn)題是其共享的本性所導(dǎo)致的, 對(duì)于引用類型值的屬性來(lái)說(shuō)問(wèn)題非常突出 :

function Person(){}

Person.prototype = {
  constructor : Person,
  name : "Jon",
  job : "FrontEnd Developer",
  friends : ["Lucy","Jeniffer"],
  sayJob : function(){
      console.log(this.job);
  }
};

var p1 = new Person();
var p2 = new Person();

p1.friends.push("Quinene");

console.log(p1.friends); //"Lucy", "Jeniffer", "Quinene"
console.log(p2.friends); //"Lucy", "Jeniffer", "Quinene"
console.log(p1.friends === p2.friends); //true
組合使用構(gòu)造函數(shù)模式和原型模式(最常用)

構(gòu)造函數(shù)模式用于定義實(shí)例屬性, 原型模式用于定義方法和共享的屬性 :

結(jié)果每個(gè)實(shí)例都有自己的一份實(shí)例屬性的副本, 但同時(shí)又共享著對(duì)方法的引用,最大限度的節(jié)省了內(nèi)存:

這種模式還支持向構(gòu)造函數(shù)傳參 :

function Person(name, age, job){
  //定義實(shí)例屬性(將來(lái)創(chuàng)建實(shí)例時(shí)不會(huì)相同的屬性s)
  this.name = name;
  this.age = age;
  this. job = job;
  this. friends = ["Mark", "Martin"];
}

Person.prototype = {
  //構(gòu)造函數(shù)屬性指回Person
  cosntructor : Person,
  //定義方法
  sayJob : function(){
    console.log(this.job);
  },
  //定義共享屬性
  country : "China"
};

//創(chuàng)建實(shí)例
var p1 = new Person("Jon", 25, "FrontEnd Developer");
var p2 = new Person("Percy", 26, "DBA");

//為實(shí)例p1的friends屬性添加值
p1.friends.push("Jeniffer");

console.log(p1.friends); //"Mark", "Martin", "Jeniffer"
console.log(p2.friends); //"Mark", "Martin"
console.log(p1.friends == p2.friends); //false
console.log(p1.sayJob == p2.sayJob); //true
console.log(p1.country == p2.country); //true
動(dòng)態(tài)原型模式

動(dòng)態(tài)原型模式把所有信息都封裝在構(gòu)造函數(shù)中, 而通過(guò)在構(gòu)造函數(shù)中初始化原型(僅在必要的情況下), 又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn) :?

即 可以通過(guò)檢查某個(gè)應(yīng)該存在的方法是否有效, 來(lái)決定是否需要初始化原型

function Person(name, age, job){

  //屬性
  this.name = name;
  this.age = age;
  this.job = job;
  
  //方法
  if(typeof this.sayJob != "function"){
      Person.prototype.sayJob = function(){
        console.log(this.job);
    }
  }
}

var p1 = new Person("Jon", 25, "F2E");
p1.sayJob(); //F2E

這里只在sayJob()方法不存在的情況下, 才會(huì)將它添加到原型中.

這段代碼只會(huì)在初次調(diào)用構(gòu)造函數(shù)時(shí)才會(huì)執(zhí)行.

這里對(duì)原型所做的修改, 也會(huì)立即在所有實(shí)例中得到反映.

if語(yǔ)句檢查的可以是初始化之后應(yīng)該存在的任何屬性和方法—— 不必用一大堆if語(yǔ)句判斷每個(gè)屬性的方法,只要其中檢查一個(gè)即可;

這種模式創(chuàng)建的對(duì)象可以用instanceof操作符確定它的類型

寄生構(gòu)造函數(shù)模式

基本思路是創(chuàng)建一個(gè)函數(shù), 這個(gè)函數(shù)作用僅僅是封裝創(chuàng)建對(duì)象的代碼, 然后再返回新創(chuàng)建的對(duì)象.

function Person(name, age, job){
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayJob = function(){
    console.log(this.name);
  };
  return o;
}

var p1 = new Person("Jon", 25, "F2E");
p1.sayJob(); //F2E

Person函數(shù)創(chuàng)建了一個(gè)新對(duì)象o, 并以相應(yīng)的屬性和方法初始化該對(duì)象, 然后把它返回.

除了使用new操作符并把使用的包裝函數(shù)叫做構(gòu)造函數(shù)外, 這個(gè)模式跟工廠模式其實(shí)是一樣的.

構(gòu)造函數(shù)在不返回值的情況下, 默認(rèn)會(huì)返回新對(duì)象的實(shí)例, 而通過(guò)在構(gòu)造函數(shù)的末尾添加一個(gè)return語(yǔ)句, 可以重寫(xiě)調(diào)用構(gòu)造函數(shù)時(shí)返回的值.

這種模式在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù).假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組,

因?yàn)椴荒苤苯有薷腁rray構(gòu)造函數(shù), 因此可以使用這種模式 :

function SpecialArray(){
  //創(chuàng)建一個(gè)數(shù)組用于接收傳入的值
  var values = new Array();
  //然后使用push方法(用構(gòu)造函數(shù)接收到的所有參數(shù))初始化了數(shù)組的值;
  values.push.apply(values, arguments);
  //給數(shù)組實(shí)例添加了一個(gè)toPipedString()方法, 該方法返回以短橫線分割的數(shù)組值;
  values.toPipedString = function(){
      return this.join("-");
  };
  //將數(shù)組以函數(shù)值的形式返回.
  return values;
}

var colorsArr = new SpecialArray("red", "blue", "purple");
console.log(colorsArr.toPipedString()); //red-blue-purple
//關(guān)于該模式 : 首先, 返回的對(duì)象與構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型屬性之間沒(méi)有關(guān)系;也就是說(shuō), 構(gòu)造函數(shù)返回的對(duì)象與在構(gòu)造函數(shù)外部創(chuàng)建的對(duì)象沒(méi)有什么不同.為此不能依賴instance操作符來(lái)確定對(duì)象類型.
console.log(colorsArr instanceof SpecialArray); //false
穩(wěn)妥構(gòu)造函數(shù)模式

穩(wěn)妥對(duì)象 : 沒(méi)有公共屬性, 其方法也不引用this的對(duì)象.

適合在安全的環(huán)境中(禁止使用this和new), 或者在防止數(shù)據(jù)被其他應(yīng)用程序改動(dòng)時(shí)使用

穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式, 但有兩點(diǎn)不同 :?

一是新創(chuàng)建對(duì)象的實(shí)力方法不引用this,

二是不適用new操作符調(diào)用構(gòu)造函數(shù) :

function Person(name, age, job){
  //創(chuàng)建要返回的對(duì)象
  var o = new Object();
  
  //可以在這里定義私有變量和方法
  
  //添加方法
  o.sayJob = function(){
      console.log(job);
  }
  
  //返回對(duì)象
  return o;
}

/*這種方式創(chuàng)建的對(duì)象中, 除了使用sayJob()方法外, 沒(méi)有其他辦法訪問(wèn)job的值*/

//使用穩(wěn)妥的Person構(gòu)造函數(shù)
var p1 = new Person("Jon", 25, "FrontEnd Developer");
p1.sayJob(); //FrontEnd Developer
console.log(p1.job); //嘗試直接訪問(wèn)job屬性會(huì)返回undefined
繼承

許多OO語(yǔ)言都支持兩種繼承方式 :?

接口繼承 : 只繼承方法簽名

實(shí)現(xiàn)繼承 : 繼承實(shí)際的方法

由于函數(shù)沒(méi)有簽名, 在ES中無(wú)法實(shí)現(xiàn)接口繼承.ES只支持實(shí)現(xiàn)繼承, 而且其 實(shí)現(xiàn)繼承 主要是依靠原型鏈實(shí)現(xiàn)的.

方法簽名由方法名稱和一個(gè)參數(shù)列表(方法的參數(shù)的順序和類型)組成。

方法簽名應(yīng)該如下所示,相應(yīng)的可變參數(shù)分別使用String和Exception聲明:

Log.log(String message, Exception e, Object... objects) {...}

原型鏈

利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法.

簡(jiǎn)單回顧下構(gòu)造函數(shù), 原型, 實(shí)例的關(guān)系 :?

每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象( prototype ), 原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針( constructor ), 而每個(gè)實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針( [[ prototype ]], __proto__ )

那么,假如我們讓原型對(duì)象(prototype)等于另一個(gè)類型的實(shí)例, 那么此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針.

相應(yīng)地, 另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針.

假如另一個(gè)原型又是另一個(gè)原型的實(shí)例, 那么上述關(guān)系依然成立, 如此層層遞進(jìn), 就構(gòu)成了實(shí)力與原型的鏈條, 這就是所謂的原型鏈的概念.

實(shí)現(xiàn)原型鏈的基本模式 :

/*定義兩個(gè)類型, SuperType和SubType*/
function SuperType(){
  //SuperType自己的屬性
  this.property = true;
}

SuperType.prototype.getSuperValue = function(){
  //SuperType自己的方法
  return this.property;
}

function SubType(){
  //SubType自己的屬性
  this.subproperty = false;
}

/*SupType通過(guò)創(chuàng)建SuperType()的實(shí)例繼承了SuperType, 并賦值給SubType.prototype, 即SubType的原型對(duì)象實(shí)現(xiàn)的本質(zhì)是重寫(xiě)原型對(duì)象, 代之以一個(gè)新類型的實(shí)例.
換句話說(shuō), 原來(lái)存在于SuperType的實(shí)例中的所有屬性和方法, 現(xiàn)在也存在于SubType.prototype中了
*/
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){
  //添加SubType自己的方法, 這樣就在繼承了SuperType的屬性和方法的基礎(chǔ)上又添加了一個(gè)新方法
  return this.subproperty;
}

//創(chuàng)建一個(gè)新實(shí)例
var instance = new SubType();
console.log(instance.getSuperValue()); //true

//測(cè)試是否為Object, SuperType, SubType的實(shí)例
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)系如圖所示 :?

最終結(jié)果 :

instance實(shí)例指向SubType的原型, SubType的原型又指向SuperType的原型.

getSuperValue()方法仍然還在SuperType.prototype中, 但property則位于SubType.prototype中.

這是因?yàn)? property是一個(gè)實(shí)例屬性,而getSuperType()則是一個(gè)原型方法

既然SubType.prototype現(xiàn)在是SuperType的實(shí)例, 那么prototype當(dāng)然就位于該實(shí)例中了.

要注意,實(shí)例的 instance.constructor現(xiàn)在指向的是SuperType, 這是因?yàn)?b>SubType的原型現(xiàn)在指向了另一個(gè)對(duì)象—— SuperType的原型.

而這個(gè)原型對(duì)象的constructor屬性指向的是SuperType.

別忘記了默認(rèn)的原型 :

所有引用類型默認(rèn)都繼承了Object, 而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的.

要記住, 所有函數(shù)的默認(rèn)原型都是Object的實(shí)例, 因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針指向Object.prototype.

這也正是所有自定義類型都會(huì)繼承toString(), valueOf()的根本原因.

所以, 上面例子展示的原型鏈應(yīng)該還包含另一個(gè)繼承層次 : (完整的原型鏈如下)

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

使用instanceof 操作符, 測(cè)試實(shí)例和原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù), 如果存在就會(huì)返回true

使用isPrototypeOf() 方法, 只要是原型鏈中出現(xiàn)過(guò)的原型, 都可以說(shuō)是該原型鏈所派生的實(shí)例的原型,因此該方法會(huì)返回true

//上面第一段代碼的最后片段 : 
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
謹(jǐn)慎的定義方法( 子類型重寫(xiě)超類型已存在的方法 or 子類型添加超類型本身不具有的方法 ) :

給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后 :

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

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

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

//從SuperType繼承
SubType.prototype = new SuperType();

//SubType自己的新方法
SubType.prototype.getSubValue = function(){
  return this.subproperty;
}

//SubType繼承的父類方法getSuperValue()被重寫(xiě), 但只會(huì)重寫(xiě)SubType自身的getSuperValue(), 不會(huì)影響上一級(jí)父類原來(lái)的方法, 即如果調(diào)用的是SuperType的getSuperValue()方法的話還是會(huì)返回原來(lái)的true.
SubType.prototype.getSuperValue = function(){
  return false;
}

//創(chuàng)建實(shí)例
var ins1 = new SubType();
var ins2 = new SuperType();

console.log(ins1.getSuperValue()); //false, 重寫(xiě)的方法
console.log(ins2.getSuperValue()); //true, SubType重寫(xiě)getSuperValue()方法并不會(huì)影響父類原有的方法
通過(guò)原型鏈實(shí)現(xiàn)繼承時(shí), 不能使用對(duì)象字面量創(chuàng)建原型方法, 因?yàn)檫@樣會(huì)重寫(xiě)原型鏈 :
function SuperType(){
  this.property = true;
}

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

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

//從SuperType繼承
SubType.prototype = new SuperType();

/*剛剛把SuperType的實(shí)例賦值給SubType的原型??, 又使用對(duì)象字面量??把原型替換 SubType.prototype = {...}, 所以現(xiàn)在SubType的原型包含的是 一個(gè)屬于Object的實(shí)例而不是SuperType的, 原先的原型鏈已經(jīng)被切斷, SubType與SuperType已經(jīng)沒(méi)有任何關(guān)系了*/

//使用對(duì)象字面量把 原型替換
SubType.prototype = {
  getSubValue : function(){
      return this.subproperty;
  },
  someOtherMethod : function(){
      return false;
  }
}

var ins1 = new SubType();
console.log(ins1.getSuperValue()); //Uncaught TypeError: ins1.getSuperValue is not a function
原型鏈的問(wèn)題 :

最主要的問(wèn)題來(lái)自包含引用類型值的原型.

之前說(shuō)過(guò), 包含引用類型值的原型屬性會(huì)被所有實(shí)例共享.

這也是為什么要在構(gòu)造函數(shù)中, 而不是原型對(duì)象中定義屬性的原因.

第二個(gè)問(wèn)題是, 在創(chuàng)建子類型的實(shí)例時(shí), 不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)

在通過(guò)原型來(lái)實(shí)現(xiàn)繼承時(shí), 原型實(shí)際上會(huì)變成另一個(gè)類型是實(shí)例( SubType.prototype = new SuperType(); ), 于是, 原先的實(shí)例屬性也就順理成章的變成了現(xiàn)在的原型屬性了.

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

function SubType(){}

SubType.prototype = new SuperType();

var ins1 = new SubType();
console.log(ins1.colors); // "red", "green", "blue"
//在ins1添加colors屬性的屬性值
ins1.colors.push("purple");
console.log(ins1.colors); //"red", "green", "blue", "purple"

var ins2 = new SubType();
//ins1中添加到colors中的屬性值直接被添加到了SubType()的原型屬性里面, 導(dǎo)致后來(lái)新增的實(shí)例也繼承了這些屬性
console.log(ins2.colors); //"red", "green", "blue", "purple"]
基于以上問(wèn)題, 多帶帶使用原型鏈, 在開(kāi)發(fā)中是很少的.
借用構(gòu)造函數(shù)( 偽造對(duì)象, 經(jīng)典繼承 )

思路 : 在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型的構(gòu)造函數(shù).

函數(shù)只不過(guò)是在特定環(huán)境中執(zhí)行代碼的對(duì)象, 因此可以通過(guò)apply()call()方法也可以在(將來(lái))新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù)

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

function SubType(){
  //繼承自SuperType
  //當(dāng)SubType(){...}被實(shí)例化后, SuperType()函數(shù)中定義的所有對(duì)象初始化代碼就會(huì)被執(zhí)行
  SuperType.call(this);
}

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

var ins2 = new SubType();
console.log(ins2.colors); //"green", "blue", "purple"
//相比原型鏈, 借用構(gòu)造函數(shù)還有一個(gè)很大的優(yōu)勢(shì), 就是子類型的構(gòu)造函數(shù)可以向超類型的構(gòu)造函數(shù)傳遞參數(shù)

function SuperType(name){
  //父類構(gòu)造函數(shù)接受一個(gè)name函數(shù), 并賦值給一個(gè)屬性
  this.name = name;
}

function SubType(){
  /*在SubType()構(gòu)造函數(shù)中調(diào)用SuperType()構(gòu)造函數(shù)時(shí),
  實(shí)際上是為SubType的實(shí)例設(shè)置了name屬性*/
  SuperType.call(this, "Jon");
  
/*為了確保SuperType構(gòu)造函數(shù)不會(huì)重寫(xiě)子類型的屬性, 可以在調(diào)用父類構(gòu)造函數(shù)后,再添加應(yīng)該在子類型中定義的屬性*/
  this.age = 25;
}

var ins1 = new SubType();
console.log(ins1.name); //Jon
console.log(ins1.age); //25

如果僅僅是借用構(gòu)造函數(shù), 那么也無(wú)法避免構(gòu)造函數(shù)模式存在的問(wèn)題—— 方法都在構(gòu)造函數(shù)內(nèi)部定義, 那么函數(shù)復(fù)用就無(wú)從談起了.

而且在超類型的原型中定義的方法, 對(duì)子類型而言也是不可見(jiàn)的, 結(jié)果所有類型都只能使用構(gòu)造函數(shù)模式.

所以這種方式也是很少多帶帶使用的

組合繼承( 偽經(jīng)典繼承 )

指的是將 原型鏈借用構(gòu)造函數(shù) 的技術(shù)組合到一塊, 從而發(fā)揮二者之長(zhǎng)的一種繼承模式.

思路是, 使用 原型鏈 實(shí)現(xiàn) 對(duì)原型屬性和方法的繼承 , 而通過(guò) 借用構(gòu)造函數(shù) 來(lái)實(shí)現(xiàn)對(duì) 實(shí)例屬性的繼承

這樣, 既通過(guò)在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用, 又能夠保證每個(gè)實(shí)例都有自己的屬性. 所以這成為JavaScript中常用的繼承方式

function SuperType(name){
  //父類定義兩個(gè)屬性name和colors
  this.name = name;
  this.colors = ["blue", "red", "yellow"];
}

//父類定義原型方法sayName
SuperType.prototype.sayName = function(){
  console.log(this.name);
}

function SubType(name, age){
  //SubType構(gòu)造函數(shù)在調(diào)用SuperType構(gòu)造函數(shù)時(shí)傳入了name參數(shù)
  SuperType.call(this, name);
  //然后定義自己的屬性age
  this.age = age;
}

//將SuperType的實(shí)例賦值給SubType的原型
SubType.prototype = new SuperType(); //name, colors[], sayName()
SubType.prototype.constructor = SubType; //構(gòu)造函數(shù)指回自己
//在該新原型上定義了方法sayAge()
SubType.prototype.sayAge = function(){
  console.log(this.age);
}

//兩個(gè)不同的SubType實(shí)例既分別擁有自己的屬性————包括colors屬性, 又可以使用相同的方法了
var ins1 = new SubType("Jon", 25);
ins1.colors.push("purple");
console.log(ins1.colors); //"blue", "red", "yellow", "purple"
ins1.sayName(); //Jon
ins1.sayAge(); //25

var ins2 = new SubType("Mark", 24);
console.log(ins2.colors); //"blue", "red", "yellow"
ins2.sayName(); //Mark
ins2.sayAge(); //24
原型式繼承

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

/*
在object()函數(shù)內(nèi)部, 先創(chuàng)建了一個(gè)臨時(shí)性的構(gòu)造函數(shù)F(){},
然后將傳入的對(duì)象o作為這個(gè)構(gòu)造函數(shù)F(){}的原型,
最后返回了這個(gè)臨時(shí)類型的新實(shí)例.
從本質(zhì)上講, object()對(duì)傳入其中的對(duì)象o執(zhí)行了一次淺復(fù)制
*/
/*
這種繼承方式要求你必須有一個(gè)對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ),
把它傳給object()函數(shù),然后再根據(jù)具體需求對(duì)得到的對(duì)象加以修改即可.
*/
function object(o){
  function F(){}
  F.prototype = o;
  return new F;
}
/*
這個(gè)例子中, 可以作為另一個(gè)對(duì)象的基礎(chǔ)是person對(duì)象
*/
var person = {
  name : "Jon",
  colorsLike : ["black", "white"]
}
/*
把它(person對(duì)象)傳入到object()函數(shù)中, 然后該函數(shù)就會(huì)返回一個(gè)新對(duì)象( anotherPerson1 和 anotherPerson2 ), 
這兩個(gè)新對(duì)象把person作為原型, 所有它們的原型中就包含一個(gè)基本類型值屬性和
一個(gè)引用類型值屬性,這意味著person.colorsLike不僅于person所有,同時(shí)也會(huì)被
anotherPerson1, anotherPerson2共享, 實(shí)際上, 就相當(dāng)于又創(chuàng)建了person對(duì)象的兩個(gè)副本
*/
var anotherPerson1 = object(person); //anotherPerson1現(xiàn)在有了person的所有屬性(這里是name和colorsLike[])
console.log(anotherPerson1.name); //person原有的name屬性值, 輸出Jon
console.log(anotherPerson1.colorsLike); //person原有的colorsLike[]數(shù)組, 輸出["black", "white"]
anotherPerson1.name = "Percy"; //修改anotherPerson1的name屬性為自己的值
anotherPerson1.colorsLike.push("purple"); //添加anotherPerson1自己喜歡的顏色
console.log(anotherPerson1.name); //Percy
console.log(anotherPerson1.colorsLike); //["black", "white", "purple"]
console.log(person.name); //Jon
console.log(person.colorsLike); //person的colorsLike數(shù)組值已經(jīng)被anotherPerson1添加的屬性影響, 此時(shí)也輸出了["black", "white", "purple"]

var anotherPerson2 = object(person);
console.log(anotherPerson2.name); //Jon
console.log(anotherPerson2.colorsLike); //["black", "white", "purple"]
anotherPerson2.colorsLike.push("red"); //再push一個(gè)
console.log(anotherPerson2.colorsLike); //["black", "white", "purple", "red"]
console.log(person.colorsLike); //再度被anotherPerson2新增的值影響, 輸出["black", "white", "purple", "red"]

ES5新增了一個(gè)方法Object.create()規(guī)范了原型式繼承, 該方法接收兩個(gè)參數(shù), 一個(gè)用作新對(duì)象的原型的對(duì)象和一個(gè)(可選)一個(gè)為新對(duì)象定義額外屬性的對(duì)象

瀏覽器支持, IE9+和各現(xiàn)代瀏覽器

還是直接看例子比較直觀 :

傳入一個(gè)參數(shù)的時(shí)候, 這個(gè)方法跟上面object()方法的行為相同 :

var person = {
  name : "Jon",
  colorsLike : ["black", "white"]
};

console.log(person.colorsLike); // ["black", "white"]

//傳入一個(gè)參數(shù)的時(shí)候, 這個(gè)方法跟上面object()方法的行為相同
var anotherPerson = Object.create(person);
anotherPerson.name = "Percy";
anotherPerson.colorsLike.push("purple");

console.log(anotherPerson.name); //Percy
console.log(anotherPerson.colorsLike); //["black", "white", "purple"]
console.log(person.name); //Jon
console.log(person.colorsLike); //["black", "white", "purple"]

傳入兩個(gè)參數(shù)的時(shí)候, 第二個(gè)參數(shù)與Object.defineProperties()方法的第二個(gè)參數(shù)格式相同 : 每個(gè)屬性都是通過(guò)自己的描述符定義的, 以這種方式指定任何屬性都會(huì)覆蓋原型對(duì)象上的同名屬性

var person = {
  name : "Jon",
  colorsLike : ["black", "white"]
};

console.log(person.colorsLike); // ["black", "white"]

//傳入兩個(gè)參數(shù)
var anotherPerson = Object.create(person, {
  name : {
    value : "Martin"
  }
});

console.log(anotherPerson.name); //Martin
寄生式繼承

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

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

function createAnother(original){
  var clone = object(original); //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
  clone.sayHi = function(){ //以某種方式增強(qiáng)這個(gè)對(duì)象(添加自身方法或者屬性等)
      console.log("Good Day!");
  };
  return clone; //返回該對(duì)象
}

var person = {
  name : "Jon",
  friends : ["Martin", "Jeniffer"]
};

/*
這個(gè)實(shí)例中的代碼 基于person 返回了一個(gè)新對(duì)象————anotherPerson
該對(duì)象不僅具有person所有屬性和方法, 而且還有自己的sayHi()方法
*/
/*
在主要考慮對(duì)象而不是自定義類型和構(gòu)造函數(shù)的情況下, 寄生式繼承也是一種有用的方式, 前面示范繼承模式使用的object()函數(shù)并不是必須的, 任何能夠返回新對(duì)象的函數(shù)都適用于此模式
*/

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //Good Day!
寄生組合式繼承

前面說(shuō)過(guò), 組合繼承是JS中最常用的繼承模式, 不過(guò)它也有自己的不足

組合繼承 最大的問(wèn)題是, 無(wú)論在什么情況下, 都會(huì)調(diào)用兩次父類型構(gòu)造函數(shù), 一次是在創(chuàng)建子類型原型的時(shí)候, 一次是在子類型構(gòu)造函數(shù)內(nèi)部 :

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);  //第二次調(diào)用SuperType()
  this.age = age;
}

/*
第一次 調(diào)用SuperType構(gòu)造函數(shù)時(shí), SubType.prototype會(huì)得到兩個(gè)屬性,
name和colors[], 它們都是SuperType的實(shí)例屬性, 只不過(guò)現(xiàn)在位于SubType的原型中;
當(dāng)調(diào)用SubType構(gòu)造函數(shù)時(shí), 又會(huì)再一次調(diào)用一次SuperType構(gòu)造函數(shù), 這一次又在新對(duì)象上創(chuàng)建了實(shí)例屬性name和colors[], 于是, 這兩個(gè)屬性就遮蔽了原型中的兩個(gè)同名屬性
*/
SubType.prototype = new SuperType(); //第一次調(diào)用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
  console.log(this.age);
}

如下圖 :

寄生組合式繼承, 即通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性, 通過(guò)原型鏈的混成模式來(lái)繼承方法.

思路是, 不必為了指定子類型的原型而調(diào)用構(gòu)造超類型的構(gòu)造函數(shù), 我們所需要的無(wú)非就是超類型的一個(gè)副本而已 ??

本質(zhì)上, 就是使用寄生式繼承來(lái)繼承超類型的原型, 然后再將結(jié)果指定給子類型的原型.

基本模式如下所示. ??

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

/*
寄生組合式繼承的最簡(jiǎn)單形式, 這個(gè)函數(shù)接收兩個(gè)參數(shù), 子類型構(gòu)造函數(shù)和超類型構(gòu)造函數(shù);
在函數(shù)內(nèi)部,第一步是創(chuàng)建超類型原型的一個(gè)副本, 第二步是為創(chuàng)建的的副本添加constructor屬性, 從而彌補(bǔ)因重寫(xiě)而失去默認(rèn)的constructor屬性;
最后一步, 將新創(chuàng)建的對(duì)象(即副本)賦值給子類型的原型,這樣我們就可以調(diào)用inheritPrototype()函數(shù)的語(yǔ)句,去替換前面例子中未知類型原型賦值的語(yǔ)句了(41行)
*/
function inheritPrototype(subType, superType){
  var prototype = object(superType.prototype);   //創(chuàng)建對(duì)象
  prototype.constructor = subType;               //增強(qiáng)對(duì)象
  subType.prototype = prototype;                 //指定對(duì)象
}

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;
}

inheritPrototype(SubType, SuperType); //調(diào)用inheritPrototype()函數(shù)

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

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


var instance2 = new SubType("Mark", 24);
console.log(instance2.colors);  //"red,blue,green"
instance2.sayName();      //"Mark"
instance2.sayAge();       //24
函數(shù)表達(dá)式
常用JS定義函數(shù)方式有兩種 :

第一種是函數(shù)聲明 :

function Person(name){
  this.name = name;
  console.log("name is " + this.name);
}
Person("Jon"); //name is Jon


//函數(shù)聲明支持函數(shù)聲明提升, 即執(zhí)行代碼前會(huì)先讀取函數(shù)聲明, 那么函數(shù)聲明可以放在調(diào)用它的代碼之后而不出錯(cuò) : 
Person("Jon"); //works ! 輸出name is Jon
function Person(name){
  this.name = name;
  console.log("name is " + this.name);
}

第二種是函數(shù)表達(dá)式 :

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

Person("Jon"); //Jon

//函數(shù)表達(dá)式不支持函數(shù)聲明提升
Person("Jon"); //Uncaught TypeError: Person is not a function
var Person = function(name){
  this.name = name;
  console.log(this.name);
}

要在使用條件語(yǔ)句后面執(zhí)行函數(shù)的話, 條件語(yǔ)句內(nèi)的函數(shù)必須使用函數(shù)表達(dá)式的方式定義, 如果使用函數(shù)聲明方式定義, 會(huì)在不同的瀏覽器導(dǎo)致不同問(wèn)題的發(fā)生 :

//條件語(yǔ)句內(nèi)的函數(shù)定義必須使用函數(shù)表達(dá)式
var b = true;
if(b){
  sayColors = function(){
      console.log(this.color);
  };
}else{
  console.log("error!");
}
匿名函數(shù)
function createComparisonFunction(propertyName){
  //這里返回的就是匿名函數(shù), 它能賦值給一個(gè)變量, 或者以其他的方式調(diào)用
  return function(object1, object2){
    var value1 = object1[propertyName];
    var value2 = object2[propertyName];

    if (value1 < value2) {
      return -1;
    }else if(value1 > value2){
      return 1;
    }else{
      return 0;
    }
  };
}
遞歸

遞歸函數(shù)是一個(gè)函數(shù)通過(guò)名字調(diào)用自身的情況下構(gòu)成的

//遞歸階乘函數(shù)
function factorial(num){
  if(num <= 1){
      return 1;
  }else{
      return num * factorial(num - 1); 
  }
}

//注意如下調(diào)用會(huì)產(chǎn)生錯(cuò)誤
var anotherFactorial = factorial; //把factorial()函數(shù)保存在一個(gè)變量中
factorial = null; //把factorial函數(shù)設(shè)置為null
console.log(anotherFactorial(3)); //Uncaught TypeError: factorial is not a function
//使用arguments.callee解決上面的問(wèn)題
//arguments.callee是一個(gè)指向當(dāng)前正在執(zhí)行的函數(shù)的指針, 因此可以用它來(lái)實(shí)現(xiàn)對(duì)函數(shù)的遞歸調(diào)用
//嚴(yán)格模式下不允許使用arguments.callee
function factorial(num){
  if (num <= 1) {
    return -1;
  }else{
    //arguments.callee代替了函數(shù)名factorial
    return num * arguments.callee(num - 1);
  }
}
//解決嚴(yán)格模式下不允許使用arguments.callee的問(wèn)題
//使用命名函數(shù)表達(dá)式來(lái)達(dá)成相同的結(jié)果
var factorial = (function f(num){ //創(chuàng)建一個(gè)名為f()的命名函數(shù)表達(dá)式, 賦值給factorial
  if(num <= 1){
      return 1;
  }else{
      return num * f(num - 1);
  }
});
閉包

注意匿名函數(shù)閉包不要混淆.

閉包指的是有權(quán)訪問(wèn) 另一個(gè)函數(shù)作用域中的變量函數(shù)

創(chuàng)建閉包常用的方式, 就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù) :

function createComparisonFunction(propertyName){
  return function(object1, object2){
    //value1和value2訪問(wèn)了外部函數(shù)的變量propertyName, 即使該內(nèi)部函數(shù)被返回或被其他地方調(diào)用, 也不影響它訪問(wèn)外部函數(shù)的propertyName變量(因?yàn)樵撏獠孔兞吭诒緝?nèi)部函數(shù)的作用域內(nèi))
    var value1 = object1[propertyName];
    var value2 = object2[propertyName];

    if (value1 < value2) {
      return -1;
    }else if(value1 > value2){
      return 1;
    }else{
      return 0;
    }
  };
}

理解 :

//定義compare函數(shù)
function compare(value1, value2){
  if(value1 < value2){
     return -1;   
  }else if(value1 > value2){
      return 1;
  }else{
      return 0;
  }
}

//在全局作用域中調(diào)用函數(shù), 從作用域鏈的優(yōu)先級(jí)來(lái)分的話, 外部函數(shù)的活動(dòng)對(duì)象始終處于第二位, 外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位 ...(以此類推), 直到作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境
var result = compare(5, 8);

//在調(diào)用compare()函數(shù)時(shí), 會(huì)創(chuàng)建一個(gè)包含arguments, value1, value2的活動(dòng)對(duì)象(在作用域鏈的優(yōu)先級(jí)處于第一位), 全局執(zhí)行環(huán)境的變量對(duì)象(包含result和compare)在compare()執(zhí)行環(huán)境的作用域鏈優(yōu)先級(jí)處于第二位

作用域鏈優(yōu)先級(jí)圖示 :

后臺(tái)的每一個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對(duì)象 — — 變量對(duì)象

全局環(huán)境的變量對(duì)象始終存在, 而像compare()函數(shù)這樣的局部環(huán)境的變量對(duì)象, 則只在函數(shù)執(zhí)行的過(guò)程中存在.

創(chuàng)建compare()函數(shù)時(shí), 會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈, 該作用域鏈會(huì)被保存在內(nèi)部的[[ Scope ]]屬性中

調(diào)用compare()函數(shù)時(shí), 會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境

然后通過(guò)復(fù)制函數(shù)的[[ Scope ]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈

?

未完待續(xù)...

模仿塊級(jí)作用域

TODO

私有變量

TODO

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/86308.html

相關(guān)文章

  • CSS技巧

    摘要:技巧使你的更加專業(yè)這是上關(guān)于技巧的一篇譯文,另外你也可以在本項(xiàng)目看到原文。列舉了一些很實(shí)用的技巧,比如給空內(nèi)容的標(biāo)簽添加內(nèi)容,逗號(hào)分隔列表等等。排序算法看源碼,把它背下來(lái)吧排序算法的封裝。主要幫助初學(xué)者更好的掌握排序算法的實(shí)現(xiàn)。 成為專業(yè)程序員路上用到的各種優(yōu)秀資料、神器及框架 成為一名專業(yè)程序員的道路上,需要堅(jiān)持練習(xí)、學(xué)習(xí)與積累,技術(shù)方面既要有一定的廣度,更要有自己的深度。 Java...

    DangoSky 評(píng)論0 收藏0
  • CSS技巧

    摘要:技巧使你的更加專業(yè)這是上關(guān)于技巧的一篇譯文,另外你也可以在本項(xiàng)目看到原文。列舉了一些很實(shí)用的技巧,比如給空內(nèi)容的標(biāo)簽添加內(nèi)容,逗號(hào)分隔列表等等。排序算法看源碼,把它背下來(lái)吧排序算法的封裝。主要幫助初學(xué)者更好的掌握排序算法的實(shí)現(xiàn)。 成為專業(yè)程序員路上用到的各種優(yōu)秀資料、神器及框架 成為一名專業(yè)程序員的道路上,需要堅(jiān)持練習(xí)、學(xué)習(xí)與積累,技術(shù)方面既要有一定的廣度,更要有自己的深度。 Java...

    zgbgx 評(píng)論0 收藏0
  • 快速掌握JavaScript面試基礎(chǔ)知識(shí)()

    摘要:第一部分請(qǐng)點(diǎn)擊快速掌握面試基礎(chǔ)知識(shí)一關(guān)鍵字如果使用關(guān)鍵字來(lái)調(diào)用函數(shù)式很特別的形式。該對(duì)象默認(rèn)包含了指向原構(gòu)造函數(shù)的屬性。接下來(lái)通過(guò)例子來(lái)幫助理解屬性包含了構(gòu)造函數(shù)以及構(gòu)造函數(shù)中在上定義的屬性。也就是說(shuō),的回調(diào)函數(shù)后執(zhí)行。 譯者按: 總結(jié)了大量JavaScript基本知識(shí)點(diǎn),很有用! 原文: The Definitive JavaScript Handbook for your next...

    qieangel2013 評(píng)論0 收藏0
  • 深入理解js

    摘要:詳解十大常用設(shè)計(jì)模式力薦深度好文深入理解大設(shè)計(jì)模式收集各種疑難雜癥的問(wèn)題集錦關(guān)于,工作和學(xué)習(xí)過(guò)程中遇到過(guò)許多問(wèn)題,也解答過(guò)許多別人的問(wèn)題。介紹了的內(nèi)存管理。 延遲加載 (Lazyload) 三種實(shí)現(xiàn)方式 延遲加載也稱為惰性加載,即在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖像。用戶滾動(dòng)到它們之前,視口外的圖像不會(huì)加載。本文詳細(xì)介紹了三種延遲加載的實(shí)現(xiàn)方式。 詳解 Javascript十大常用設(shè)計(jì)模式 力薦~ ...

    caikeal 評(píng)論0 收藏0
  • 前端資源系列(4)-前端學(xué)習(xí)資源分享&前端面試資源匯總

    摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒(méi)想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...

    princekin 評(píng)論0 收藏0
  • JS框架 - 收藏集 - 掘金

    摘要:現(xiàn)在回過(guò)頭總結(jié),才又進(jìn)一步的揭開(kāi)了閉包的一層后臺(tái)管理系統(tǒng)解決方案前端掘金基于系列的后臺(tái)管理系統(tǒng)解決方案。什么是繼承大多數(shù)人使用繼承不外乎是為了獲得基于的單頁(yè)應(yīng)用項(xiàng)目模板前端掘金小貼士本項(xiàng)目已升級(jí)至。 關(guān)于js、jq零碎知識(shí)點(diǎn) - 掘金寫(xiě)在前面: 本文都是我目前學(xué)到的一些比較零碎的知識(shí)點(diǎn),也是相對(duì)偏一點(diǎn)的知識(shí),這是第二篇。前后可能沒(méi)有太大的相關(guān)性,需要的朋友可以過(guò)來(lái)參考下,喜歡的可以點(diǎn)個(gè)...

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

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

0條評(píng)論

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