一、this 在JavaScript中this表示:誰調用它,this就是誰。 JavaScript是由對象組成的,一切皆為對象,萬物皆為對象。this是一個動態的對象,根據調用的對象不同而發生變化,當然也可以使用call、apply修改this指向的對象。它代表函數運行時,自動生成的一個內部對象, ...
一、this
在JavaScript中this表示:誰調用它,this就是誰。
JavaScript是由對象組成的,一切皆為對象,萬物皆為對象。this是一個動態的對象,根據調用的對象不同而發生變化,當然也可以使用call、apply修改this指向的對象。它代表函數運行時,自動生成的一個內部對象,只能在函數內部使用
1.1、JavaScript中函數與方法的區分
在面向過程的語言中我們習慣把完成某個特定功能的代碼塊稱為“函數”或“過程”,當然過程一般沒有返回值。在面向對象語言中我們把對象的功能稱為“方法”。但JavaScript是種介於面向對象與面向過程中間的語言,同樣的方法有時是函數,有時是方法,如下所示:
<script type="text/javascript"> //1 function show(){ console.log("這是一個函數"); } //2 (function(){ console.log("這是一個函數表達式"); })(); //3 var obj1={ show:function(){ console.log("這是一個方法"); } }; //4 function obj2(){ //obj2是函數,構造函數 this.show=function(){ console.log("這是一個方法,是一個表達式"); } } var obj3=new obj2(); </script>
可以簡單的認為如果調用時沒有通過對象沒有指定上下文則為函數,否則為方法。
1.2、指向全局對象
當在全部範圍內使用 this,它將會指向全局對象。一般是window對象,但全局對象不一定只有window,特別是在node.js環境中。作為函數調用時一般指向全局對象。
<script type="text/javascript"> var name="tom"; console.log(this.name); //頂層對象,一般為window function show() { console.log(this.name); //頂層對象,一般為window return function(){ console.log(this.name); //頂層對象,一般為window,因為返回的是函數 } } var f1=show(); f1(); </script>
運行結果:
1.3、作為方法調用
當函數作為方法調用時this指向調用該方法的對象。
function show() { //當obj1.view()時this就是obj1,obj2.view()時this就是obj2 console.log(this.name); } var obj1={name:"jack",view:show}; obj1.view(); var obj2={name:"mark",view:show}; obj2.view();
運行結果:
示例代碼:
<script type="text/javascript"> var name="lucy"; this.name="Mali"; function show() { //當obj1.view()時this就是obj1,obj2.view()時this就是obj2 console.log(this.name); return function(){ console.log(this.name); //這裡的this是調用時動態決定 } } var obj1={name:"jack",view:show}; obj1.view(); var obj2={name:"mark",view:show}; var f1=obj2.view(); f1(); //因為f1屬於window(瀏覽器環境),調用f1時的this也就是window </script>
運行結果:
示例代碼:
<script type="text/javascript"> var name="lucy"; this.name="Mali"; function show() { //當obj1.view()時this就是obj1,obj2.view()時this就是obj2 console.log(this.name); that=this; //閉包 return function(){ console.log(that.name); //這裡的that指向的是外層函數調用時的對象 } } var obj1={name:"jack",view:show}; obj1.view(); var obj2={name:"mark",view:show}; var f1=obj2.view(); f1(); </script>
運行結果:
1.4、在構造函數中的this
構造函數中的this指向新創建的對象,new出誰this就是誰。
示例代碼:
<script type="text/javascript"> this.name="吉娃娃"; function Dog(name) { this.name=name; this.bark=function(){ console.log(this.name+"在叫,汪汪..."); } } var dog1=new Dog("哈士奇"); dog1.bark(); </script>
運行結果:
按照嚴格的語法,構造函數不應該返回值,但是JavaScript是允許構造方法返回值的,預設返回this,修改後的示例如下:
this.name="吉娃娃"; function Dog(name) { this.name=name; this.bark=function(){ console.log(this.name+"在叫,汪汪..."); } return this.bark; } var dog1=new Dog("哈士奇"); dog1();
運行結果:
1.5、指定this指向的對象
當調用方法是通過Function.call,或Function.apply時this為調用時指定的對象,如果沒有指定則為頂層對象,瀏覽器中是window。
示例代碼:
this.name="伽啡"; function Cat(name) { this.name=name; } var bark=function(n){ console.log(this.name+"叫了"+(n||1)+"聲喵喵..."); } var cat1=new Cat("波斯"); var cat2=new Cat("龍貓"); bark.call(cat1,5); //調用時this是cat1 bark.apply(cat2,[8]); //調用時this是cat2 bark.apply(window,[9]); //調用時this是window bark.apply(); //調用時this是頂層對象
運行結果:
1.6、作為事件時的this
如果頁面中的元素與事件進行綁定時事件中的this一般指向網頁元素,如按鈕,文本框等。但是在元素中直接指定要執行的函數則this指向window頂層對象。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>this</title> </head> <body> <input type="button" value="按鈕A" class="btn"/> <input type="button" value="按鈕B" class="btn"/> <input type="button" value="按鈕C" class="btn"/> <input type="button" value="按鈕D" onclick="handler()"/> <!--handler中的this指向window或頂層對象--> <input type="button" value="按鈕E" id="btnE"/> <script type="text/javascript"> var buttons = document.querySelectorAll(".btn"); for (var i=0;i<buttons.length;i++) { //handler中的this指向按鈕對象 buttons[i].onclick=handler; } function handler(){ alert(this.value); } //handler中的this指向btnE document.getElementById("btnE").addEventListener("click",handler,false); </script> </body> </html>
當點擊A-C時的效果:
當點擊D時的效果:
當點擊按鈕E時的效果:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>this</title> </head> <body> <input type="button" value="按鈕F的值" id="btnF" /> <input type="button" value="按鈕G的值" id="btnG" onclick="app.show()"/> <script type="text/javascript"> value="window的值" var app = { value: "app的值", show: function() { alert(this.value); //如果show為事件,則this指向觸發事件的對象 }, init: function() { //handler中的this指向btnE document.getElementById("btnF").addEventListener("click", this.show, false); } }; app.show(); //"app的值",show方法中的this指向app這個對象 app.init(); //init中的this指向誰app對象 </script> </body> </html>
載入時運行結果:
點擊按鈕F時的效果:
點擊按鈕G時的效果:
在HTML元素上直接指定事件嚴格來說都不能說是事件綁定,只能描述是當按鈕點擊完成後執行的函數。如果想將執行時的對象帶回,可以增加參數this。
1.7、小結
函數調用可以如下幾種基本形式:
1)、fun(x,y);
2)、obj.fun(x,y);
3)、fun.apply(obj,[x,y]);
4)、fun.call(obj,x,y);
第1,2種調用的方式最終將轉換成3,4方法,也就是說1,2只是一種簡化的語法糖,那麼this就是apply與obj的第1個參數,指向調用時的上下文。
二、原型(prototype)
JavaScript是一種通過原型實現繼承的語言與別的高級語言是有區別的,像java,C#是通過類型決定繼承關係的,JavaScript是的動態的弱類型語言,總之可以認為JavaScript中所有都是對象,在JavaScript中,原型也是一個對象,通過原型可以實現對象的屬性繼承,JavaScript的對象中都包含了一個" prototype"內部屬性,這個屬性所對應的就是該對象的原型。
"prototype"作為對象的內部屬性,是不能被直接訪問的。所以為了方便查看一個對象的原型,Firefox和Chrome內核的JavaScript引擎中提供了"__proto__"這個非標準的訪問器(ECMA新標準中引入了標準對象原型訪問器"Object.getPrototype(object)")。
1.1、為什麼需要prototype
現在有一個Student構造函數,通過new調用該構造函數可以創建一個新的對象,示例如下:
//構造方法,用於創建學生對象 function Student(name) { this.name = name; } var tom=new Student("tom"); tom.show=function(){ console.log(this.name); } var rose=new Student("rose"); rose.show=function(){ console.log(this.name); } tom.show(); rose.show();
運行結果:
上面的示例中tom與rose都需要show方法,創建對象後我們直接為兩個對象分別都增加了show方法,但是這樣做的弊端是如果再增加更多對象也需要添加show方法,最好的辦法是修改構造方法Student,如下所示:
//構造方法,用於創建學生對象 function Student(name) { this.name = name; this.show = function() { console.log(this.name); } } var tom = new Student("tom"); var rose = new Student("rose"); tom.show(); rose.show();
但是如果Student構造函數是一個內置的函數或是其它框架定義的類型,則修改就比較麻煩了,可以不修改源碼的情況擴展該對象的原型也可以達到目的,如下所示:
//構造方法,用於創建學生對象 function Student(name) { this.name = name; } //修改Student對象的原型,增加show方法 Student.prototype.show = function() { console.log(this.name); } var tom = new Student("tom"); var rose = new Student("rose"); tom.show(); rose.show();
在示例中我們並沒有修改Student這個構造函數而是修改了了Student的原型對象,類似它的父類,如下圖所示:
此時你也許會認為所有的Object都增加了show方法,這樣想是錯誤的,因為Student的原型是一個對象而不是像其它高級語中的類型,我們修改的只是Student的原型對象,不會影響其它的對象。
var mark=new Object(); mark.name="mark"; var lucy={name:"lucy"}; //mark.show(); //lucy.show();
此處的兩個show方法是錯誤的。總之原型的主要作用就是為了實現繼承與擴展對象。
1.2、typeof與instanceof
1.2.1、typeof
在 JavaScript 中,判斷一個變數的類型可以用typeof,如:
var str1="Hello"; var str2=new String("Hello"); console.log(typeof 1); console.log(typeof(true)); console.log(typeof str1); console.log(typeof str2); console.log(typeof([1,2,3])); console.log(typeof Date); console.log(typeof undefined); console.log(typeof null); console.log(typeof zhangguo);
運行結果:
1)、數字類型, typeof 返回的值是 number。比如說:typeof(1),返回值是number
2)、字元串類型, typeof 返回的值是 string。比如typeof("123")返回值是string。
3)、布爾類型, typeof 返回的值是 boolean 。比如typeof(true)返回值是boolean。
4)、對象、數組、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。
5)、函數類型,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。
6)、不存在的變數、函數或者undefined,將返回undefined。比如:typeof(abc)、typeof(undefined)都返回undefined。
1.2.2、instanceof
使用 typeof 運算符時採用引用類型存儲值會出現一個問題,無論引用的是什麼類型的對象,它都返回 "object"。ECMAScript 引入了另一個 Java 運算符 instanceof 來解決這個問題。instanceof 運算符與 typeof 運算符相似,用於識別正在處理的對象的類型。與 typeof 方法不同的是,instanceof 方法要求開發者明確地確認對象為某特定類型。
instanceof用於判斷某個對象是否被另一個函數構造。
例如:
var oStringObject = new String("hello world");
console.log(oStringObject instanceof String); // 輸出 "true"
var str1="Hello"; var str2=new String("Hello"); console.log(str1 instanceof String); //string false console.log(str2 instanceof String); //object true console.log(1 instanceof Number); //false function Foo(){}; var f1=new Foo(); console.log(f1 instanceof Foo);
運行結果:
1.3、Function與Object
1.3.1、Function
函數就是對象,代表函數的對象就是函數對象。所有的函數對象是被Function這個函數對象構造出來的。Function是最頂層的構造器。它構造了系統中所有的對象,包括用戶自定義對象,系統內置對象,甚至包括它自已。這也表明Function具有自舉性(自已構造自己的能力)。這也間接決定了Function的call和constructor邏輯相同。每個對象都有一個 constructor 屬性,用於指向創建其的函數對象。
先來看一個示例:
function Foo(a, b, c) { //Foo這個構造函數由Function構造,函數也是對象 return a * b * c; } var f1=new Foo(); //f1由Foo構造,Foo是一個構造函數,可以理解為類 console.log(Foo.length); //參數個數 3 console.log(typeof Foo.constructor); //function console.log(typeof Foo.call); //function console.log(typeof Foo.apply); //function console.log(typeof Foo.prototype); //object object
運行結果:
函數與對象具有相同的語言地位
沒有類,只有對象
函數也是一種對象,所謂的函數對象
對象是按引用來傳遞的
function Foo() {}; var foo = new Foo(); //Foo為foo的構造函數,簡單理解為類型 console.log(foo instanceof Foo); // true //但是Function並不是foo的構造函數 console.log(foo instanceof Function); // false //Function為Foo的構造函數 alert(Foo instanceof Function); //true
上面的代碼解釋了foo和其構造函數Foo和Foo的構造函數Function的關係,Foo是由Function構造得到,可以簡單理解為,系統中有一個這樣的構造函數:
function Function(name,body) { } var Foo=new Function("Foo","");
1.3.2、Object
對於Object它是最頂層的對象,所有的對象都將繼承Object的原型,但是你也要明確的知道Object也是一個函數對象,所以說Object是被Function構造出來的。
alert(Function instanceof Function);//true alert(Function instanceof Object);//true alert(Object instanceof Function);//true function Foo() {}; var foo = new Foo(); alert(foo instanceof Foo); // true alert(foo instanceof Function); // false alert(foo instanceof Object); // true alert(Foo instanceof Function); // true alert(Foo instanceof Object); // true
JavaScript 原型鏈
function A() {this.x="x";}; var a=new A(); function B() {this.y="y"}; B.prototype=a; var b=new B(); function C() {this.z="z"}; C.prototype=b; var c=new C(); console.log(c instanceof C); console.log(c instanceof B); console.log(c instanceof A); console.log(c instanceof Object); console.log(c.x+","+c.y+","+c.z);
運行結果:
1.4、通過prototype擴展對象
JavaScript內置了很多對象,如Array、Boolean、Date、Function、Number、Object、String
假設我們想給String類型增加一個repeat方法,實現重覆字元,如"a".rpt(),則將輸出aa,"a".rpt(5),輸出“aaaaa”,因為String是JavaScript中內置的對象,可以通過修改該對象的原型達到目的:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>prototype原型</title> </head> <body> <script type="text/javascript"> var v="hi"; String.prototype.rpt=function(n) { n=n||2; var temp=""; for(var i=0;i<n;i++) temp+=this; return temp; } console.log(v.rpt()); console.log(v.rpt(10)); </script> </body> </html>
運行結果:
示例中給String對象的原型增加了一個rpt方法,所有的String都是衍生自String.prototype,那所有的String對象都將獲得rpt方法。
//擴展String類型,增加trim方法用於刪除字元串的首尾空格 String.prototype.trim=function() { return this.replace(/^\s+|\s+$/igm,''); } console.log("[begin]"+" Hello JavaScript ".trim()+"[end]");
運行結果:
為了擴展更加方便,可以修改Function的原型,給每一個函數衍生的對象增加方法method用於擴展。
//修改函數對象的原型,添加method方法,擴展所有的函數 Function.prototype.method=function(name,fun){ //如果當前擴展的函數的原型中不包含名稱為name的對象 if(!this.prototype[name]){ //添加 this.prototype[name]=fun; } } String.method("show",function(){ console.log("輸出:"+this); }); "Hello".show(); "JavaScript".show();
運行結果:
1.5、通過prototype調用函數
我們可以通過對象調用某個方法,因為這個對象的原型中已經定義好了該方法,其實我們通過原型也可以直接調用某個方法,有些方法只存在原型中,只有當前類型關聯了該原型才可以獲得該方法,但有時我們需要使用該方法去處理非該原型下的對象,如:
function add(x,y,z) { var array1=[].slice.call(arguments,0,3); console.log(array1); var array2=Array.prototype.slice.apply(arguments,[0,3]); console.log(array2); } add(1,2,8,9,10);
運行結果:
示例代碼:
var str1 = "Hello JavaScript"; console.log(str1.toUpperCase()); //傳統的調用辦法 var str2=String.prototype.toUpperCase.apply(str1); //通過原形直接調用 console.log(str2); var str3=String.prototype.toUpperCase.call(str1); //通過原形直接調用 console.log(str3); var array1=[2,3,5,7,8,9,10]; var array2=array1.slice(1,3); console.log(array2); var slice=Array.prototype.slice; console.log(slice.apply(array1,[1,3]));//通過原形直接調用 console.log(slice.call(array1,1,3));
運行結果:
練習:擴展Date,增加一個顯示中文日期的方法,如:new Date().trandition(),輸出2016年08月09日 23點15分18秒;使用prototype的方式間接調用該方法。
三、JavaScript面向對象(OOP)
3.1、封裝
function Animal() { this.name="動物"; this.getName=function(){ return this.name; } this.setName=function(name){ this.name=name; } this.bark=function(){ console.log(this.name+"在叫..."); } } var animal=new Animal(); animal.setName("小狗"); animal.bark();
這裡定義的一個構造函數,將name封裝成屬性,Animal函數本身用於封裝動物的屬性與行為,運行結果:
3.2、繼承
JavaScript的繼承的實現主要依靠prototype(原型)來實現,再增加兩個動物,如狗與貓:
function Animal() { this.name="動物"; this.getName=function(){ return this.name; } this.setName=function(name){ this.name=name; } this.bark=function(){ console.log(this.name+"在叫..."); } } function Dog(){ this.hunt=function(){console.log("狗在捕獵");}; } //指定Dog構造函數的原型對象,簡單理解為父類 Dog.prototype=new Animal(); function Cat(){ this.hunt=function(){console.log("貓在抓老鼠");}; } Cat.prototype=new Animal(); var dog=new Dog(); dog.bark(); dog.hunt(); var cat=new Cat(); cat.bark(); cat.hunt();
運行結果:
3.3、多態
java與C#中的多態主要體現在重載與重寫上,因為JavaScript是弱類型的,類方法參數是動態指定的所以並沒有真正意義上的重載,只能在方法中判斷參數達到目的。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript面向對象</title> </head> <body> <script type="text/javascript"> function Animal() { this.name="動物"; this.getName