JavaScript_高程三_02

面向對象的程序設計

ECMA-262定義對象:無序屬性的集合,其屬性能夠包含基本值,對象或者函數。
普通理解:對象是一組沒有特定順序的值。
對象的每一個屬性或方法都有一個名字,而每一個名字都映射一個值。javascript

每一個對象都是基於一個引用類型建立的。java

理解對象

屬性類型正則表達式

在定義只有內部的特徵(attribute)時,描述了屬性(property)的各類特徵。
是爲了實現JavaScript引擎用的,在JavaScript中不能直接訪問他們。
爲了表示特性是內部值,把它們放在了兩對方括號中. 例如: [[Enumerable]]segmentfault

ECMAScript中有兩種屬性:數據屬性和訪問器屬性。數組

數據屬性
數據屬性包含一個數據值的位置。在這個位置能夠讀取和寫入值。數據屬性有4個描述行爲的特性
  • [[Configurable]]: 可否經過delete刪除屬性從而從新定義屬性,可以修改屬性的特性,或者可否把屬性修改成訪問屬性。 默認值:false;(不能夠從新定義或刪除)
  • [[Enmuerable]]: 可以經過for-in循環返回屬性。(是否能夠被枚舉).默認值:false;(不能夠被枚舉)
  • [[Writable]]: 可否修改屬性的值。默認值:false,(不能夠被修改)
  • [[Value]]:包含這個屬性的數據值,讀取屬性的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。默認值:undefiend.
var Person = {
    name: 'Nicholas'
}

// 建立name屬性。爲它置頂的值是「Nicholas」。也就是[[Value]]被設置了"Nicholas",而對這個值的任何修改都將反映在這個位置。

Object.defineOProperty();
做用:修改屬性默認特性
參數1:屬性所在的對象
參數2:屬性的名字
參數3:一個描述符對象
描述符(descriptor)對象的屬性必須是:configurable,enumerable,writable,value.
設置其中一個或多個值,能夠修改對應的特性值。瀏覽器

var Person = {};
Object.defineProperty(Person, "name", {
    writable: false,
    value: "Nicholas"
});

alert(Person.name); //"Nicholas"
Person.name = "Greg"; // 設置成只讀的,屬性是不可修改的。
alert(Person.name); //"Nicholas"

constructor屬性,是沒法被枚舉的. 正常的for-in循環是沒法枚舉. [eable = false];緩存

Object.getOwnPropertyNames(); //枚舉對象全部的屬性:無論該內部屬性可以被枚舉.安全

//3個參數,  參數1:從新設置構造的對象 (給什麼對象設置)     參數2:設置什麼屬性        參數3:options配置項 (要怎麼去設置)
Object.defineProperty(Person.prototype,'constructor',{
    enumerable: false,  //是不是 可以 被枚舉
    value: Person  //值   構造器的 引用
});
訪問器屬性
訪問器屬性不包含數據值,包含一堆geter() 和setter(); 函數 (這兩個函數都不是必須的)
在讀取訪問器屬性時,會調用getter(); 這個函數負責返回有效值,在寫入訪問器屬性時,會調用setter()函數並傳入新值,這個函數負責決定如何處理數據。
訪問器屬性的4個特性:
  • [[Configurable]]: 可以經過delete刪除屬性從而定義新的屬性,可否修改屬性的特性,或者可否把屬性修改成數據屬性。默認值:false; (不可從新定義,和刪除屬性)
  • [[Enmuerable]]:可否經過for-in循環返回屬性。默認值:false;(不可被枚舉)
  • [[Get]]: 在讀取屬性時調用的函數。默認值爲:undefeind
  • [[Set]]: 在寫入屬性時調用的函數。默認值爲:undefiend

訪問器屬性不能直接定義,必須使用Object.defineProperty();來定義。服務器

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

book.year = 2020;
alert(book.edition); // 5

// _year前面的下劃線是一種經常使用的記號,用於表示只能經過對象方法訪問的屬性。

定義多個屬性 網絡

Object.defineProperties();
定義多個屬性。
參數1:對象要添加和修改其屬性的對象
參數2:對象的屬性與第一個對象中要添加或修改的屬性一一對應.

var book = {};

Object.defineProperties(book, {
    _year: {
        value: 2016
    },
    edition: {
        value: 1
    },
    year: {
        get: function () {
            return this._year;
        },
        set: function (newValue) {
            this._year = newValue;
        }
    }
});

讀取屬性的特性
Object.getOwnPropertyDescriptor()
取得給定屬性的描述符.
參數1:屬性所在的對象
參數2:要讀取其描述符的屬性名稱
返回值:對象。若是是訪問器屬性,這個對象含有:configurable,enumerable,get和set
若是是數據屬性,這個對象含有:configureable,enumerbale,writeable,value

建立對象

工廠模式
解決:建立多個類似對象。(未解決:對象識別的問題-怎麼知道一個對象的類型)

