走在前端的大道上javascript
本篇將本身讀過的相關 javascript原型和原型鏈 文章中,對本身有啓發的章節片斷總結在這(會對原文進行刪改),會不斷豐富提煉總結更新。前端
通常的初學者,在剛剛學習了基本的javascript語法後,都是經過面向函數來編程的。以下代碼:java
var decimalDigits = 2, tax = 5; function add(x, y) { return x + y; } function subtract(x, y) { return x - y; } //alert(add(1, 3));
經過執行各個函數來獲得最後的結果。可是利用原型,咱們能夠優化一些咱們的代碼,使用構造函數:
首先,函數本體中只存放變量:git
var Calculator = function (decimalDigits, tax) { this.decimalDigits = decimalDigits; this.tax = tax; };
其具體的方法經過prototype屬性來設置:編程
Calculator.prototype = { add: function (x, y) { return x + y; }, subtract: function (x, y) { return x - y; } }; //alert((new Calculator()).add(1, 3));
這樣就能夠經過實例化對象後進行相應的函數操做。這也是通常的js框架採用的方法。segmentfault
原型還有一個做用就是用來實現繼承。首先,定義父對象:框架
var BaseCalculator = function() { this.decimalDigits = 2; }; BaseCalculator.prototype = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; } };
而後定義子對象,將子對象的原型指向父元素的實例化:編程語言
var Calculator = function () { //爲每一個實例都聲明一個稅收數字 this.tax = 5; }; Calculator.prototype = new BaseCalculator();
咱們能夠看到Calculator的原型是指向到BaseCalculator的一個實例上,目的是讓Calculator集成它的add(x,y)和subtract(x,y)這2個function,還有一點要說的是,因爲它的原型是BaseCalculator的一個實例,因此無論你建立多少個Calculator對象實例,他們的原型指向的都是同一個實例
。
上面的代碼,運行之後,咱們能夠看到由於Calculator的原型是指向BaseCalculator的實例上的,因此能夠訪問他的decimalDigits屬性值,那若是我不想讓Calculator訪問BaseCalculator的構造函數裏聲明的屬性值,那怎麼辦呢?只須要將Calculator指向BaseCalculator的原型
而不是實例就好了。代碼以下:函數
var Calculator = function () { this.tax= 5; }; Calculator.prototype = BaseCalculator.prototype;
在使用第三方庫的時候,有時候他們定義的原型方法不能知足咱們的須要,咱們就能夠本身添加一些方法,代碼以下:學習
//覆蓋前面Calculator的add() function Calculator.prototype.add = function (x, y) { return x + y + this.tax; }; var calc = new Calculator(); alert(calc.add(1, 1));
原型鏈
對象的原型指向對象的父,而父的原型又指向父的父,這種原型層層的關係,叫作原型鏈。
在查找一個對象的屬性時,javascript會向上遍歷原型鏈,直到找到給定名稱的屬性爲止,當查找到達原型鏈的頂部,也便是Object.prototype,仍然沒有找到指定的屬性,就會返回undefined。
示例以下:
function foo() { this.add = function (x, y) { return x + y; } } foo.prototype.add = function (x, y) { return x + y + 10; } Object.prototype.subtract = function (x, y) { return x - y; } var f = new foo(); alert(f.add(1, 2)); //結果是3,而不是13 alert(f.subtract(1, 2)); //結果是-1
咱們能夠發現,subtrace是按照向上找的原則,而add則出了意外。緣由就是,屬性在查找的時候是先查找自身的屬性,若是沒有再查找原型。
說到Object.prototype,就不得不提它的一個方法,hasOwnProperty。它能判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性,它是javascript中惟一一個處理屬性可是不查找原型鏈的函數。使用代碼以下:
// 修改Object.prototype Object.prototype.bar = 1; var foo = {goo: undefined}; foo.bar; // 1 'bar' in foo; // true foo.hasOwnProperty('bar'); // false foo.hasOwnProperty('goo'); // true
而爲了判斷prototype對象和某個實例之間的關係,又不得不介紹isPrototyleOf方法,演示以下:
alert(Cat.prototype.isPrototypeOf(cat2)); //true
JavaScript和Java、C++等傳統面向對象的編程語言不一樣,它是沒有類(class)的概念的(ES6 中的class也只不過是語法糖,並不是真正意義上的類),而在JavaScript中,一切皆是對象(object)。在基於類的傳統面向對象的編程語言中,對象由類實例化而來,實例化的過程當中,類的屬性和方法會拷貝到這個對象中;對象的繼承其實是類的繼承,在定義子類繼承於父類時,子類會將父類的屬性和方法拷貝到自身當中。所以,這類語言中,對象建立和繼承行爲都是經過拷貝完成的。但在JavaScript中,對象的建立、對象的繼承(更好的叫法是對象的代理,由於它並非傳統意義上的繼承)是不存在拷貝行爲的。如今讓咱們忘掉類、忘掉繼承,這一切都不屬於JavaScript。
其實,原型這個名字自己就很容易產生誤解,原型在百度詞條中的釋義是:指原來的類型或模型。按照這個定義解釋的話,對象的原型是對象建立自身的模子,模子具有的特色對象都要具備,這儼然就是拷貝的概念。咱們已經說過, JavaScript的對象建立不存在拷貝,對象的原型實際上也是一個對象,它和對象自己是徹底獨立的兩個對象。既然如此,原型存在的意義又是什麼呢?原型是爲了共享多個對象之間的一些共有特性(屬性或方法),這個功能也是任何一門面向對象的編程語言必須具有的。A、B兩個對象的原型相同,那麼它們必然有一些相同的特徵。
JavaScript中的對象,都有一個內置屬性[[Prototype]],指向這個對象的原型對象。當查找一個屬性或方法時,若是在當前對象中找不到定義,會繼續在當前對象的原型對象中查找;若是原型對象中依然沒有找到,會繼續在原型對象的原型中查找(原型也是對象,也有它本身的原型);如此繼續,直到找到爲止,或者查找到最頂層的原型對象中也沒有找到,就結束查找,返回undefined。能夠看出,這個查找過程是一個鏈式的查找,每一個對象都有一個到它自身原型對象的連接,這些連接組成的整個鏈條就是原型鏈。擁有相同原型的多個對象,他們的共同特徵正是經過這種查找模式體現出來的。
在上面的查找過程,咱們提到了最頂層的原型對象,這個對象就是Object.prototype,這個對象中保存了最經常使用的方法,如toString、valueOf、hasOwnProperty等,所以咱們才能在任何對象中使用這些方法。
1.字面量方式
當經過字面量方式建立對象時,它的原型就是Object.prototype。雖然咱們沒法直接訪問內置屬性[[Prototype]],但咱們能夠經過Object.getPrototypeOf()或對象的__proto__獲取對象的原型。
var obj = {}; Object.getPrototypeOf(obj) === Object.prototype; // true obj.__proto__ === Object.prototype; // true
2.函數的構造調用
經過函數的構造調用(注意,咱們不把它叫作構造函數,由於JavaScript中一樣沒有構造函數的概念,全部的函數都是平等的,只不過用來建立對象時,函數的調用方式不一樣而已)也是一種經常使用的建立對象的方式。基於同一個函數建立出來的對象,理應能夠共享一些相同的屬性或方法,但這些屬性或方法若是放在Object.prototype裏,那麼全部的對象均可以使用它們了,做用域太大,顯然不合適。因而,JavaScript在定義一個函數時,同時爲這個函數定義了一個 默認的prototype屬性,全部共享的屬性或方法,都放到這個屬性所指向的對象中。由此看出,經過一個函數的構造調用建立的對象,它的原型就是這個函數的prototype指向的對象。
var f = function(name) { this.name = name }; f.prototype.getName = function() { return this.name; } //在prototype下存放全部對象的共享方法 var obj = new f('JavaScript'); obj.getName(); // JavaScript obj.__proto__ === f.prototype; // true
//建立構造函數 function Person(name){ this.name = name } //每一個構造函數JS引擎都會自動添加一個prototype屬性,咱們稱之爲原型,這是一個對象 //每一個由構造函數建立的對象都會共享prototype上面的屬性與方法 console.log(typeof Person.prototype) // 'object' //咱們爲Person.prototype添加sayName方法 Person.prototype.sayName = function(){ console.log(this.name) } //建立實例 var person1 = new Person('Messi') var person2 = new Person('Suarez') person1.sayName() // 'Messi' person2.sayName() // 'Suarez' person1.sayName === person2.sayName //true
咱們藉助上面的例子來理解構造函數-原型-實例,三者之間的關係,主要有幾個基本概念
Person.prototype.constructor === Person
person1.__proto__ === Person.prototype person1.__proto__.constructor === Person
Oject自己是一個構造函數,它也是一個對象,那麼
Object.__proto__ === Function.prototype
還有幾個須要咱們知道的特殊概念:
Object.prototype.__proto__ === null
3.Object.create()
第三種經常使用的建立對象的方式是使用Object.create()。這個方法會以你傳入的對象做爲建立出來的對象的原型。
var obj = {}; var obj2 = Object.create(obj); obj2.__proto__ === obj; // true
這種方式還能夠模擬對象的「繼承」行爲。
function Foo(name) { this.name = name; } Foo.prototype.myName = function() { return this.name; }; function Bar(name,label) { Foo.call( this, name ); // this.label = label; } // temp對象的原型是Foo.prototype var temp = Object.create( Foo.prototype ); // 經過new Bar() 建立的對象,其原型是temp, 而temp的原型是Foo.prototype, // 從而兩個原型對象Bar.prototype和Foo.prototype 有了"繼承"關係 Bar.prototype = temp; Bar.prototype.myLabel = function() { return this.label; }; var a = new Bar( "a", "obj a" ); a.myName(); // "a" a.myLabel(); // "obj a" a.__proto__.__proto__ === Foo.prototype; //true
這是容易混淆的兩個屬性。__proto__指向當前對象的原型,prototype是函數才具備的屬性,默認狀況下,new 一個函數建立出的對象,其原型都指向這個函數的prototype屬性。
1.對於JavaScript中的內置對象,如String、Number、Array、Object、Function等,由於他們是native代碼實現的,他們的原型打印出來都是ƒ () { [native code] }。
2.內置對象本質上也是函數,因此能夠經過他們建立對象,建立出的對象的原型指向對應內置對象的prototype屬性,最頂層的原型對象依然指向Object.prototype。
'abc'.__proto__ === String.prototype; // true new String('abc').__proto__ === String.prototype; //true new Number(1).__proto__ ==== Number.prototype; // true [1,2,3].__proto__ === Array.prototype; // true new Array(1,2,3).__proto__ === Array.prototype; // true ({}).__proto__ === Object.prototype; //true new Object({}).__proto__ === Object.prototype; // true var f = function() {}; f.__proto__ === Function.prototype; // true var f = new Function('{}'); f.__proto__ === Function.prototype; // true
3.Object.create(null) 建立出的對象,不存在原型。
var a = Object.create(null); a.__proto__; // undefined
此外函數的prototype中還有一個constructor方法,建議你們就當它不存在,它的存在讓JavaScript原型的概念變得更加混亂,並且這個方法也幾乎沒有做用。