在JavaScript中this表示:誰調用當前函數this就指向誰,不知道調用者時this指向window。javascript
JavaScript是由對象組成的,一切皆爲對象,萬物皆爲對象。this是一個動態的對象,根據調用的對象不一樣而發生變化,固然也可使用call、apply修改this指向的對象。它表明函數運行時,自動生成的一個內部對象,只能在函數內部使用html
代碼以下:java
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript this</title> </head> <body> <h2>JavaScript this</h2> <input type="button" value="按鈕A" id="btnA"/> <input type="button" value="按鈕B" id="btnB" onclick="sayHello()"/> <script type="text/javascript"> function sayHello(){ alert("Hello Button!"); console.log(this); }; document.getElementById("btnA").onclick=sayHello; </script> </body> </html>
請問輸出到控制檯的結果是什麼?相同嗎?輸出兩個什麼對象?node
答案:git
<input type="button" value="按鈕A" id="btnA"/> Window
在面向過程的語言中咱們習慣把完成某個特定功能的代碼塊稱爲「函數」或「過程」,固然過程通常沒有返回值。在面嚮對象語言中咱們把對象的功能稱爲「方法」。但JavaScript是種介於面向對象與面向過程當中間的語言,一樣的方法有時是函數,有時是方法,以下所示:github
<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>
能夠簡單的認爲若是調用時沒有經過對象沒有指定上下文則爲函數,不然爲方法。chrome
當在所有範圍內使用 this,它將會指向全局對象。通常是window對象,但全局對象不必定只有window,特別是在node.js環境中。做爲函數調用時通常指向全局對象。設計模式
<script type="text/javascript"> var i=5; i=6; console.log(i); //6 console.log(window.i); //6 console.log(this.i); //6 console.log(this); //window </script>
結果:數組
<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>
運行結果:瀏覽器
當函數做爲方法調用時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>
運行結果:
構造函數中的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();
運行結果:
當調用方法是經過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是頂層對象
運行結果:
若是頁面中的元素與事件進行綁定時事件中的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)、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個參數,指向調用時的上下文。
在javascript面向對象中關於原型(prototype)、原型鏈、__proto__、Function、Object等內容是較難理解的,先上幾張便於你們理解的圖:
JavaScript藏寶圖
在JavaScript中,原型也是一個對象,經過原型能夠實現對象的屬性繼承,JavaScript的對象中都包含了一個"Prototype"內部屬性,這個屬性所對應的就是該對象的原型。
"Prototype"做爲對象的內部屬性,是不能被直接訪問的。因此爲了方便查看一個對象的原型,Firefox和Chrome中提供了"__proto__"這個非標準(不是全部瀏覽器都支持)的訪問器(ECMA引入了標準對象原型訪問器"Object.getPrototype(object)")。
(1)、全部構造器/函數的__proto__都指向Function.prototype,它是一個空函數(Empty function)
(2)、全部對象的__proto__都指向其構造器的prototype
(3)、對於全部的對象,都有__proto__屬性,這個屬性對應該對象的原型
(4)、對於函數對象,除了__proto__屬性以外,還有prototype屬性
當一個函數被用做構造函數來建立實例時,該函數的prototype屬性值將被做爲原型賦值給全部對象實例(也就是設置實例的__proto__屬性)
實例說明:
代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //構造器,函數,對象 function Cat(nickname, color) { //各個對象獨有的 this.nickname = nickname; this.color = color; } //全部對象公有的 Cat.prototype.life = 9; Cat.prototype.bark = function() { console.log(this.nickname, this.color, this.life, "喵喵..."); } var c1 = new Cat("波斯", "綠色"); c1.bark(); var c2 = new Cat("加菲", "紅色"); c2.bark(); </script> </body> </html>
控制檯:
關係圖:
(1)、全部的函數/構造器都擁有prototype屬性,用於指向原型對象。
(2)、全部的函數便是構造器又是對象。
(3)、全部的對象都擁有__proto__非標準屬性。
(4)、全部函數(構造器)的__proto__都指向Function.prototype對象。
(5)、全部對象的__proto__指向它的構造器的prototype對象。
(6)、全部函數都是由Function構造出來的,Function本身構造了本身,Object是由Function構造出來的,Function是構造器。
(7)、全部對象的最終原型對象都指向了Object的prototype屬性,Object的prototype對象的__proto__屬性指向NULL。
(8)、全部prototype原型對象中的constructor屬性都指向其構造器。
(9)、原型對象prototype中的成員是全部對象共有的。
(10)、對象在查找成員時先找本對象本身的全部成員,再查找構造器的原型中的成員(向上再查找父類的成員,多步),最終查詢Object的成員。
JavaScript Object Layout:
Object Relationship Diagram with Inheritance
JavaScript是一種經過原型實現繼承的語言與別的高級語言是有區別的,像java,C#是經過類型決定繼承關係的,JavaScript是的動態的弱類型語言,總之能夠認爲JavaScript中全部都是對象,在JavaScript中,原型也是一個對象,經過原型能夠實現對象的屬性繼承,JavaScript的對象中都包含了一個" prototype"內部屬性,這個屬性所對應的就是該對象的原型。
"prototype"做爲對象的內部屬性,是不能被直接訪問的。因此爲了方便查看一個對象的原型,Firefox和Chrome內核的JavaScript引擎中提供了"__proto__"這個非標準的訪問器(ECMA新標準中引入了標準對象原型訪問器"Object.getPrototype(object)")。
如今有一個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方法是錯誤的。總之原型的主要做用就是爲了實現繼承與擴展對象。
在JavaScript中,原型也是一個對象,經過原型能夠實現對象的屬性繼承,JavaScript的對象中都包含了一個」[[Prototype]]」內部屬性,這個屬性所對應的就是該對象的原型。
「[[Prototype]]」做爲對象的內部屬性,是不能被直接訪問的。因此爲了方便查看一個對象的原型,Firefox和Chrome中提供了__proto__這個非標準(不是全部瀏覽器都支持)的訪問器(ECMA引入了標準對象原型訪問器」Object.getPrototype(object)」)。在JavaScript的原型對象中,還包含一個」constructor」屬性,這個屬性對應建立全部指向該原型的實例的構造函數
在 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。
使用 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);
運行結果:
函數就是對象,表明函數的對象就是函數對象。全部的函數對象是被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","");
如上面例子中的構造函數,JavaScript中函數也是對象,因此就能夠經過_proto_查找到構造函數對象的原型。
Function對象做爲一個函數,就會有prototype屬性,該屬性將對應」function () {}」對象。
Function對象做爲一個對象,就有__proto__屬性,該屬性對應」Function.prototype」,也就是說,」Function._proto_ === Function.prototype」。
在這裏對「prototype」和「proto」進行簡單的介紹:
對於全部的對象,都有__proto__屬性,這個屬性對應該對象的原型。
對於函數對象,除了__proto__屬性以外,還有prototype屬性,當一個函數被用做構造函數來建立實例時,該函數的prototype屬性值將被做爲原型賦值給全部對象實例(也就是設置實例的__proto__屬性)
對於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);
運行結果:
當查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性爲止,到查找到達原型鏈的頂部(也就是 Object.prototype),若是仍然沒有找到指定的屬性,就會返回 undefined。
Object對象自己是一個函數對象。既然是Object函數,就確定會有prototype屬性,因此能夠看到」Object.prototype」的值就是」Object {}」這個原型對象。反過來,當訪問」Object.prototype」對象的」constructor」這個屬性的時候,就獲得了Obejct函數。
另外,當經過」Object.prototype._proto_」獲取Object原型的原型的時候,將會獲得」null」,也就是說」Object {}」原型對象就是原型鏈的終點了。
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();
運行結果:
咱們能夠經過對象調用某個方法,由於這個對象的原型中已經定義好了該方法,其實咱們經過原型也能夠直接調用某個方法,有些方法只存在原型中,只有當前類型關聯了該原型才能夠得到該方法,但有時咱們須要使用該方法去處理非該原型下的對象,如:
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的方式間接調用該方法。
prototype是函數纔有的屬性,prototype自己就是一個對象,prototype對象中擁有constractor構造器(該構造器反向指回函數)
__proto__是對象帶有屬性,用於訪問建立對象的類型所對應的原型
字面量建立的對象(o={})或Object建立的對象__proto__指向Object.prototype
示例:
var a = {}; console.log(a.prototype); //undefined console.log(a.__proto__); //Object {} var b = function(){} console.log(b.prototype); //b {} console.log(b.__proto__); //function() {}
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> //構造方法,類,函數 function Foo(name) { this.name = name; } var f1 = new Foo("zhangguo"); //prototype是函數纔有的屬性,prototype自己就是一個對象,prototype對象中擁有constractor構造器(該構造器反向指回函數) console.log(f1.prototype); //undefined console.log(Foo.prototype); //function Foo.prototype //__proto__是對象帶有屬性,用於訪問建立對象的類型所對應的原型 console.log(f1.__proto__); //function Foo.prototype console.log(Foo.__proto__); //function(){} //Foo.__proto__ 就是Function.prototype console.log(Foo.__proto__===Function.prototype); //true console.log(Foo.prototype===f1.__proto__); //true //字面量建立的對象(o={})或Object建立的對象__proto__指向Object.prototype var o={}; console.log(o.__proto__===Object.prototype); //true var obj=new Object(); console.log(obj.__proto__===Object.prototype); //true var obj2=Object.create({}); console.log(obj2.__proto__===Object.prototype); //false // console.log(obj1.prototype); // console.log(obj1.__proto__); // console.log(Foo.prototype); </script> </body> </html>
結果:
/*一、字面量方式*/ var a = {}; console.log(a.__proto__); //Object {} console.log(a.__proto__ === a.constructor.prototype); //true /*二、構造器方式*/ var A = function(){}; var a = new A(); console.log(a.__proto__); //A {} console.log(a.__proto__ === a.constructor.prototype); //true /*三、Object.create()方式*/ var a1 = {a:1} var a2 = Object.create(a1); console.log(a2.__proto__); //Object {a: 1} console.log(a.__proto__ === a.constructor.prototype); //false(此處即爲圖1中的例外狀況)
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> //構造方法,類,函數 function Foo(name) { this.name = name; } var f1 = new Foo("zhangguo"); console.log(f1.constructor === Foo); //true console.log(f1.__proto__ === Foo.prototype); //true /*一、字面量方式*/ var a = {}; //a的__proto__是指向了a的構造器所指向的原型 //字面量建立的對象的__proto__指向了Object的原型 console.log(a.__proto__ === a.constructor.prototype); //true /*二、構造器方式*/ var A = function() {}; var a = new A(); console.log(a.__proto__); //A的原型 console.log(a.__proto__ === a.constructor.prototype); //true console.log(a.__proto__ === A.prototype); //true /*三、Object.create()方式*/ var a1 = {a: 1} var a2 = Object.create(a1); console.log(a2.__proto__===a1); //true,a1就是a2的原型(父類) </script> </body> </html>
結果:
示例:
var A = function(){}; var a = new A(); console.log(a.__proto__); //A {}(即構造器function A 的原型對象) console.log(a.__proto__.__proto__); //Object {}(即構造器function Object 的原型對象) console.log(a.__proto__.__proto__.__proto__); //null
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> //構造方法,類,函數 function Foo(name) { this.name = name; } var f1 = new Foo("zhangguo"); //一、var f1={}; //二、f1.__proto__=Foo.prototype; //三、Foo.call(f1,"zhangguo"); //在Foo的原型中添加屬性age Foo.prototype.age = 80; Foo.prototype.show = function() { console.log("name:" + this.name + ",age:" + this.age); } var f2 = new Foo("zhangsan"); console.log(f1.age == f2.age); f1.show(); f2.show(); var A = function() {}; var a = new A(); console.log(a.__proto__); //A {}(即構造器function A 的原型對象) console.log(a.__proto__.__proto__); //Object {}(即構造器function Object 的原型對象) console.log(a.__proto__.__proto__.__proto__); //null </script> </body> </html>
結果:
當查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性爲止,到查找到達原型鏈的頂部(也就是 Object.prototype),若是仍然沒有找到指定的屬性,就會返回 undefined。
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.MaxNumber = 9999; Person.__proto__.MinNumber = -9999; var will = new Person("Will", 28); console.log(will.MaxNumber); // 9999 console.log(will.MinNumber); // undefined
在這個例子中分別給」Person.prototype 「和」 Person.proto」這兩個原型對象添加了」MaxNumber 「和」MinNumber」屬性,這裏就須要弄清」prototype」和」proto」的區別了。
「Person.prototype 「對應的就是Person構造出來全部實例的原型,也就是說」Person.prototype 「屬於這些實例原型鏈的一部分,因此當這些實例進行屬性查找時候,就會引用到」Person.prototype 「中的屬性。
對象建立方式影響原型鏈
var July = { name: "張三", age: 28, getInfo: function(){ console.log(this.name + " is " + this.age + " years old"); } } console.log(July.getInfo());
當使用這種方式建立一個對象的時候,原型鏈就變成下圖了. July對象的原型是」Object.prototype」也就是說對象的構建方式會影響原型鏈的形式。
在JavaScript中,每一個函數 都有一個prototype屬性,當一個函數被用做構造函數來建立實例時,這個函數的prototype屬性值會被做爲原型賦值給全部對象實例(也就是設置 實例的`__proto__`屬性),也就是說,全部實例的原型引用的是函數的prototype屬性。(只有函數對象纔會有這個屬性!)
使用new關鍵字調用函數(new ClassA(…))的具體步驟:
1. 建立空對象;
var obj = {};
2. 設置新對象的constructor屬性爲構造函數,設置新對象的__proto__屬性指向構造函數的prototype對象;
obj.__proto__ = ClassA.prototype;
3. 使用新對象調用構造函數,函數中的this被指向新實例對象:
ClassA.call(obj); //{}.構造函數();
4. 將初始化完畢的新對象地址,保存到等號左邊的變量中
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> function Car(name){ this.name=name; } //一、直接建立 var c1=new Car("BYD"); //二、實際建立對象的步驟 //2.一、建立空的對象字面量 var c2={}; //2.二、將對象的__proto__指向構造器的原型對象(prototype) c2.__proto__=Car.prototype; //2.三、調用構造器,初始化建立的對象 Car.call(c2,"BYD"); //2.四、將建立的對象地址賦值給變量(返回給左邊的變量) </script> </body> </html>
結果:
注意:若構造函數中返回this或返回值是基本類型(number、string、boolean、null、undefined)的值,則返回新實例對象;若返回值是引用類型的值,則實際返回值爲這個引用類型。
new 的過程分爲三步
var p = new Person('張三',20); 1. var p={}; 初始化一個對象p。 2. p._proto_=Person.prototype;,將對象p的 __proto__ 屬性設置爲 Person.prototype 3. Person.call(p,」張三」,20);調用構造函數Person來初始化p。
一、prototype是函數纔有的屬性
二、__proto__是每一個對象都有的屬性,它不是W3C的標準屬性,它指向prototype
三、__proto__能夠理解爲「構造器原型」,__proto__===constructor.prototype(Object.create()建立的對象不適用)
四、當js引擎查找對象的屬性時,先查找對象自己是否存在該屬性,若是不存在會在原型鏈上查找,但不會查找本身的prototype
五、函數的原型對象constructor默認指向函數自己,原型對象除了有原型屬性外,爲了實現繼承,還有一個原型鏈指針__proto__,該指針指向上一層的原型對象,而上一層的原型對象的結構依然相似,這樣利用__proto__一直指向Object的原型對象上,而Object的原型對象用Object.prototype.__proto__ = null表示原型鏈的最頂端,如此變造成了javascript的原型鏈繼承,同時也解釋了爲何全部的javascript對象都具備Object的基本方法。
function Foo() {} var foo = new Foo(); console.log(foo.prototype);// undefined console.log(foo.__proto__ === Foo.prototype);// true console.log(Foo.prototype);// [object Object] console.log(Foo.prototype.prototype);// undefined
解釋
只有函數對象有 prototype 屬性(通常對象本身加的不算)
一、 foo 是 Foo 的一個實例,可是不是一個函數,因此沒有prototype;Foo是Function的一個實例,而Function是一個函數,他的實例Foo也是一個函數,因此他們都有prototype。此外Object Array RegExp等也是函數。Math就僅僅是一個new Object() ,不是函數。
二、構造函數的prototype,默認狀況下就是一個new Object()還額外添加了一個constructor屬性。因此說默認是沒有prototype只有__proto__的。
除了Object.prototype這個對象,其餘全部的對象都會有__proto__屬性,以後函數纔會有prototype屬性。
在建立對象的時候會自動建立一個__proto__屬性,指向它構造函數的prototype,當訪問這個對象的屬性的時候會順帶訪問__proto__中對應的屬性,也就是構造函數prototype這樣實現了繼承。
只有建立函數的時候纔會建立一個prototype屬性,目的就是爲了完成上面的繼承方式。
圖(橙色箭頭是初始的關係,綠色是執行var Fish = new Fu...建立,藍色是執行f1= new Fish()建立。)
這樣f1 就能夠經過__proto__ 訪問 Fish.prototype中的屬性(固然這是程序執行的時候自動查找的)。Fish就能夠訪問 Function.prototype定義的屬性。全部對象均可以訪問Object.prototype 中的屬性。
一、全部的函數/構造器都擁有prototype屬性,原型必定是對象
二、全部原型對象擁有constructor屬性指回其構造函數,原型中存放全部構造器建立的對象共有成員
三、JavaScript中經過原型鏈實現繼承,將對象與對象串聯起來,查詢成員時由近到遠
四、全部的對象都將指向其構造器的原型對象,默認不容許直接訪問,但chrome等瀏覽器中能夠經過__proto__訪問到(全部對象的__proto_指向其構造器的原型對象)
五、JavaScript中一切都是對象,函數建立了對象其自己又是對象,對象都指定原型
六、全部的函數都是Function建立出來的,Function本身建立了本身
七、Object函數是Function建立出來的,Object函數的__proto__指向Function的原型對象
八、Function的原型對象的__proto__指向Object的原型對象,Object的原型對象指向了null,這是原型鏈的盡頭
由於在JavaScript中沒有訪問修飾符,也沒有塊級做用域,this所定義的屬性默認對外就是公開訪問的,以下示例:
function Animal(){ this.name="動物"; this.weight=0; //重量 } var cat=new Animal(); cat.name="貓"; cat.weight=-90; console.log(cat);
輸出:
weight是-90,不正確,封裝:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> function Animal() { this.name = "動物"; var weight = 0; //重量 this.getWeight = function() { //讀 return weight; }; this.setWeight = function(_weight) { //寫 if(_weight > 0) { weight = _weight; } else { throw "動物的重量必須爲正數"; } } } var cat = new Animal(); cat.name = "貓"; try{ cat.setWeight(-90); }catch(e){ console.log(e); } console.log(cat); </script> </body> </html>
結果:
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函數自己用於封裝動物的屬性與行爲,運行結果:
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();
運行結果:
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=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(); //重寫原型對象中的bark方法 Dog.prototype.bark=function(){ console.log("汪汪..."); } function Cat(){ this.hunt=function(){console.log("貓在抓老鼠");}; } Cat.prototype=new Animal(); //重寫原型對象中的bark方法 Cat.prototype.bark=function(){ console.log("喵喵..."); } function play(animal) { animal.bark(); animal.hunt(); } var dog=new Dog(); var cat=new Cat(); play(dog); play(cat); </script> </body> </html>
運行結果:
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //二種重寫的辦法 function Animal(name) { this.name = name || "動物"; this.bark = function() { console.log(this.name + "叫了:嗷嗷..."); } }; function Cat() { this.hunt = function() { console.log(this.name + "正在捕老鼠!"); } } Cat.prototype = new Animal("貓"); Cat.prototype.bark = function() { console.log("喵喵"); } var cat = new Cat(); cat.hunt(); cat.bark(); function Dog() { this.hunt = function() { console.log(this.name + "正在捕獵!"); } this.bark = function() { console.log("汪汪"); } } //讓Dog繼承Animal,修改原型的指向 Dog.prototype = new Animal("狗"); var dog = new Dog(); dog.hunt(); dog.bark(); //重載的辦法 function add(n) { if(typeof n === "number") { return ++n; } if(n instanceof Array){ var sum=0; for(var i=0;i<n.length;i++){ sum+=n[i]; } return sum; } } console.log(add(100)); console.log(add([1, 2, 3, 4, 5])); </script> </body> </html>
結果:
練習:請使用javascript完成一個簡單工廠設計模式。
https://github.com/zhangguo5/javascript003.git
https://git.coding.net/zhangguo5/javascript_01.git
https://git.dev.tencent.com/zhangguo5/javascriptpro.git