function createPerson(name, age, job){

    var o = new Object();

    o.name = name;
    o.age = age;
    o.job = job;
    
    o.sayName = function(){
        alert(this.name);
    };

    return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

構造函數模式

function Person(name, age, job){

    this.name = name;
    this.age = age;
    this.job = job;

    this.sayName = function(){
        alert(this.name);
    };
    
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
  • 沒有顯示的建立對象
  • 直接將屬性和方法賦給this對象
  • 沒有reutrn語句

建立實例會通過:

  1. 建立一個對象
  2. 將構造函數的做用域賦給新對象(所以this就指向了這個新對象)
  3. 執行構造函數中的代碼(爲這個新對象添加屬性)
  4. 返回新對象

對象的 constructor 屬性最初是用來表示對象類型。
提到檢測對象類型,仍是使用instanceof操做符要更可靠。

將構造函數看成函數

構造函數與其它函數的惟一區別,在於調用它們的方式不一樣。
構造函數也是函數,不存在定義構造函數的語法。任何函數,只要經過new操做來調用,就能夠做爲構造函數。
任何函數,若是不經過new操做符來調用,就跟普通函數沒什麼不一樣。

構造函數的問題
每一個方法都要在每一個實例上從新建立
經過原型模式來解決

原型模式
每一個函數都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個對象,
而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法

每一個函數都有一個prototype(原型)屬性,這個屬性石一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法

字面的理解:prototype就是經過調用構造函數而建立的那個對象實例的原型
讓全部對象實例共享它全部包含的屬性和方法。沒必要在構造函數中定義對象實例的信息,而是將這些信息直接添加到原型中。

理解原型對象
只要建立了一個函數,就會根據一組特定的規則爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。

默認狀況下:全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。

建立了自定義的構造函數以後,其原型對象默認只會取得constructor屬性。

當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。

原型鏈查找:
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。若是在實例中找到了具備給定名字的屬性,則返回該屬性的值,若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性。若是在原型對戲那個中找到這屬性,則返回該屬性的值。

能夠經過對象實例訪問保存在原型中的值,但卻不能經過對象實例重寫原型中的值,若是在實例中添加了一個屬性,而該屬性與實例原型中的一個屬性同名,那在實例中建立該屬性,該屬性將會屏蔽原型中的那個屬性。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

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

person1.name = "Greg";

alert(person1.name); //"Greg" —— 來自實例的name

alert(person2.name); //"Nicholas" -- 來自原型中的name

當爲對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。添加這個屬性只會組織訪問原型中的那個屬性,但不會修改那個屬性。即便將這個屬性設置爲null,也只會在實例中設置這個屬性,而不會恢復其指向原型的鏈接。可使用delete操做符能夠徹底刪除實例屬性。

hasOwnProperty() // 從Object繼承而來的方法
檢測一個屬性是否存在於實例中,仍是存在於原型。
判斷一個對象屬性 是屬於 原型屬性 仍是屬於 實例屬性
在實例中,返回true
在原型上,返回false

function Person() {}

Person.prototype.name = 'Nicholas';

var p1 = new Person();
console.log(p1.hasOwnProperty('name')); // false

原型與in操做符
in 操做符,經過對象可以訪問給定屬性時返回true,不管是在實例中仍是原型中。可以訪問到,就返回true.

同時使用hasOwnProperty(); 和 in操做符 : 肯定是屬性存在實例中,仍是原型中。

// 判斷屬於原型上的屬性
function hasPrototypeProperty( obj, attr ) {
    return !obj.hasOwnProperty(attr) && attr in obj;
}

Object.keys()
參數:接收一個對象做爲參數,返回一個包含全部可枚舉的屬性的字符串數組。

function Person() {}

Person.prototype.name = 'Nicholas';
Person.prototype.age = 20;

var p1 = new Person();

var keys = Object.keys(Person.prototype);

console.log(keys); // ["name", "age"]

更簡單的原型方法

function Person() {}
Person.prototype = {}

將Person.prototype設置爲等於一個對象字面量形式建立的新對象。
會形成constructro屬性再也不指向Person。
每建立一個函數,就會同時建立它的peorotype,這個對象也會自動得到constructro屬性。直接賦值爲一個字面量形式,本質上是徹底重寫了默認的prottoype對象,所以constructor屬性也就變成新的對象的constructor屬性(指向Object構造函數) 再也不指向Person函數。
經過instanceof操做符可以返回正確的結果,可是經過constructor已經沒法肯定對象的類型了。

能夠手動設置回constructor屬性爲當前的類

function Person () {}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas'
}

這種方式重設constructor屬性會致使它的[[Enumerable]]特性被設置爲true

constructor是不可被枚舉,能夠經過Object.definePropert();修改特性,重置構造函數。

Object.defineProperty(Person.protype, 'constructor', function () {
    enmuerable:  false,
    value: Person
});

原型動態
因爲在原型中查找值的過程是一次搜索,對原型對象所作的任何修改都可以當即從實例上反應出來,即便是先建立了實例後修改原型也是同樣。

實例中的指針僅指向原型,而不指向構造函數。

能夠隨時爲原型添加屬性和方法,而且修改可以當即在全部對象實例中反映出來,但若是是重寫了這個原型對象,那麼就不同。
調用構造函數時會爲實例添加一個指向最初原型的[[Prototype]] 或者__proto__ .而把原型修改成另一個對象就等於切斷了構造函數與最初原型之間的聯繫。

function Person () {}

var firend = new Person();

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

firend.sayName(); // error //  friend 指向的原型中不包含以該名字命名的屬性

clipboard.png

原生對象的原型
原型模式的體如今建立自定義類型方面,全部的原生的引用類型,都是採用這種模式建立的。
全部元素引用類型(Object,Array,String...)都在其構造函數上定義了方法

經過原生對象的原型,不只能夠取得全部默認方法的引用,並且也能夠定義新的方法。

console.log( typeof Array.prototype.slice ); // function

不建議這樣使用,會致使命名衝突,也可能意外的重寫原生的方法。

原型對象的問題

原型模式省略了爲構造函數傳遞參數初始化的,形成全部實例默認狀況下都將取得相同的屬性值。
原型模式最大的問題是由其共享的本性所致使。

對於包含引用類型的原型對象,若是修改其值,那麼另個實例也會修改。

function Person () {}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    friends: ['Shelby', 'Court']
}

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

