JavaScript原型,原型鏈 ? 有什麼特色? JavaScript如何實現繼承?

一、javascript高級程序設計對原型的解釋:

一、原型:

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

原型最初只包含constructor屬性, 這個屬性也是共享的。後邊能夠自行添加須要的屬性和方法。這個屬性又指向函數自己(Person)。java

按照字面的意思來理解,那麼prototype就是經過調用構造函數而建立的那個對象實例的原型對象數組

使用原型對象的好處是能夠讓全部的對象實例共享它所包含的屬性和方法app

換句話說,就是沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中函數

二、原型模式建立對象,(其實最好的是共享方法,不共享屬性)以下代碼:

function Person(){}

Person.prototype.name = "dadaoshenyi";
Person.prototype.age = 26;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
}

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

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

alert(person1.sayName ==person1.sayName);//true,指向堆內存中的相同目標,同一個函數。

函數Person的原型屬性(prototype)指向函數的原型對象①(Person.prototype)(顯示的這個對象),①這個原型對象包含新添加的屬性和方法,還包含一個constructor(構造函數)屬性,②這個屬性包含一個指向①prototype屬性所在函數Person()的指針。測試

對象實例person1包含了一個內部屬性指向__proto__([[Prototype]])(指針)原型對象(Person.prototype)的屬性。這就是它與它的構造函數的關係。 原型鏈。ui

如上圖展現了各個對象之間的關係。this

三、理解原型對象:

不管何時,只要建立一個函數,就會根據一組特定的規則爲函數建立一個prototype屬性這個屬性指向函數的原型對象spa

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

雖然在全部的實現中都沒法訪問到[[prototype]], 可是能夠經過isPrototypeOf()方法來確認對象之間是否存在這種關係。

以下調用:alert(Person.prototype.isPrototypeOf(person1));//true,測試person1的[[prototype]]指向Person.prototype就返回true

調用Object.getPrototypeOf(person1);能夠返回實例對象[[prototype]]的值。

每當代碼讀取到某個對象的某個屬性的時候,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先是從對象實例自己開始。若是在實例中找到了這個給定名字的屬性,就會返回這個屬性的值,若是沒有找到,就會繼續搜索指針[[prototype]](截圖中的__proto__)指向的原型對象,在對象原型中查找具備給定名字的屬性。若是找到就返回這個屬性的值,若是沒找到就會繼續向上搜索,沿着指針[[prototype]](截圖中的__proto__)指向的原型對象。這正是對象實例共享原型所保存的屬性和方法的基本原理。

在對象實例中能夠訪問保存在原型中的值,可是卻不能修改原型中的值,可是能夠在實例中從新賦值,如perosn1.name = "changyangzhe";覆蓋原有的屬性或者方法(就近查找)。

可使用delete person1.name;刪除添加的屬性,從而能夠繼續查找到原型上的屬性。

使用hasOwnPrototype("name");方法來檢測一個屬性究竟是屬於原型仍是實例。

四、原型與in操做符:

有兩種使用方式,for...in與in,

單獨使用的時候,in操做符會在經過對象能訪問到給定屬性的時候返回true,不管這個屬性是在實例中仍是在對象原型中。區別與hasOwnPrototype();方法,查找深度不一樣。

使用for...in循環來遍歷對象中可訪問的、可枚舉的(enumerated)屬性。包括實例中的和原型中的屬性,包括在原型中不可枚舉屬性(將[[Enumerable]]標記爲false)的屬性,由於根據規定,全部開發人員定義的屬性都是可枚舉的——只有在IE8及更早的版本中例外。

Object.keys();方法返回全部可枚舉***實例屬性***的字符串數組。Object.keys(Person.prototype);//"name,age,job,sayName";

想要獲取全部的實例屬性,不管是否可枚舉。使用Object.getOwnPrtotypeNames(Person.prototype);

五、更簡單的原型語法:

減小輸入、從視覺上更好的封裝原型的功能。以下寫法:

function Person(){}

Person.prototype = {
      name:"dadaoshenyi",
      age:26,
      job:"Software Engineer",
      sayName:function(){
             alert(this.name);
      }    
};

以前已經知道:每建立一個函數,就會同時建立它的prototype對象,這個對象也會自動獲取constructor屬性。

這裏本質上是重寫了默認的prototype對象所以constructor屬性也就變成了新的對象的constructor屬性(指向Object構造函數),再也不指向Person函數。

若是須要能夠在Person.Prototype中添加屬性constructor:Person,就仍是原來的樣子。

關於constructor屬性:這是一個對象,其中包含了函數的arguments、caller、length、name等屬性。構造器可原生、可自定義。這種狀況下,constructor屬性的[[Enumerable]]特性被設爲true;原生的狀況下是不可枚舉的。

六、原型對象的動態性:

因爲在原型中查找值的過程是一次搜索,因此咱們對原型對象所作的任何修改都是可以當即從實例上反映出來的——即便先建立實例,後修改原型。

