JS學習筆記整理五 面向對象的程序設計

面向對象的程序設計

理解對象

屬性類型

對象的屬性類型包含:數據屬性、訪問器屬性javascript

  1. 數據屬性vue

    • 可配置:Configurable,是否能用delete刪除。可否修改屬性特性。此值一旦設爲false,就不可逆。
    • 可修改:Writable,可否修改value值。
    • 可枚舉:Enumerable,可否經過for...in枚舉。
    • 值:Value
  2. 訪問器屬性java

    沒有value和writable,多了一對get和set。數組

    無論writable真假,只要configurable爲真,經過Object.defineProperty就能把特性改成訪問器屬性。瀏覽器

Object.getOwnPropertyDescriptor(o,」prop」);:讀取屬性特性。安全

Object.getOwnPropertyDescriptors(o);:讀取全部屬性特性。閉包

犀牛書裏把訪問器accessor稱爲存取器,我以爲這樣更形象。可使用直接量語法的擴展語法來定義屬性,好比下面兩種方法:app

var o={
    name:'tiedan',
    get ga(){return this.name},
    set ga(value){this.name=value}
}
var p={name:'tiedan'};
Object.defineProperty(p,'ga',{
    set:function(value){this.name},
    get:function (){return this.name}});
console.log(Object.getOwnPropertyDescriptors(o));
console.log(Object.getOwnPropertyDescriptors(p));
/*{ name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get ga],set: [Function: set ga],enumerable: true,configurable: true } } { name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get],set: [Function: set],enumerable: false,configurable: false } }*/
//方括號裏有細微區別。 
複製代碼

使用Object.defineProperty或者Ojbect.defineProperties,不顯示定義configurable、writable、enumerable,則這三個特性默認值都爲false函數

vue的核心實現就是利用Object.definProperty劫持屬性的get和set特性,來實現雙向綁定的。測試

Object.preventExtensions對象不能添加新屬性(可刪除原有屬性)。可經過Object.isExtensible來檢測對象是否能擴展。

Ojbect.seal能夠密封對象,密封后的對象不能刪除和添加屬性。全部屬性特性的[[configurable]]都爲false。可經過Object.isSeal來檢測對象是否爲密封對象。

Object.freeze能夠凍結對象,比Object.seal更進一步,對象不能有任何變化。全部特性均爲false。可經過Object.isFrozen來檢測對象是否爲凍結對象。

經過Object.getOwnPropertyNames看到的Object.prototype的屬性,這部分屬性都是可繼承的。

[ 'constructor',             //給全部對象的構造器屬性,建立函數時,就會自動建立函數原型,和其constructor屬性,若是重寫原型,則繼承這個
 '__defineGetter__',      //一些瀏覽器的非標準方法,在Object.defineProperty不支持的時候,能夠嘗試用這個定義屬性的特性。
 '__defineSetter__',      //同上相似
 'hasOwnProperty',     //檢測對象是否有自有的屬性
 '__lookupGetter__',    //非標準方法,用來返回命名屬性的Getter方法
 '__lookupSetter__',    //同上相似
 'isPrototypeOf',         //是否爲檢測對象的原型。其原理同instanceof同樣都是查找原型鏈,只是instanceof後面是構造函數
 'propertyIsEnumerable',//屬性是不是可枚舉的,這個和Object.getOwnPropertyDescriptor()裏獲得的enumberable屬性是同樣的
 'toString',                  //
 'valueOf',                  //
 '__proto__',               //通常是內部屬性,一個原型指針
 'toLocaleString' ]     //
複製代碼

也能夠經過Object.getOwnPropertyNames看看Object的屬性和方法。

建立對象

犀牛書例子,最早由道格拉斯.克羅克福德提出,這我的在兩本書裏出現N屢次。下面的是犀牛書p122的例子,紅皮書相似的例子在p169。 Object.create()建立一個新對象,參數是新對象的原型。create方法有兩個參數,第二個參數與defineProperties的第二個參數同樣。

function inherit(p){
    if(p==null) throw TypeError();
    //首先排除null,自己typeof就可能有null。Object.create是能夠傳null的
    if(Object.create){
        return Object.create(p);
    }
    var t=typeof p;
    if(t!="object"&&t!="function")throw TypeError;//不能是基本類型值
    function a(){}
    a.prototype=p;
    return new a();
  }
複製代碼

new的實現大體以下:

o = {};
o.__proto__=f.prototype;
f.apply(o,arguments);
複製代碼

因此new Object();不如直接用{}執行快就是由於這個緣由嗎?

自定義的new以下:

function New(f) {
  //返回一個func
  return function () {
    o = {};
    o.__proto__=f.prototype;
    f.apply(o, arguments);//繼承父類的屬性
    return o; //返回一個Object
  }
}
複製代碼

經過new fn();或者Object.create(p);其實都是返回新對象的方法。

工廠模式

「生產對象」,因此通常直接調用。不使用new(使用也能返回正常對象)。

function Person(name,age){
    var o=new Object();
    o.name=name;
    o.age=age;
    return o;
}
複製代碼

弊端:解決了對象建立問題,但沒有解決對象識別問題

構造函數模式

function Person(name,age){
    this.name=name;
    this.age=age;
}
複製代碼

沒有顯示建立對象,直接將屬性和方法賦給this,(能夠)沒有return。經過new來調用。

實際上步驟:

var o={};//建立新對象
o.__proto__=fn.prototype;//對象的原型指針賦值
fn.apply(o,arguments);//this指向新對象,執行構造函數代碼
return o;//返回新對象
複製代碼

new fn()就是相似上面的過程。構造函數fn的執行實際上就是給o對象賦值操做的過程。

弊端:方法重複定義,即使放到全局也只適合對象調用,還容易污染全局做用域。

原型模式

function Person(){
}
Person.prototype.name='tiedan';
Person.prototype.age='1';
複製代碼

對於原型有兩種寫法:

fn.prototype.p1=value1;
fn.protptype.p2=value2;
複製代碼
fn.prototype={
  p1:value1,
  p2:value2
}
複製代碼

第二種看起來更清楚,可是存在問題,由於這至關於重寫原型對象。以前定義好的一些屬性方法可能會丟失,constructor屬性須要顯示從新指向構造函數。要不就沒了。在第二種重寫原型以前不能先new fn,這會致使重寫原型以後切斷了構造函數fn和最初的原型之間的聯繫。由於prototype屬性自己存儲的就是一個指針。而對象和原型之間的聯繫僅靠__proto__存儲的一個指針。至關於原型鏈出了問題,沒法經過這個屬性找到新的原型對象,得到新對象的屬性和方法。說白了就是fn和obj指向了不一樣的原型對象。

function Person(name,age){
    this.name=name;
    this.age=age;
}
Person.prototype.height=100;
var p1=new Person("tiedan",36);
Person.prototype={
    constructor:Person,
    height:180
}
var p2=new Person("someone",29);
console.log(p1 instanceof Person);//false,由於其__proto__指向的已經不是最新的Person.prototype。經管如此,p1.constructor.prototype仍是對的。
console.log(p1.height);//100
console.log(p2 instanceof Person);//false
console.log(p2.height);//180
複製代碼

弊端:除了上面所說的,還有就是由於原型對象的屬性和方法都是實例共享的。那麼對於屬性是引用類型,好比數組,你們也是共享的同一個引用對象。修改這類屬性值,實際上會影響到全部實例。

建議屬性值是引用類型的,不要用原型模式,除非你就須要這種共享引用類型的方式。

in運算符有兩種用法,一種用於for...in。另外一種在單獨使用時,in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性存在於實例中仍是原型中!

function hasPrototypeProperty(object,name){
  return !object.hasOwnProperty(name)&&(name in object);
}
複製代碼

動態原型模式

function Person(name,age){
    this.name=name;
    this.age=age;
    if(typeof this.sayName!='function'){
        Person.prototype.sayName=function(){console.log(this.name);}
    }
}
複製代碼

相對比較完美的一種模式。

使用這種模式,一樣不能用對象字面量重寫原型。思考一下new的實現步驟,就能明白,構造函數裏重寫原型,則必然會在o對象建立以後切斷與原有原型的聯繫。

寄生構造函數模式

function Person(name,age){
    var o=new Object();
    o.name=name;
    o.age=age;
    return o;
}
var tiedan=new Person('鐵蛋',1);
console.log(tiedan instanceof Person);//false
複製代碼

這個模式和工廠模式的函數徹底同樣,只是這個直接經過new來調用。測試了一下,當作構造函數使用,末尾return會代替構造函數正常的return值。

弊端:返回的對象和構造函數沒什麼關係。不能依賴instanceof來檢測對象。(返回的其實是裏面的o類型)