p1.friends.push('Van');

console.log(p1.friends);  // ["Shelby", "Court", "Van"]
console.log(p1.friends);  // ["Shelby", "Court", "Van"]

實例通常都要有屬性本身的所有屬性。這個問題形成不多會單獨使用原型模式。

組合使用構造函數模式和原型模式
構造函數模式:用於定義實例的屬性
原型模式:用於定義方法和共享的屬性。

結果:每一個實例都會有本身的一份實例屬性的副本,但同時又共享這對方法的引用,最大限度的節省內存,同時還支持向構造函數傳遞參數。

是目前在ECMAScript中使用最普遍,認同度最高的一種建立自定義類型的方法。是用來定義引用類型的一種默認模式。

動態原型模式
把信息都封裝到函數中,這樣體現了封裝的概念。

經過在構造函數中初始化原型(僅在必要狀況下),同時保持了同時使用構造函數和原型的優勢。
能夠經過檢查默認應該存在的方法是否有效,來決定是否須要初始化運原型。

//動態原型模式:(讓你的代碼  都封裝到一塊兒)
function Person( name,age,firends ) {
    
    this.name = name;
    this.age = age;
    this.firends = firends;
    
    //動態原型方法
    if ( typeof this.sayName !== 'function'  ) {
        
        Person.prototype.sayName = function () {
            console.log(this.name);
        }
        
    }
    
}

sayName()方法不存在的狀況下,纔會將它添加到原型中if語句中的代碼只會在初次調用構造函數時纔會執行。此後,原型已經完成初始化,不須要再作什麼修改。這邊的原型所作的修改,可以理解在全部實例中獲得反映。
if語句的檢查能夠是初始化以後應該存在的任何屬性或方法---沒必要用一大堆if語句檢查每一個屬性和每一個方法。只要檢查其中一個便可。

採這種模式建立的對象,還可使用instanceof操做符肯定它的類型。
使用動態原型模式時,不能使用對象字面量重寫原型。若是在已經建立了實例的狀況下重寫原型,就會切斷現有實例與新原型之間的聯繫。

寄生構函數模式
建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象。
在工廠模式,組合使用構造函數模式和原型模式,都不能使用的狀況下,使用。
這個模式與工廠模式是相似的,構造函數在不返回值的狀況下,默認會返回新對象的實例。而在構造函數末尾添加一個return語句,能夠重寫調用構造函數時返回出來的值。

function Person ( name ) {
    var o = new Object();
    o.name = name;
    o.sayName = function () {

        console.log(this.name);

    }
    return o;
}


var friend = new Person('Van');
friend.sayName();

寄生構造函數模式,首先,返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係。
構造函數返回的對象與在構造函數外部建立的對象沒有什麼不一樣,爲此,不恩呢該依賴 instanceof操做符來肯定對象類型。

通常來講,不可經過prototype修改原生對象的方法。可使用寄生構造函數模式,達到特殊需求。

穩妥構造函數模式

穩妥模式就是沒有公共屬性,並且其餘方法也不引用this對象,穩妥模式最適合在安全的環境中使用。若是程序對於安全性要求很高,那麼很是適合這種模式。
也不能使用new關鍵字。

//穩妥構造函數式  durable object (穩妥對象)
//1,沒有公共的屬性
//2,不能使用this對象

function Person ( name,age ) {
    
    //建立一個要返回的對象。  利用工廠模式思惟。
    var obj = new Object();
    
    //能夠定義一下是有的變量和函數 private
    
    var name = name || 'zf';
    
//                        var sex = '女';
//                        var sayName = function () {
//                        }

        
    //添加一個對外的方法
    obj.sayName = function () {
        console.log(name);
    }
    
    return obj;
    
}

var p1 = Person('xixi',20);

p1.sayName();

繼承

許多OO語言橫縱都支持兩種繼承方式:接口繼承 和實現繼承。
接口繼承:只繼承方法簽名
實現繼承:繼承實際的方法。

因爲函數中沒有簽名,在ECMAScript中沒法實現接口繼承,ECAMScript 只支持實現繼承,而其實現繼承主要原型鏈來實現。

原型鏈

基本思想:讓一個引用類型繼承另外一個引用類型的屬性和方法。

讓原型對象等於另外一個類型的實例,此時的原型對象將包含一個指向另外一個原型的指針,相應的,另外一個原型中也包含指向另外一個構造函數的指針。

實現的本質是重寫原型對象,代之以一個新類型的實例

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

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

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

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

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

var instance = new SubType();