若是重寫原型對象就會致使現有原型與任何之前已經存在的對象實例之間的聯繫的切斷。

七、原生對象的原型:

原生模式的重要性不只體如今建立自定義的類型方面,全部原生的引用類型都是採用這種模式建立的,如打印String.Prototype

經過原生對象的原型,能夠取得默認方法的引用並且能夠定義新的方法能夠向修改自定義對象的原型同樣修改原型對象的原型(雖然不推薦)。以下例子:

String.prototype.startsWith = function (text){

      return this.indexOf(text) == 0;

}

var msg = "hello world!";//這裏使用var msg = new String("hello world!");也是能夠的,應該是使用string類型調用的時候,自動轉化爲了String包裝類型。string與object均可用。

alert(msg.startsWith("hello"));   //true

經典的一個實例:將具備length屬性的對象轉化爲數組。除了IE下的節點組合,IE下對象是以com對象的形式實現的。

Array.prototype.slice.call(arguments);//將類數組轉化爲數組

var a={length:2,0:'first',1:'second'};
Array.prototype.slice.call(a);//  ["first", "second"]
 
var a={length:2};
Array.prototype.slice.call(a);//  [undefined, undefined]

探究這個方法的實現原理: 

首先,slice有兩個用法,一個是String.slice,一個是Array.slice,第一個返回的是字符串,第二個返回的是數組,這裏咱們看第2個。

var a={length:2,0:'first',1:'second'};
Array.prototype.slice;//function slice() { [native code] }
Array.prototype.slice.call(a);//["first", "second"]

slice() 方法可從已有的數組中返回選定的元素。
語法   arrayObject.slice(start,end)
start    必需。規定從何處開始選取。若是是負數,那麼它規定從數組尾部開始算起的位置。也就是說,-1 指最後一個元素,-2 指倒數第二個元素,以此類推。
end    可選。規定從何處結束選取。該參數是數組片段結束處的數組下標。若是沒有指定該參數,那麼切分的數組包含從 start 到數組結束的全部元素。若是這個參數是負數,那麼它規定的是從數組尾部開始算起的元素。

轉成數組的通用方法:

var toArray = function(s){
      try{
            return Array.prototype.slice.call(s);
      } catch(e){
            var arr = [];
            for(var i = 0,len = s.length; i < len; i++){
                   //arr.push(s[i]);
                   arr[i] = s[i];  //聽說這樣比push快
             }
             return arr;
      }
}

八、原型對象的問題:

對於建立的對象實例,全部的方法和屬性都是共享的,修改也不方便。

九、最經常使用的建立object模式:(組合使用構造函數模式和原型模式)

建立自定義類型的最多見方式,就是組合使用構造函數模式與原型模式。

構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性。這樣,每一個實例都會有本身的一份實例屬性的副本,但同時有共享着對方法的引用,最大限度的節省了內存。集兩種模式之長。具體的例子以下:

//構造函數模式用於定義實例屬性
function Person(name,age,job) {
     this.name = name;
     this.age = age;
     this.job = job;
}

//原型模式用於定義方法和共享的屬性
Person.prototype = {
     constructor:Person,
     sayName:function() {
            alert(this.name);
     }
}

10 、如何判斷一個對象是否屬於某個類? 

一、typeof  形如 var x = "xx";  typeof x == 'string'
    返回類型有:'undefined' 「string」 'number' 'boolean'  'function'  'object'   
    缺點:對於object類型不能細分是什麼類型 
    優勢:對空null的判斷 'undefined'的應用
二、instanceof 形如 var d = new String('test'); d instanceof String ==true 
  返回的類型有:String Number Boolean Function Object Array Date
  優勢:能區分出更細的類型如 Date Array 如
var num = 3; num instanceof Number 能返回具體的類型
  缺點:直變量不能區分 必須採用new 的對象
三、constructor 形如:var x = []; x.constructor==Array   優勢:能夠返回繼承的類型   缺點: 不能對象的細分,如繼承 必須手動修正 4、Object.prototype.toString.call();   優勢:通用,返回"[object String]" 具體object的類型   缺點:不能返回繼承的類型
var type = function(v){
    return Object.prototype.toString.call(v);
};
console.log(type(null));//[object Null]
console.log(type(undefined));//[object Undefined]
console.log(type(1));//[object Number]
console.log(type(true));//[object Boolean]
console.log(type("2"));//[object String]
console.log(type([1,2,3]));//[object Array]
console.log(type({"name":"zhuhui"}));//[object Object]
console.log(type(type));//[object Function]
console.log(type(new Date()));//[object Date]
console.log(type(/^\d+$/));//[object Regexp] 

二、javascript高級程序設計對原型鏈的解釋:

一、原型鏈:實現繼承

ECMAScript只支持實現繼承,並且其實現繼承主要依靠原型鏈來實現。

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

