什麼是閉包函數?javascript
閉包函數是一種函數的使用方式,最多見的以下:html
function fn1(){ function fn(){ } return fn; }
這種函數的嵌套方式就是閉包函數,這種模式的好處是可讓內層函數訪問到外層函數的變量,而且讓函數總體不至於由於函數的執行完畢而被銷燬。java
例如:程序員
function fn1(){ var a =10; function fn(){ console.log(a); // 10 } return fn; }
再好比下面的代碼,隨着函數的每次執行,變量的值都會進行遞增1,緣由是由於外層函數的變量處於內層函數的做用域鏈當中,被內層函數所使用着,當js垃圾回收機制讀取到這一狀況後就不會進行垃圾回收。web
例如:ajax
function fn1(){ var a = 1; function fn(){ a++; console.log(a); } return fn; } // 調用函數 var x = fn1(); x(); // 2 x();//3
閉包函數在js的開發當中是很是常見的寫法,例以下面這種寫法,功能是實現了對數組的一些常規操做的封裝,也是屬於對閉包函數的一種應用。編程
let Utils = (function(){ var list = []; return { add:function(item){ if(list.indexOf(item)>-1) return; // 若是數組內元素存在,那麼不在重複添加 list.push(item); }, remove:function(item){ if(list.indexOf(item) < 0) return; // 若是要刪除的數組數組以內不存在,那麼就返回 list.splice(list.indexOf(item),1); }, get_length:function(){ return list.length; }, get_showData:function() { return list; } } })(); Utils.add("hello,world"); Utils.add("this is test"); console.log(Utils.get_showData());// ["hello,world","this is test"]
在上面的代碼中,函數嵌套函數造成了閉包函數的結構,在開發中是比較常見的寫法。json
閉包的概念:數組
閉包是指有權限訪問上一級父做用域的變量的函數
.瀏覽器
在js開發中,常常碰到當即執行函數
的寫法。大致以下:
// 下面的這種寫法就是當即執行函數 // 在函數內部的內容會自動執行 (function(){ var a = 10; var b = 20; console.log(a+b); // 30 })();
咱們也能夠經過第二個括號內傳入參數:
(function(i){ console.log(i); })(i);
這種自調用的寫法本質上來說也是一個閉包函數
。
經過這種閉包函數,咱們能夠有效的避免變量污染等問題,從而建立一個獨立的做用域。
可是問題相對來講也很明顯,就是在這個獨立的做用域當中,咱們沒有辦法將其中的函數或者變量讓外部訪問的到。因此若是咱們在外部須要
訪問這個當即執行函數中的變量或者方法,咱們就須要經過第二個括號將window這個全局的變量對象傳入,而且將須要外部訪問的變量或者函數賦值
給window,這樣作至關於將其暴露在了全局的做用域範圍以內。
須要注意的是,一般狀況下咱們只須要將必要的方法暴露,這樣才能保證代碼並不會相互產生過多的影響,從而下降耦合度。
例如:
(function (window){ var a = 10; // 私有屬性 function show(){ return a++; } function sayHello(){ // 私有方法 alert("hello,world"); } window.show = show;// 將show方法暴露在外部 })(window);
須要理解的是,在不少的代碼中,老是在(function(){})()的最前面加上一個
;
,目的是爲了防止合併代碼的時候js將代碼解析成(function(){})()(function(){})()
這種狀況。
由於js的特殊性,因此不少時候咱們在學習js的時候,除了js代碼的語法之外,還要學習不少爲了解決實際問題的方案,例以下面的這種寫法就是爲了
實現module
的寫法。
例如:
var testModule = function(){ var name = "張三"; // 私有屬性,外部沒法訪問 return { get_name:function(){ // 暴露在外部的方法 alert(name); }, set_name:function(new_name){ // 暴露在外部的方法 name = new_name; } } }
咱們也能夠將這種寫法進行升級,和當即執行函數進行適度的結合也是常見的寫法:
例如:
var blogModule = (function (my) { my.name = "zhangsan"; // 添加一些功能 my.sayHello = function(){ console.log(this.name) } return my; } (blogModule || {})); console.log(blogModule.sayHello())
自調用函數其實也就是遞歸函數
自調用函數顧名思義,就是調用自身的函數,而當即執行函數則是當即會及執行的函數。
下面是兩者的一些比較:
// 這是一個自執行的函數,函數內部執行自身,遞歸 function foo() { foo(); } // 這是一個自執行的匿名函數,由於沒有標示名稱 // 必須使用arguments.callee屬性來執行本身 var foo = function () { arguments.callee(); }; // 這可能也是一個自執行的匿名函數,僅僅是foo標示名稱引用它自身 // 若是你將foo改變成其它的,你將獲得一個used-to-self-execute匿名函數 var foo = function () { foo(); }; // 有些人叫這個是自執行的匿名函數(即使它不是),由於它沒有調用自身,它只是當即執行而已。 (function () { /* code */ } ()); // 爲函數表達式添加一個標示名稱,能夠方便Debug // 但必定命名了,這個函數就再也不是匿名的了 (function foo() { /* code */ } ()); // 當即調用的函數表達式(IIFE)也能夠自執行,不過可能不經常使用罷了 (function () { arguments.callee(); } ()); (function foo() { foo(); } ());
所謂的做用域
,指的就是變量
和函數
的可訪問範圍
,控制着變量和函數的可見性與生命週期,
在JavaScript中變量的做用域有全局做用域
和函數做用域
以及ES6新增長的塊級做用域
。
例如,在函數外部經過var
關鍵字聲明的變量就是全局變量,做用域的範圍也就是全局做用域,而在函數內部
經過var
聲明或者let
聲明的就是局部變量,做用域僅限於函數內部,在{}
內部或者流程控制語句或者循環語句內部
經過let
聲明的變量做用域範圍則僅限於當前做用域。函數的參數在()
內部,只能在函數內部使用,做用域範圍也僅限於函數。
同時window
對象的全部屬性也擁有全局做用域。
例如:
// 做用域範圍 var a = 10; // 全局 function fn1(a,b){ // 函數fn1內 c = 30; // 全局 var x = 30; // 函數fn1內 function fn2(){ var s = "hello"; // 函數fn2內 console.log(x); // 30 函數內部能夠訪問外層的變量 } } for(var i =0;i<10;i++){} // 循環體內聲明的計數變量i也是一個全局 console.log(i); // 10 for(let j = 0;i<10;j++){} // let 聲明的計數變量j 是一個局部 console.log(j);// 出錯,訪問不到
上面咱們說到了做用域,下面再來講下執行環境(execution context)。
什麼是執行環境呢?
簡單點說,執行環境定義了變量或者函數有權訪問的其餘的數據,而且也決定了他們各自的行爲。須要知道的
是,每個執行環境當中,都有着一個與之關聯的變量對象(variable object),執行環境中定義的全部變量和函數都會保存在這個對象中,解析器在處理數據的時候就會訪問這個內部對象。
而全局執行環境是最外層的一個執行環境,在web瀏覽器中最外層的執行環境關聯的對象是window
,因此咱們
能夠這樣說,全部的全局變量和函數 都是做爲window對象的屬性和方法建立的。
咱們建立的每個函數都有本身的執行環境,當執行流進行到函數的時候,函數的環境會被推入到一個函數執行棧
當中,而在函數執行完畢後執行環境出棧並被銷燬,保存在其中的全部變量和函數定義隨之銷燬,控制權返回到以前的執行環境中,全局的執行環境在應用程序退出(瀏覽器關閉)纔會被銷燬。
當代碼在一個執行環境執行之時,會建立一個變量對象的一個做用域鏈(scope chain),來保證在執行環境中
,對執行環境有權訪問的變量和函數的有序訪問。
做用域第一個也d就是頂層對象始終是當前執行代碼所在環境的變量對象(VO)。
例如:
function fn1(){}
fn1在建立的時候做用域鏈被添加進全局對象,全局對象中擁有全部的全局變量。
例如上面的fn1在建立的時候,所處的環境是全局環境,因此此時的this就指向window。
在函數運行過程當中標識符的解析是沿着做用域鏈一級一級搜索的過程,從第一個對象開始,逐級向後回溯,直到找到同名標識符爲止,找到後再也不繼續遍歷,找不到就報錯。
若是執行環境是函數,那麼將其活動對象(activation object, AO)做爲做用域鏈第一個對象,第二個對象是包含環境,下一個是包含環境上一層的包含環境...
也就是說所謂的做用域鏈,就是指具體的某個變量或者函數從其第一個對象(活動對象)一直到頂層執行環境。這中間的聯繫就是做用域鏈。
誤解
的閉包函數談及閉包函數的概念,常常會有人錯誤的將其理解爲從父上下文中返回內部函數,甚至理解成只有匿名函數才能是閉包。
而實際來講,由於做用域鏈,使得全部的函數都是閉包(與函數類型無關: 匿名函數,FE,NFE,FD都是閉包)。
注意:這裏只有一類函數除外,那就是經過Function構造器建立的函數,由於其[[Scope]]只包含全局對象。
閉包函數是js當中很是重要的概念,在諸多的地方能夠應用到閉包,經過閉包,咱們能夠寫出不少優秀的代碼,下面是一些常見的內容:
例如:
// 數組排序 [1, 2, 3].sort(function (a, b) { ... // 排序條件 }); // map方法的應用,根據函數中定義的條件將原數組映射到一個新的數組中 [1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6] // 經常使用的 forEach [1, 2, 3].forEach(function (element) { if (element % 2 != 0) { alert(element); } }); // 1, 3
例如咱們經常使用的call和apply方法,它們是兩個應用函數
,也就是應用到參數中的函數(在apply中是參數列表,在call中是獨立的參數):
例如:
(function () { alert([].join.call(arguments, ';')); // 1;2;3 }).apply(this, [1, 2, 3]);
還有最常使用的寫法:
var a = 10; setTimeout(function () { alert(a); // 10, after one second }, 1000);
固然,ajax的寫法也就是回調函數其實本質也是閉包:
//... var x = 10; // only for example xmlHttpRequestObject.onreadystatechange = function () { // 當數據就緒的時候,纔會調用; // 這裏,不管是在哪一個上下文中建立 // 此時變量「x」的值已經存在了 alert(x); // 10 }; //...
固然也包括咱們上邊說的封裝獨立做用域的寫法:
例如:
var foo = {}; // 初始化 (function (object) { var x = 10; object.getX = function _getX() { return x; }; })(foo); alert(foo.getX()); // 得到閉包 "x" – 10
在工做當中,咱們老是能夠聽到人說將數據轉換爲JSON對象,或者說把JSON對象轉換爲字符串之類的話,下面是關於JSON的具體說明。
不少人錯誤的將JSON認爲是JavaScript當中的對象字面量(object Literals),緣由很是簡單,就是由於它們的語法是很是類似的,可是在ECMA中明確的說明了。JSON只是一種數據交互語言,只有咱們將之用在string上下文的時候它才叫JSON。
序列化與反序列化
2個程序(或服務器、語言等)須要交互通訊的時候,他們傾向於使用string字符串由於string在不少語言裏解析的方式都差很少。複雜的數據結構常常須要用到,而且經過各類各樣的中括號{},小括號(),叫括號<>和空格來組成,這個字符串僅僅是按照要求規範好的字符。
爲此,咱們爲了描述這些複雜的數據結構做爲一個string字符串,制定了標準的規則和語法。JSON只是其中一種語法,它能夠在string上下文裏描述對象,數組,字符串,數字,布爾型和null,而後經過程序間傳輸,而且反序列化成所須要的格式。
常見的數據流行交互格式有YAML
、XML
、和JSON
都是經常使用的數據交互格式。
字面量
引用Mozilla Developer Center裏的幾句話,供你們參考:
何時會成爲JSON
JSON是設計成描述數據交換格式的,他也有本身的語法,這個語法是JavaScript的一個子集。
{ "prop": "val" } 這樣的聲明有多是JavaScript對象字面量也有多是JSON字符串,取決於什麼上下文使用它,若是是用在string上下文(用單引號或雙引號引住,或者從text文件讀取)的話,那它就是JSON字符串,若是是用在對象字面量上下文中,那它就是對象字面量。
例如:
// 這是JSON字符串 var foo = '{ "prop": "val" }'; // 這是對象字面量 var bar = { "prop": "val" };
並且要注意,JSON有很是嚴格的語法,在string上下文裏{ "prop": "val" } 是個合法的JSON,但{ prop: "val" }和{ 'prop': 'val' }確實不合法的。全部屬性名稱和它的值都必須用雙引號引住,不能使用單引號。
JS當中的JSON對象
目前,JSON對象已經成爲了JS當中的一個內置對象,有兩個靜態的方法:JSON.parse和JSON.stringify。
JSON.parse主要要來將JSON字符串反序列化成對象,JSON.stringify用來將對象序列化成JSON字符串。老版本的瀏覽器不支持這個對象,但你能夠經過json2.js來實現一樣的功能。
例如:
// 這是JSON字符串,好比從AJAX獲取字符串信息 var my_json_string = '{ "prop": "val" }'; // 將字符串反序列化成對象 var my_obj = JSON.parse( my_json_string ); alert( my_obj.prop == 'val' ); // 提示 true, 和想象的同樣! // 將對象序列化成JSON字符串 var my_other_json_string = JSON.stringify( my_obj );
javascript中對象的建立有不少種方式,以下:
第二種形式是經過{}
這種寫法直接來建立一個對象
還有一種是經過new Object()的形式建立構造函數。
經過對象字面量的形式建立對象
例如:
let obj = { value:10, setValue:function(new_value){ this.value = new_value; }, geyValue:function(){ return this.value; } }
經過構造函數的形式建立一個對象
例如:
function Person(name,age){ this.name = name; this.age = age; } var a = new Person("張三",30);
經過new Object()建立對象
例如:
var obj = new Object(); console.log(obj); // {}
下面來講一下經過對象直接量建立的對象的一些使用方式:
屬性和方法的增刪改查:
例如:
// 建立一個對象直接量 var person = { name:"張三", age:40, sayHello:function(){ alert(this.name,"你好"); }, setName:function(name){ this.name = name; } } // 更改屬性 person.name = "李四"; person['age'] = 40; // 更改方法 person.sayHello = function(){ return this.name + "你好"; } // 添加屬性 person.like = "旅遊"; // 添加方法 person.run = function(){ return `${this.name} 正在跑步`; } // 刪除 delete person.age; // 查詢和調用 console.log(person.name); console.log(person.run());
例如:
function Person(name,age){ this.name = name; // 初始化屬性 this.age = age; } Person.prototype.sayHello = function(){ alert(this.name); } // 實例化 var zhangsan = new Person("張三",20); // 查看屬性 console.log(zhangsan.name); //. 調用方法 zhangsan.sayHello(); // 修改屬性 zhangsan.name = "張小三"; // 修改方法 zhangsan.sayHello = function(){ console.log(this.name + "你好"); } // 添加屬性和調用屬性相同 ... // 添加方法和調用方法相同 ... // 刪除 delete zhangsan.name; console.log(zhangsan);
在js對象當中,存在兩個屬性getter和setter,經過這兩個屬性咱們能夠進行屬性的查詢和賦值。
例如:
var obj = { value:10, get add(){ return this.value + 1; }, set changeVal(new_val){ return this.value = new_val; } } // 使用 console.log(obj.add); // 11 obj.changeVal = 20; console.log(obj.value); // 20
應用:變量監聽
例如:
html代碼以下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>變量的動態監聽</title> </head> <body> <button id="btn" onclick="test()">點擊</button> <p id="p">0</p> </body> <script src="test.js" charset="utf-8"></script> <script type="text/javascript"> function test(){ watchVal.value = ++watchVal.value; let info = document.getElementById("p"); info.innerHTML = watchVal.value; console.log(watchVal.value); } </script> </html>
js代碼以下:
// 變量監聽 var watchVal = { value:0, get val(){ console.log('取值:',this.value); return this.value; }, set val(vals) { this.value = vals; console.log("存儲以後的值",this.value); } }
在諸多面向對象編程語言當中,封裝
、繼承
、多態
是必備的內容,而在js這門基於對象的編程語言身上,想要實現其諸多特性,只能
經過prototype
原型來實現。
咱們能夠先來經過代碼來簡單的體驗一下原型:
// 建立一個數組 var arr = [1,2,3,4]; // 打印這個數組 console.log(arr);
你能夠在打印的結果中最後看到一個屬性__proto__
。
你過你嘗試着將其打印,你會發現其中包含了Array
的屬性和方法。
它是什麼呢?
它其實就是數據arr數組
的原型,而__proto__
表述的其實就是數組的原型鏈.
你須要知道的是,在js當中,__proto__
就是數據原型的表明,咱們能夠經過其向咱們建立的數組中添加方法:
例如:
// 建立一個數組 var arr = [1,2,3,4]; // 打印這個數組 console.log(arr); // 向數組的原型上添加一個sayHello()方法 arr.__proto__.sayHello = function(){ console.log("hello,world!"); } arr.sayHello();
咱們在開發的過程當中,更多的是採用下面的寫法:
// 建立一個數組 var arr = [1,2,3,4]; // 打印這個數組 console.log(arr); // 向數組的原型上添加一個sayHello()方法 Array.prototype.sayHello = function(){ console.log("hello,world!"); } arr.sayHello();
在上面的代碼中,prototype
一樣表示着原型,只不過更多的時候是被應用在構造函數的身上。Array
是數組對象的原型
,它是一個構造函數,因此能夠經過這個屬性來進行方法的設置。
當數據是一個構造函數的時候,prototype的使用方式還能夠進行簡化:
function Person(){ } Person.prototype = { sayHello:function(){ console.log(111) } } var a = new Person(); a.sayHello();
在編程領域,面向對象是一種常見的編程範式,也被成爲OOP
,ECMASript支持包括結構化、面向對象、函數式、命令式等多種編程方式。
下面要說的就是面向對象編程的內容。
MDN說明:
面向對象編程是用抽象方式建立基於現實世界模型的一種編程模式。它使用先前創建的範例,包括模塊化,多態和封裝幾種技術。今天,許多流行的編程語言(如Java,JavaScript,C#,C+ +,Python,PHP,Ruby和Objective-C)都支持面向對象編程(OOP)。 相對於「一個程序只是一些函數的集合,或簡單的計算機指令列表。」的傳統軟件設計觀念而言,面向對象編程能夠看做是使用一系列對象相互協做的軟件設計。 在 OOP 中,每一個對象可以接收消息,處理數據和發送消息給其餘對象。每一個對象均可以被看做是一個擁有清晰角色或責任的獨立小機器。 面向對象程序設計的目的是在編程中促進更好的靈活性和可維護性,在大型軟件工程中廣爲流行。憑藉其對模塊化的重視,面向對象的代碼開發更簡單,更容易理解,相比非模塊化編程方法, 它能更直接地分析, 編碼和理解複雜的狀況和過程。
面向對象編程術語:
Namespace 命名空間 容許開發人員在一個獨特,應用相關的名字的名稱下捆綁全部功能的容器。 Class 類 定義對象的特徵。它是對象的屬性和方法的模板定義。 Object 對象 類的一個實例。 Property 屬性 對象的特徵,好比顏色。 Method 方法 對象的能力,好比行走。 Constructor 構造函數 對象初始化的瞬間,被調用的方法。一般它的名字與包含它的類一致。 Inheritance 繼承 一個類能夠繼承另外一個類的特徵。 Encapsulation 封裝 一種把數據和相關的方法綁定在一塊兒使用的方法。 Abstraction 抽象 結合複雜的繼承,方法,屬性的對象可以模擬現實的模型。 Polymorphism 多態 多意爲「許多」,態意爲「形態」。不一樣類能夠定義相同的方法或屬性。
JS當中的命名空間
命名空間是一個容器,它容許開發人員在一個獨特的,特定於應用程序的名稱下捆綁全部的功能。 在JavaScript中,命名空間只是另外一個包含方法,屬性,對象的對象。
創造的JavaScript命名空間背後的想法很簡單:一個全局對象被建立,全部的變量,方法和功能成爲該對象的屬性。使用命名空間也最大程度地減小應用程序的名稱衝突的可能性。
例如:
咱們來建立一個全局變量叫作 MYAPP
// 全局命名空間 var MYAPP = MYAPP || {};
在上面的代碼示例中,咱們首先檢查MYAPP是否已經被定義(是否在同一文件中或在另外一文件)。若是是的話,那麼使用現有的MYAPP全局對象,不然,建立一個名爲MYAPP的空對象用來封裝方法,函數,變量和對象。
咱們也能夠建立子命名空間:
// 子命名空間 MYAPP.event = {};
下面是用於建立命名空間和添加變量,函數和方法的代碼寫法:
// 給普通方法和屬性建立一個叫作MYAPP.commonMethod的容器 MYAPP.commonMethod = { regExForName: "", // 定義名字的正則驗證 regExForPhone: "", // 定義電話的正則驗證 validateName: function(name){ // 對名字name作些操做,你能夠經過使用「this.regExForname」 // 訪問regExForName變量 }, validatePhoneNo: function(phoneNo){ // 對電話號碼作操做 } } // 對象和方法一塊兒申明 MYAPP.event = { addListener: function(el, type, fn) { // 代碼 }, removeListener: function(el, type, fn) { // 代碼 }, getEvent: function(e) { // 代碼 } // 還能夠添加其餘的屬性和方法 } //使用addListener方法的寫法: MYAPP.event.addListener("yourel", "type", callback);
自定義對象
[類]
JavaScript是一種基於原型的語言,它沒類的聲明語句,好比C+ +或Java中用的。這有時會對習慣使用有類申明語句語言的程序員產生困擾。相反,JavaScript可用方法做類。定義一個類跟定義一個函數同樣簡單。在下面的例子中,咱們定義了一個新類Person。
function Person() { } // 或 var Person = function(){ }
[對象(類的實例)]
咱們使用 new obj 建立對象 obj 的新實例, 將結果(obj 類型)賦值給一個變量方便稍後調用。
在下面的示例中,咱們定義了一個名爲Person的類,而後咱們建立了兩個Person的實例(person1 and person2).
function Person() { } var person1 = new Person(); var person2 = new Person();
[構造器]
在實例化時構造器被調用 (也就是對象實例被建立時)。構造器是對象中的一個方法。 在JavaScript中函數就能夠做爲構造器使用,所以不須要特別地定義一個構造器方法,每一個聲明的函數均可以在實例化後被調用執行。
構造器經常使用於給對象的屬性賦值或者爲調用函數作準備。 在本文的後面描述了類中方法既能夠在定義時添加,也能夠在使用前添加。
在下面的示例中, Person類實例化時構造器調用一個 alert函數。
function Person() { alert('Person instantiated'); } var person1 = new Person(); var person2 = new Person();
[屬性(對象屬性)]
屬性就是 類中包含的變量;每個對象實例有若干個屬性. 爲了正確的繼承,屬性應該被定義在類的原型屬性 (函數)中。
可使用 關鍵字 this調用類中的屬性, this是對當前對象的引用。 從外部存取(讀/寫)其屬性的語法是: InstanceName.Property; 這與C++,Java或者許多其餘語言中的語法是同樣的 (在類中語法 this.Property 經常使用於set和get屬性值)
在下面的示例中,咱們爲定義Person類定義了一個屬性 firstName 並在實例化時賦初值。
function Person(firstName) { this.firstName = firstName; alert('Person instantiated'); } var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects alert('person1 is ' + person1.firstName); // alerts "person1 is Alice" alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"
[方法(對象屬性)]
方法與屬性很類似, 不一樣的是:一個是函數,另外一個能夠被定義爲函數。 調用方法很像存取一個屬性, 不一樣的是add () 在方法名後面極可能帶着參數. 爲定義一個方法, 須要將一個函數賦值給類的 prototype 屬性; 這個賦值給函數的名稱就是用來給對象在外部調用它使用的。
在下面的示例中,咱們給Person類定義了方法 sayHello(),並調用了它.
function Person(firstName) { this.firstName = firstName; } Person.prototype.sayHello = function() { alert("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); // call the Person sayHello method. person1.sayHello(); // alerts "Hello, I'm Alice" person2.sayHello(); // alerts "Hello, I'm Bob"
在JavaScript中方法一般是一個綁定到對象中的普通函數, 這意味着方法能夠在其所在context以外被調用。
function Person(firstName) { this.firstName = firstName; } Person.prototype.sayHello = function() { alert("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); var helloFunction = person1.sayHello; person1.sayHello(); // alerts "Hello, I'm Alice" person2.sayHello(); // alerts "Hello, I'm Bob" helloFunction(); // alerts "Hello, I'm undefined" (or fails // with a TypeError in strict mode) console.log(helloFunction === person1.sayHello); // logs true console.log(helloFunction === Person.prototype.sayHello); // logs true helloFunction.call(person1); // logs "Hello, I'm Alice"
如上例所示, 全部指向sayHello函數的引用 ,包括 person1, Person.prototype, 和 helloFunction 等, 均引用了相同的函數.
在調用函數的過程當中,this的值取決於咱們怎麼樣調用函數. 在一般狀況下,咱們經過一個表達式person1.sayHello()來調用函數:即從一個對象的屬性中獲得所調用的函數。此時this被設置爲咱們取得函數的對象(即person1)。這就是爲何person1.sayHello() 使用了姓名「Alice」而person2.sayHello()使用了姓名「bob」的緣由。
然而咱們使用不一樣的調用方法時, this的值也就不一樣了。當從變量 helloFunction()中調用的時候, this就被設置成了全局對象 (在瀏覽器中即window)。因爲該對象 (很是可能地) 沒有firstName 屬性, 咱們獲得的結果即是"Hello, I'm undefined". (這是鬆散模式下的結果, 在 嚴格模式中,結果將不一樣(此時會產生一個error)。 可是爲了不混淆,咱們在這裏不涉及細節) 。
call和apply能夠顯示的更改this
繼承
建立一個或多個類的專門版本類方式稱爲繼承(Javascript只支持單繼承)。 建立的專門版本的類一般叫作子類,另外的類一般叫作父類。 在Javascript中,繼承經過賦予子類一個父類的實例並專門化子類來實現。在現代瀏覽器中你可使用 Object.create 實現繼承.
在下面的例子中, 咱們定義了 Student類做爲 Person類的子類. 以後咱們重定義了sayHello() 方法並添加了 sayGoodBye() 方法.
// 定義Person構造器 function Person(firstName) { this.firstName = firstName; } // 在Person.prototype中加入方法 Person.prototype.walk = function(){ alert("I am walking!"); }; Person.prototype.sayHello = function(){ alert("Hello, I'm " + this.firstName); }; // 定義Student構造器 function Student(firstName, subject) { // 調用父類構造器, 確保(使用Function#call)"this" 在調用過程當中設置正確 Person.call(this, firstName); // 初始化Student類特有屬性 this.subject = subject; }; // 創建一個由Person.prototype繼承而來的Student.prototype對象. // 注意: 常見的錯誤是使用 "new Person()"來創建Student.prototype. // 這樣作的錯誤之處有不少, 最重要的一點是咱們在實例化時 // 不能賦予Person類任何的FirstName參數 // 調用Person的正確位置以下,咱們從Student中來調用它 Student.prototype = Object.create(Person.prototype); // See note below // 設置"constructor" 屬性指向Student Student.prototype.constructor = Student; // 更換"sayHello" 方法 Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); }; // 加入"sayGoodBye" 方法 Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); }; // 測試實例: var student1 = new Student("Janet", "Applied Physics"); student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student1.walk(); // "I am walking!" student1.sayGoodBye(); // "Goodbye!" // Check that instanceof works correctly console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true
對於「Student.prototype = Object.create(Person.prototype);」這一行,在不支持 Object.create方法的老JavaScript引擎中,
可使用相似下面的代碼來進行解決:
function createObject(proto) { function ctor() { } ctor.prototype = proto; return new ctor(); } // Usage: Student.prototype = createObject(Person.prototype);