console.log( instance.getSuperValue() ); // true

clipboard.png

調用 instance.getSuperValue()會經歷三個步驟:
1:搜索實例
2:搜索SubType.prototype
3:搜索SuperTpe.prototype

別忘記默認的原型
全部函數的原型都是Object的實例,所以默認原型都會包含一個內部指針,指向Object.prototype

肯定原型和實例的關係

能夠經過兩種方式來肯定原型和實例之間的關係。第一種方式是使用 instanceof 操做符,只要用
這個操做符來測試實例與原型鏈中出現過的構造函數,結果就會返回 true 。

方式1:使用instanceof操做符。 測試實例與原型鏈中出現的構造函數,就會返回true。

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

方式2:使用isPrototypeOf(); 是原型鏈中出現過的原型,均可以說是該原型鏈所派生的實例的原型。就會返回true。

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
謹慎地定義方法
子類型有時候須要重寫超類型中的某個方法,或者須要添加超類型中不存在的某個方法。
給原型添加方法的代碼必定要放在替換原型的語句以後。

即在經過原型鏈實現繼承時,不能使用對象字面量建立原型方法。由於這
樣作就會重寫原型鏈,

即在經過原型鏈實現繼承時,不能使用對象字面量建立原型方法。這樣會重寫原型鏈。致使替換原型無效。

原型鏈的問題

原型鏈能夠實現繼承,可是存在一些問題:包含引用類型的原型。
經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就變成了如今的原型屬性了。

在構造函數中,而不是原型對象中定義屬性的緣由:包含引用類型的原型屬性會被實例所共享。

第二個問題:在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。
緣由:沒有辦法在不影響全部對象的實例狀況下,給超類型的構造函數傳遞參數。
實踐中不多單獨使用原型鏈。

借用構造函數

在子類型構造函數的內部調用超類型構造函數
函數只是在特定環境中執行代碼的對象,所以經過使用apply()和call()方法能夠在新建立的對象上執行構造函數

傳遞參數
在類型構造函數中向超類型構造函數傳遞參數
function SuperType ( name ) {
    this.name = name;
}

function SubType ( name ) {
    // 繼承了SuperType,同時傳遞參數
    SuperType.call(this,name)
    this.age = 21;
}

var instance = new SubType('cyan');
console.log( instance.name ); // cyan
console.log( instance.age ); // 21
借用構造函數的問題

沒法避免構造函數模式存在的問題--方法都定義構造函數中定義。
超類型的原型中定義的方法,對子類型而言也是不可見的。
借用構造函數不多單獨使用

組合繼承
將原型鏈和借用構造函數的技術組合到一塊,發揮兩者之長的一種繼承模式。
思路:使用原型鏈的實現對象原型屬性和方法的繼承,經過借用構造函數實現的對象屬性的繼承。
結果:即經過在原型上定義方法實現了函數複用,有可以保證每一個實例都有它本身的屬性。

instanceof 和isPrototypeOf(); 可以用於識別基於組合建立的對象。 (JavaScript中最經常使用的繼承模式)

缺點:會執行構造函數二次。

原型式繼承

藉助原型能夠基於已有的對象建立新對象,同時還不比所以建立自定義類型。

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

在 object(); 函數內部,先建立了一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回這個臨時類型的一個新實例。本質上:boject()對傳入其中的對象執行了一次淺複製

//2件事: 繼承了1次父類的模板,繼承了一次父類的原型對象


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

Person.prototype = {
    
    constructor: Person,
    
    sayHello: function () {

        console.log('hello world!');

    }
    
}

function Boy ( name,age,sex ) {
    
    //call 綁定父類的模板函數  實現  借用構造函數繼承  只複製了父類的模板
    
//  Person.call(this,name,age);

        Boy.superClass.constructor.call(this,name,age);
    
    this.sex = sex;        
    
}

//原型繼承的方式: 即繼承了父類的模板,又繼承了父類的原型對象。
//            Boy.prototype = new Person();
//只繼承 父類的原型對象
    extend(Boy,Person);  // 目的 只繼承 父類的原型對象  , 須要那兩個類產生關聯關係.

//給子類加了一個原型對象的方法。
Boy.prototype.sayHello = function () {
    
    console.log('hi,js');
    
}

var b = new Boy('zf',20,'男');

console.log( b.name );
console.log( b.sex );
b.sayHello(); 

Boy.superClass.sayHello.call(b);

//extend方法

//sub子類, sup 父類
function extend ( sub,sup ) {
    
    //目的, 實現只繼承 父類的原型對象。   從原型對象入手
    
    //1,建立一個空函數, 目的:空函數進行中轉
    var F = new Function();  // 用一個空函數進行中轉。
    
// 把父類的模板屏蔽掉, 父類的原型取到。
    
    F.prototype = sup.prototype;  // 2實現空函數的原型對象 和 超類的原型對象轉換
    
    sub.prototype = new F();  // 3原型繼承
    
    //作善後處理。 還原構造器 ,
    sub.prototype.constructor = sub;  //4 ,還原子類的構造器
    
//保存一下父類的原型對象  // 由於 ①方便解耦, 減低耦合性      ② 能夠方便得到父類的原型對象
    sub.superClass = sup.prototype;   //5 ,保存父類的原型對象。   //自定義一個子類的靜態屬性   , 接受父類的原型對象。
    
    //判斷父類的原型對象的構造器, (防止簡單原型中給更改成  Object)
    if ( sup.prototype.constructor == Object.prototype.constructor ) {
        
        sup.prototype.constructor = sup;  //還原父類原型對象的構造器
        
    }
    
}