構造函數、原型與原型實例三者的關係:

每一個構造函數都有一個原型對象(prototype),原型對象都包含一個指向構造函數的指針(constructor),而實例都包含一個指向原型對象的內部指針[[prototype]]

       

讓起始的實例的原型對象等於另外一個類型的實例,此時的原型對象將包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另外一個構造函數的指針。假如另外一個原型又是另外一個類型的實例,這樣層層遞進就構成了實例與原型的鏈條。這就是原型鏈的基本概念。

也就是說原本實例的內部指針指(__proto__)向本身的原型,本身原型的內部指針(__proto__原本指向於Object對象,如今更改了這個指向,讓它指向另一個對象(這是一個實例,也就是被繼承者),顯然這個實例也是有一個(__proto__),而後就能夠一直傳遞下去,這就是原型鏈,這就是繼承。

Javascript中,有一個函數,執行時對象查找時,永遠不會去查找原型,這個函數是?

固然是Object.hasOwnProperty(proName);方法了,該方法只會在當前對象上查找是否有須要的屬性值,不會經過原型鏈向上查找。

二、經常使用的一種基本模式的原型鏈實現: 

function SuperType(){//類型1的構造函數,以及一個屬性
     this.property = true;
}

SuperType.prototype.getSuperValue = function(){//類型1的原型方法
     return this.property;
}

function SubType(){//類型2的構造函數,以及一個屬性
     this.subproperty = false; 
}

//繼承了SuperType,類型2的原型對象重寫爲類型1的實例對象。
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){//類型2的添加的一個原型方法
      return this.subproperty;
}

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

本質上就是,讓類型2(SubType)繼承了類型1(SuperType),繼承是經過建立SuperType的實例,並將其賦值給SubType.prototype實現的

這樣,本來屬於類型1的實例中的全部屬性和方法,如今也存在於類型2的原型中了。而後能夠隨意在類型2的原型上添加方法了。

 

三、別忘記默認的原型:

全部的引用類型默認都繼承了Object,而這個繼承也是經過原型鏈來實現的。

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

 

四、肯定原型和實例的關係:

兩種方法:

方法一、

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

方法二、

alert (Object.prototype.isPrototypeOf(instance));//true
alert (SuperType.prototype.isPrototypeOf(instance));//true

五、謹慎的定義方法:

在子類型中覆蓋或者添加新的方法,都要放在替換原型語句以後(重寫以後)。

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

如這樣使用字面量添加信的方法,會致使繼承被切斷,由於至關於用了一個新的原型代替了以前的繼承的原型。

六、原型鏈的問題:

問題一、就是在使用構造函數結合原型來實現對象建立的時候,方法是原型共享的,屬性是是實例共享的。使用原型繼承會讓SuperType(類型1)的屬性成爲類型2的原型屬性。回到了原型建立對象的問題上了。

問題二、建立子類型的實例的時候不能想超類型的構造函數中傳遞參數。實際上應該說沒有辦法在不影響全部實例的狀況下,給超類型的構造函數傳遞參數。

七、經常使用的繼承的方法(最實用):

組合繼承:也稱僞經典繼承,指的是將原型鏈與借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。

原理就是:使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例的繼承。這樣既能在原型上定義方法實現了函數複用,又可以保證每一個實例都有本身的屬性。

function SuperType(name){//對象類型1的屬性
     this.name = name;
     this.colors = ["blue","red","green"];
}

SuperType.prototype.sayName = function(){//對象類型1的方法
     alert(this.name);
}

function SubType(name,age){//對象類型2的屬性,其中屬性實現了繼承,而且可給屬性值傳遞參數。
     //繼承屬性,借調了超類(類型1)的構造函數。而後定義本身的屬性。
     SuperType.call(this,name);
     this.age = age;
}

//繼承方法,制定構造器函數,添加本身的方法。
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType();
SubType.prototype.sayAge = function(){
      alert(this.age);
};

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

var instance1 = new SubType("Changyangzhe",25);
alert(instance1.colors);//"red,blue,green"
instance1.sayName();//"Changyangzhe"
instance1.sayAge();//25
//call方法,用於參數個數已知,改變了上下文環境。
語法:call([thisObj,[,arg1[, arg2[,   [,.argN]]]]]) 
定義:調用一個對象的一個方法,以另外一個對象替換當前對象。

call 方法能夠用來代替另外一個對象調用一個方法call 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。 
若是沒有提供 thisObj 參數,那麼 Global 對象被用做 thisObj。 

這裏SuperType構造函數定義了兩個屬性:name和colors。SuperType的原型定義了一個方法sayName()。本來的類型2的constructor指向的是SuperType,這裏從新指向類型2的實例是它本身SubType。

八、區別apply、call和bind:

都是更改函數的做用域上下文,bind使用apply來內部實現,返回一個函數,便於之後調用。apply和bind的傳入的參數不一樣,當即調用。

相關文章
相關標籤/搜索