穩妥構造函數模式

durable object持久對象 克羅克福德發明

function Person(name,age){
    var o=new Object();
    o.sayName=function(){
        alert(name);
    };
    return o;
}
var tiedan=Person('鐵蛋');
複製代碼

相似於寄生構造函數模式。沒有公共屬性,不引用this對象。適合在安全環境或防止數據被其餘應用程序改動時使用。1.不引this,2.不用new

實際上是利用了閉包原理。即使對象被添加屬性和方法,也沒法篡改原始值。

繼承

繼承分爲接口繼承和實現繼承

紅皮書p162說es中沒法實現接口繼承,其實es4版本是實現了接口繼承的(as3.0)。說不定哪天interface就從保留字變關鍵字了。

原型鏈

  1. 別忘記默認原型:Object.prototype

  2. 肯定原型和實例的關係,instanceofisPropertyOf

  3. 謹慎的定義方法,方法覆蓋,紅皮書p166必須用SuperType實例替換SubType的原型後再定義原型方法,不然原型對象一重寫就沒了。

  4. 原型鏈的問題:

    相似以前原型模式引用類型的問題。SubType.prototype=new SuperType();實際上就是重寫原型。原型屬性包含引用類型就容易出現意料以外的狀況。

借用構造函數

constructor stealing 明明是偷非說是借~

function SubType(age){
 SuperType.call(this,"tiedan");
 this.age=age;
}
複製代碼
  1. 可向父類構造函數傳參
  2. 問題:父類原型對子類不可見。

組合繼承

將借用構造函數和原型鏈技術結合

  1. 借用構造函數獲得實例屬性

  2. 原型鏈技術繼承原型屬性和方法,同時還能夠擴充本身的原型方法

SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.prop=…
複製代碼

原型式繼承

又是道格拉斯.克羅克福德... 提到了object(o)函數,犀牛書里名爲inherit(p)

var person={};
var anotherPerson=Object.create(person);
anotherPerson.prop=...
//繼承的引用類型會被實例共享
複製代碼

Object.create在使用一個參數的時候,行爲和object(o);相同,使用兩個參數的時候,第二個參數和Object.defineProperties的第二個參數格式相同。以這種方式指定的屬性會覆蓋原型對象的同名屬性。

其實inherit也能夠加第二個參數,也就是作一個淺複製,這能夠參看犀牛書的淺複製。extend方法。

var p2=Object.create({},{"hehe":{value:30 }});
console.log(p2);//{}
//什麼都看不見,由於和Object.defineProperties同樣,enumerable不顯示定義默認爲false,因此看不到。
複製代碼

寄生式繼承

克羅克福德推廣的

function createAnother(o){
    var anotherPerson=Object.create(o);
    anotherPerson.sayHello=function(){console.log('hello');}
    return anotherPerson;
}
var anotherPerson=createAnother({});
anotherPerson.sayHello();
複製代碼

主要考慮對象而不是自定義類型和構造函數的狀況下使用

寄生式繼承的思路與寄生構造函數和工廠模式相似。建立一個用來封裝繼承過程的函數。在函數內部以某種方式加強對象,最後像本身作了全部工做同樣返回對象。

寄生組合式繼承

以前的組合式繼承最大的問題是要調用兩次父類構造函數,一次是借用構造函數,一次是重寫子類原型。其實不必調用兩次構造函數。能夠只調用一次借用構造函數,另外一次寄生式new一個空構造函數。只爲了複製父類原型屬性和方法。

function SuperType(name){
    this.name=name;
    this.colors=['red','green','blue'];
}
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);}

複製代碼

改成:

function inheritPrototype(SubType,SuperType){
    var prototype=Object.create(SuperType.prototype);//建立對象
    prototype.constructor=SubType;//加強對象
    SubType.prototype=prototype;//指定對象,依然重寫原型啊...
}

function SuperType(name){
    this.name=name;
    this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=function(){console.log(this.name);}

function SubType(name,age){
    SuperType.call(this,name);//第二次調用
    this.age=age;
}
inheritPrototype(SubType,SuperType);//第一次調用
SubType.prototype.sayAge=function(){console.log(this.age);}

複製代碼

上面例子依然重寫了原型啊。因此,順序很重要。不然,最後兩行換一下位置,sayAge就被蓋掉了。

相關文章
相關標籤/搜索