Object.create()
參數1:用做新對象原型的對象。
參數2(可選)一個爲新對象額外屬性的對象. 配置參數(每一個屬性都是經過高本身的描述符定義)
傳入一個參數的狀況下:Object.create(); 與 object() 方法的行爲相同.
注意:定義任何屬性都會覆蓋原型對象上同名屬性。

包含引用類型值的屬性始終都會共享相應的值

var person = {
    name: 'cyan',
    firends: ['tan', 'kihaki', 'van']
}

var anotherPerson = Object.create(person, {
    name: {
        value: 'red'
    }
});
console.log( anotherPerson.name ); // red

寄生式繼承

建立一個僅用於封裝過程的函數,該函數在內部以某種方式來加強對象,最後返回對象。

function createAnother ( original ) {
    var cloen = Object.create(original); // 調用函數建立一個新對象
    clone.sayHi = function () { // 以某種方式來加強這個對象
        console.log('hi'); 
    }
    return clone; // 返回這個對象
}

寄生組合式繼承
解決,構造函數調用二次的問題。
經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
基本思路:沒必要爲了指定子類型的原型而調用超類型構造函數。須要的是超類型的原型的一個副本。
本質:使用寄生式繼承來繼承超類型的原型,而後再將指定給子類型的原型。
沒有使用new關鍵字

function inheritProtoype ( subType, superType ) {
    
    var prototype = Object.create(superType.prototype);  // 建立對象
    prototype.constructor = subType; // 加強對象
    subType.prototype = prototype; // 指定對象

}
// 1: 建立超類型的原型的一個副本。
// 2:爲建立的副本添加添加constructor屬性,從而彌補由於重寫原型而市區去的constructor屬性  
// 3: 將新建立的對象,賦值給子類型的原型。

函數表達式

經過name 能夠訪問函數名(非標準屬性)

關於函數聲明,重要特徵就是函數聲明提高:代碼執行以前會先讀取函數聲明。

匿名函數(也叫拉姆達函數): function 關鍵字後面沒有標識符
匿名函數的name屬性時空字符串

遞歸

遞歸函數: 一個函數經過名字調用自身的狀況

function factorial (num) {
    if ( num <= 1 ) { // 遞歸出口
        return 1;
    } else {
        return num * arguments.callee(num-1); // 遞歸點
    }
}

命名函數表達式:

var factorial = (function f ( num ) {
    if ( num <= 1 ) {
        return 1;
    } else {
        return num * f(num-1);
    }
});

建立了一個名爲f() 的命名函數表達式,而後將它賦值給變量factorial。即便把函數賦值給另外一個變量,函數的名字f仍然是有效的。

閉包

閉包:指有權訪問另外一個函數中做用域中的變量的函數

表現:在一個函數內部建立另外一個函數

當某個函數被調用的時候,會建立一個執行環境(execution context)及相應的做用域鏈。而後使用arguments和其它命名參數的值來初始化函數的活動對象(activation object)。
在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位直至做用域鏈的終點的全局執行環境。

後臺的每一個執行環境都有一個表示變量的對象---變量對象

做用域鏈中至少包含二個變量對象:本地活動對象和全局變量對象。
做用域鏈本質:一個指向變量對象的指針列表,引用但不實際包含變量對象。

不管何時在函數中訪問一個變量時,就會從做用域鏈中搜索具備相應名字的變量。
通常來說,當函數執行完畢後,局部活動對象就會被銷燬,內容中近保存全局做用域(全局執行環境的比變量對象)。
可是,閉包能夠延長變量的生存週期

function createComparisonFunction( propertyName ) {

    return function ( object1, object2 ) {

        var val1 = object1[propertyName];
        var val2 = object1[propertyName];

        if ( val1 < val2 ) {

            return -1;

        } else if ( val1 > val2 ) {

            return 1;

        } else {

            return 0;

        }

    }
}


var compare = createComparisonFunction('name');
var reslut = compare({name: 'cyan'}, {name: 'tan'});

// 告知垃圾回收機制將其清除,
// 隨着匿名函數的做用域鏈被銷燬,其它做用域(除了全局做用域)也均可以安全的銷燬。
compare = null; // 接觸對匿名函數的引用(以便釋放內存)

createComparisonFunction()函數執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象。
當createComparisonFunction()函數執行返回後,其執行環境的做用域鏈會被銷燬,但它的活動對象仍會留在內存中,直至匿名函數被銷燬。createComparisonFunction()的活動對象纔會被銷燬。

clipboard.png

閉包的問題:
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用過多的內存。過分使用閉包可能會致使內存佔用過多,V8引擎優化後,JavaScript引擎會嘗試回收被閉包占用的內存。

閉包與變量
閉包只能取得包含函數中任何變量的最後一值。(循環嵌套函數的i問題)
閉包所保存的是整個變量對象,而不是某個特殊的變量。

function createFunctions () {

    var reslut = [];

    for ( var i=0; i<10; i++ )  {

        reslut[i] = function (num) {

            return function () {

                return num;

            }

        }(i);

    }

    return reslut;
    
}

定義一個匿名函數,並將當即執行該匿名函數的結果賦給數組。因爲函數參數是按值傳遞的,因此就會將變量i的當前值複製給參數num。而在這個匿名函數內部,有建立並返回了一個訪問num的閉包。這樣,reslut數組中的每一個函數都有本身num變量的一個副本,所以能夠返回各自不一樣的數值。

關於this對象

this對象是運行時就函數執行環境綁定的:在全局函數中,this等於window,而當函數被做爲某個對象的方法調用時,this等於那個對象。
匿名函數的執行環境具備全局性,所以其this對象一般指向window。但有時候因爲編寫閉包的方式不一樣,這一點不明顯。(在經過call()或apply()來改變函數執行環境的狀況下,this就會指向其它對象。)

var name = 'window';

var object = {
    name: 'object',

    getNameFunc: function () {

        return function () {

            return this.name;
            
        }

    }

}

console.log( object.getNameFunc()() );

先建立了一個全局變量name,有建立一個包含name屬性的對象。這個對象包含一個方法--getNameFunc(); 它返回一個匿名函數,而匿名函數又返回this.name。因爲getNameFunc();返回一個函數,所以調用object.getNameFunc()(); 就當即調用它返回的函數,結果返回一個字符串。 結果的name變量的值是全局的。爲何匿名函數沒有取得其包含做用域(或外部做用域)的this對象呢?

每一個函數在被調用時都會自動取得兩個特殊變量:this和arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這個兩個變量。

能夠經過保存this引用來訪問

var name = 'window';

var object = {
    name: 'object',

    getNameFunc: function () {

        var self = this;

        return function () {

            return self.name;

        }

    }

}

console.log( object.getNameFunc()() );

this 和arguments存在一樣的問題,若是想訪問做用域中的arguments對象,必須將對該對象的引用保存到另外一個閉包可以訪問的變量中。

內存泄漏
若是閉包的做用域鏈保存着一個HTML元素,就意味着該元素將沒法被銷燬

閉包會引用包含函數的整個活動對象。
包含函數的活動中也仍然會保存一個引用。所以,有必要把element變量設置null。這樣可以接觸對DOM對戲那個的引用。順利地減小其引用數據,確保正常回收其佔用的內存。

模仿塊級做用域
JavaScript中沒有塊級做用域的概念

function outputNumbers ( count ) {
    for ( var i=0; i<count; i++ ) {
        console.log(i);
    }
    console.log(i); // 計數
}

變量i是定義在outputNumbers()的活動對象中,所以從它有定義開始,就能夠在函數內部隨處訪問它。

匿名函數用來模仿塊級做用域並避免這個問題。

塊級做用域:稱爲私有做用域

(function () {})();

將函數聲明包含在一堆圓括號中,表示它其實是一個函數表達式,而緊隨其後的另外一對圓括號會當即調用這個函數。

JavaScript將function 關鍵字做一個函數聲明的開始,而函數後聲明不能跟圓括號。而後函數表達式的後面能夠跟圓括號。
將函數聲明轉換成表達式使用()
需求:只要臨時須要一些變量,就可使用私有化做用域

function outputNumbers ( count ) {
    (function () {
        for ( var i=0; i<count; i++ ) {
            console.log(i);
        }
    })();
    console.log(i); // 報錯
}
// 變量i 只能在循環中使用,使用後即被銷燬,而在私有化做用域中可以訪問變量count,由於這個匿名函數是一個閉包,它可以訪問包含做用域中的全部變量。

在匿名函數中的定義的任何變量,都會在執行結束時被銷燬。

結果:減小閉包占用的內存問題,由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈了。

私有變量

特權方法:有權訪問私有變量和私有方法的公有方法。
做用:封裝性,隱藏那些不該該被被直接修改的屬性,方法。
缺點:必須使用構造函數模式來達到這個目的。
構造函數自己是有缺點:對每一個實例都是建立一樣一組新方法。

構造函數中定義特權方法:

function MyObject () {

    // 私有變量和私有函數
    var privateVariable = 10;

    function prvateFunction () {
        return false;
    }

    // 特權方法
    this.publicMethod = function () {
        privateVariable++;
        return prvateFunction();
    }

}

靜態私有變量
目的:建立靜態變量會由於使用原型而增進代碼的複用,但沒有實例對象都沒有本身的私有變量。

使用閉包和私有變量缺點:多查找做用域中的一個層次,就會在必定程度上影響查找速度。

模塊模式

目的:爲單例建立私有變量和特權方法。
本質:對象字面量定義的是單例的公共接口

單例:只有一個實例的對象。
慣例:JavaScript是以對象字面量的方式來建立單例對象。
做用:對單例進行某些初始化,同時又須要維護其私有化變量。

//經過 一個私有變量來控制是否 實例化對象, 初始化一個 init。

var Ext = {};

Ext.Base = (function () {

//私有變量 控制返回的單體對象
var uniqInstance;  

//須要一個構造器  init 初始化單體對象的方法
function Init () {
    
    //私有成員
    var a1 = 10;
    var a2 = true;
    
    var fun1 = function () {
        
        console.log( a1 );
        
    }
    
    return {
        attr1: a1,
        attr2: a2,
        fun1: fun1
    }
    
}

return {
    
    getInstance: function () {
        
        if ( !uniqInstance ) {  //不存在 ,建立單體實例
            
            uniqInstance = new Init();                    
            
        }
        
        return uniqInstance;
        
    }
    
}

})()

var init = Ext.Base.getInstance();

init.fun1(); //10

使用需求:建立一個對象並以某些數據對齊初始化,同時還要公開一些可以訪問這些私有數據方法。
每一個單例都是Object的實例,由於經過一個對象字面量表示單例一般都是 全局對象存在,不會將它傳遞給一個函數。

加強的模塊模式
在返回對象以前假如對其加強的代碼。

var application = function(){
    //私有變量和函數
    var components = new Array();

    //初始化
    components.push(new BaseComponent());

    //建立 application 的一個局部副本
    var app = new BaseComponent();

    //公共接口
    app.getComponentCount = function(){

        return components.length;
    }

    app.registerComponent = function(component){

        if (typeof component == "object"){
            components.push(component);
        }

    }

    //返回這個副本
    return app;

}();

BOM

window對象

BOM的核心對象是window,表示瀏覽器的一個實例。
在瀏覽器中,window對象有雙重角色:
1:JavaScript訪問瀏覽器窗口的一個接口
2: ECMAScript規定的Global對象。
在網頁中定義的任何一個對象,變量,函數,都已window做爲其Global對象。所以有權訪問parseInt()等方法。

窗口與框架
使用框架時,每一個框架都有本身的window對象以及全部原生構造函數及其它函數的副本。每一個框架都保存在frames集合中,能夠經過位置或經過名稱來訪問。
top對象始終指向最外圍的框架,也就是整個瀏覽器窗口。
parent 對象表示包含當前框架的框架,而 self 對象則回指 window 。

全局做用域
定義全局變量與在window對象上直接定義屬性差異:
全局變量不能經過delete操做刪除,直接定義window對象上的定義屬性能夠刪除。

var age = 22;
window.color = "red";

//在 IE < 9 時拋出錯誤,在其餘全部瀏覽器中都返回 false
delete window.age;

//在 IE < 9 時拋出錯誤,在其餘全部瀏覽器中都返回 true
delete window.color; // returns true

console.log(window.age); //22 29

console.log(window.color); // undefined

var 語句添加的window屬性[Configurable]的特性,這個特性的值被設置爲false。因此定義的不可delete操做符刪除。

嘗試訪問未聲明的變量會拋出錯誤,可是經過window對象,能夠知道某個可能未聲明的變量是否存在。

var newValue = oldValue; // 報錯,未定義

var newValue = window.oldValue; // 屬性查詢,不會拋出錯誤

location對象

提供了與當前窗口中加載的文檔有關的信息,提供一些導航功能。
location對象很特別,便是window對象的屬性,也是document對象的屬性,window.location 和 document.location引用的是同一個對象。

做用:保存着當前文檔的信息,還表現將URL解析爲獨立的片斷。

屬性 例子 說明
hash "#contents" 返回URL中的hash(#號後跟零或多個字符),若是URL中不包含散列,則返回空字符串
host "www.aa.com:80" 返回服務器名稱和端口號(若是有)
hostname "www.aa.com" 返回不帶端口號的服務器名稱
href "http:/www.aa.com" 返回當前加載頁面的完整URL。而location對象的toString()方法也返回這個值
pathname "/WileyCDA/" 返回URL中的目錄和(或)文件名
port "8080" 返回URL中指定的端口號。若是URL中不包含端口號,則這個屬性返回空字符串
protocol "http:" 返回頁面使用的協議。一般是http:或https:
search "?q=javascript" 返回URL的查詢字符串。這個字符串以問號開頭

位置操做
location對象改變瀏覽器位置

location.assign('http://segmentfault.com'); // 打開新的URL並在瀏覽器的歷史記錄中生成一條記錄。

若是將location.href 或window.location設置爲一個URL,會以該值調用assign()方法。

window.location = "http://www.segmentfault.com";
location.href = "http://www.segmentfault.com";

最經常使用的方式:location.href = "http://www.segmentfault.com";

禁止用戶使用‘後退’按鈕 使用location.replace();
參數:須要導航的URL
location.relaod(); 做用:從新加載當前顯示的頁面

location.reload(); //從新加載(有可能從瀏覽器緩存中加載)
location.reload(true); //從新加載(從服務器從新加載)

location.reload();調用以後的代碼可能會也可能不會執行,取決於網絡延遲或系統資源等因素。若是使用location.relaod();最好放在代碼最後一行。

navigator對象

做用:識別客戶端瀏覽器

navigator.userAgent // 瀏覽器的用戶代理字符串

檢測插件
非IE下使用:navigator.plugins數組

function hasPlugin(name){
    name = name.toLowerCase();
    for (var i=0; i < navigator.plugins.length; i++){
        if (navigator. plugins [i].name.toLowerCase().indexOf(name) > -1){
            return true;
    }
}
return false;

註冊處理程序
registerContentHandler() 和 registerProtocolHandler()
做用:讓一個站點指明它能夠處理特定類型的信息。(使用範圍:RSS 閱讀器和在線電子郵件程序)

registerContentHandler();
參數1:要處理的MIME類型
參數2:能夠處理該MIME類型的頁面URL
參數3:應用程序的名稱

// 站點註冊爲處理 RSS 源的處理程序
navigator.registerContentHandler("application/rss+xml","http://www.somereader.com?feed=%s", "Some Reader");
// 參數1:RSS源 的MIME類型參數  2:應該接收 RSS源 URL的 URL,其中的%s 表示RSS 源 URL,由瀏覽器自動插入。
// 做用:當下一次請求 RSS 源時,瀏覽器就會打開指定的 URL,而相應的Web 應用程序將以適當方式來處理該請求。

screen對象

代表客戶端能力:瀏覽器窗口外部的顯示器信息,如:像素寬度和高度。

window.screen.height // 屏幕的像素高度
window.screen.windth // 屏幕的像素寬度

window.resizeTo(); // 調整瀏覽器窗口大小

history對象

history 對象:保存用戶上網的歷史記錄。充窗口被打開的那一刻算起。
history是window對象的屬性,所以每一個瀏覽器串窗口,每一個標籤頁乃至每一個框架,都有本身的history對象與特定的window對象關聯。

history.go(); 在用戶的歷史記錄中任意跳轉。

//後退一頁
history.go(-1);
//前進一頁
history.go(1);
//前進兩頁
history.go(2);

// go()參數是字符串 // 可能後退,也可能前進,具體要看哪一個位置最近
//跳轉到最近的 wrox.com 頁面
history.go("segmentfault.com");

history.length; 保存着歷史記錄的數量
包括全部歷史記錄,即全部向後向前的記錄。對於加載到窗口,標籤頁或框架中的第一個頁面而言。

高級技巧

高級函數

安全的類型檢測

JavaScript 中內置的類型檢測機制並不是徹底可靠

typeof操做符,因爲它有一些沒法預知的行爲,致使檢測數據類型時獲得不靠譜的結果。(Safari直至第四版,對正則表達式 typeof 檢測 會返回 'function')

instanceof操做符,存在多個全局做用域(像一個頁面中包含多個frame)的狀況下。

var isArray = valeu instaceof Array; 
// 返回true 條件:value 必須是數組, 必須與Array構造函數在同個全局做用域中。(Array 是 window的屬性)。

在任何值上調用Object原生的toString();方法。
返回 [object NativeConstructorName]格式的字符串。每一個類在內部都有一個[[Class]]屬性,這個屬性中指定了這個字符串中的構造函數名.

應用:
檢測原生JSON對象。

var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) === '[object JSON]';

做用域安全的構造函數

做用:自定義對象和構造函數的定義和用法。
構造函數就是一個使用new操做符調用的函數。當使用new調用時,構造函數內用到的this對象會指向新建立的對象實例。

問題:當沒有使用new操做符來調用該構造函數的狀況下。this對象是在運行時綁定的,因此直接調用 類名 ,this會映射到全局對象window上,致使錯誤對象屬性的之外增長。

function Person ( name, age ) {

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

}

var p1 = Person('cyan', 22);

console.log(window.name);
console.log(window.age);
// Person實例的屬性被加到window對象上,由於構造函數時做爲普通函數調用,忽略了new操做符。由this對象的晚綁定形成的,在這裏this被解析成了window對象。

// 在類中添加判斷:
function Person ( name, age ) {

    if ( this instanceof Person ) {

        this.name = name;
        this.age = age;
    
    } else {

        return new Person(name, age);

    }

}

var p1 = Person('cyan', 22);

console.log(window.name);
console.log(window.age);
        
// 添加一個檢查,並確保this對象是Person實例。
// 要麼使用new操做符,要麼使用現有的Person實例環境中調用構造函數。

函數綁定
函數綁定要建立一個函數,能夠在待定的this環境中指定參數調用另外一個函數。
使用範圍:回調函數與事件處理程序一塊兒使用,(在將函數做爲變量傳遞的同時保留代碼執行環境)

// bind(); 函數
function bind ( fn, context ) {
    return function () {
        return fn.apply(context, arguments);
    }
}

將某個函數以值的形式進行傳遞,同時該函數必須在特定環境中執行,被綁定的函數的效果。
主要用於事件處理程序以及setTimeout() 和 setInterval();
被綁定函數與普通函數相比有更多開銷,須要更多內存支持,同時也由於多重函數調用調用比較慢。最好只在必要時使用。

函數柯里化

函數柯里化( function currying ): 把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並返回接受餘下的參數且返回結果的新函數

做用:建立已經設置好的一個或多個參數的函數。

函數柯里化的基本方法:使用一個閉包返回一個函數。

函數柯里化和函數綁定區別:當函數被調用時,返回的函數還須要設置一些傳入的參數。

函數柯里化的動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要的參數。

用處:
做爲函數綁定的一部分包含在其中,構造出更爲複雜函數

function bind ( fn, context ) {
    let args = Array.prototype.slice.call(arguments, 2);

    return {
        let innerArgs = Array.prototype.slice.call(arguments);
        let fianlArgs = args.concat(innerArgs);
        return fn.apply(context, fianlArgs);
    }

}

缺點:每一個函數都會帶來額外的開銷.不可以濫用。

相關文章
相關標籤/搜索