若是沒有面向對象這種抽象概念的小夥伴,建議先看一下我寫的
JS基礎入門篇(三十四)—面向對象(一)👏👏👏👏程序員
想要如下爲 f 添加一個say方法,有三種方法。segmentfault
<script> function Fn(){}; var f = new Fn(); </script>
方法一:至關於添加一個自定義屬性,此屬性是一個方法。
數組
<script> function Fn(){}; var f = new Fn(); f.say = function(){ console.log(1); } f.say(); //打印 1 </script>
方法二:爲構造函數.prototype添加一個say方法。
app
<script> function Fn(){}; var f = new Fn(); Fn.prototype.say = function(){ console.log(2); } f.say(); //打印 2 </script>
方法三:爲Object.prototype添加一個say方法。
函數
<script> function Fn(){}; var f = new Fn(); Object.prototype.say = function(){ console.log(3); } f.say(); //打印 3 </script>
疑問🤔️:
方法二中掛在構造函數的方法,和方法三中掛在Object的方法, f 爲何能查找的到???post
解析(此解析必定要看懂,沒有看懂多看幾遍或者百度下):
是原型鏈的概念。就是js內部的查找機制。首先要明白:this
1.prototype 原型spa
當一個函數被申明的時候,該函數下默認有一個屬性:prototype,該屬性的值是一個對象。
舉例說明:prototype
<script> function Fn(){} var f = new Fn(); console.log( Fn.prototype ); </script>
結果如圖所示:
3d
2.__proto__
當一個對象被建立的時候,該對象會自動被添加上一個屬性:__proto__,他的值也是一個對象,而且該屬性 就是 當前這個對象的構造函數的prototype
舉例說明:
<script> function Fn(){} var f = new Fn(); console.log( f.__proto__ ); </script>
結果如圖所示:
3.對象.__proto__ === 構造函數.prototype
舉例說明
<script> function Fn(){}; Fn.prototype.say=function () { console.log(1); }; var f = new CreatePreson(); f.say=function () { console.log(2); }; console.log( f.__proto__ ); console.log( Fn.prototype ); console.log( Fn.prototype === f.__proto__ ); </script>
結果如圖所示:
因此查找機制爲:
調用f.say( );時
if( 對象 f上面是否say方法 ){//爲真,執行if內部的代碼 則調用f上面的say方法 }else if(Fn.prototype是否有say方法){//爲真,執行else if內部的代碼 第一步:f.__proto__ === Fn.prototype 由這個查找到f對應的構造函數的原型,即爲 Fn.prototype。 第二步:查看Fn.prototype是否有say方法,有的話,則調用Fn.prototype是上面的say方法。 }else if( Object.prototype是否有say方法 ){ 第一步:Fn.prototype.__proto__ === Object.prototype 由這個查找到Fn.prototype對應的構造函數的原型,即爲 Object.prototype。 第二步:Object.prototype是否有say方法,有的話,則調用Object.prototype是上面的say方法。 }else{//若是以上都沒有say方法 會報錯。 }
舉例說明
<script> function Fn() { } var f = new Fn(); f.say = function () { console.log(1); }; Fn.prototype.say = function () { console.log(2); }; Object.prototype.say = function () { console.log(3); }; f.say();//打印1。由於在f上面找到了,就不會往下繼續找了。 </script>
1.hasOwnPropert
🌹🌹🌹
做用 用來判斷某個對象是否含有 指定的 自身屬性 語法 boolean object.hasOwnProperty(prop) 參數 object 要檢測的對象 prop 要檢測的屬性名稱。 注意:不會沿着原型鏈查找屬性,只查找自身屬性
若是以上文字都看不懂,能夠先看例子,再看文字。
<script> //建立構造函數 function CreatPerson(name, age) { this.name = name; this.age = age; } CreatPerson.prototype.kind = "人類"; CreatPerson.prototype.say = function () { console.log("我會說話 "); }; //生成對象,實例化的過程 var p = new CreatPerson("Lily",28); //調用hasOwnProperty方法,查看是不是自身的屬性,再也不在原型鏈上面找。 console.log(p.hasOwnProperty("name"));//true console.log(p.hasOwnProperty("age"));//true console.log(p.hasOwnProperty("kind"));//false console.log(p.hasOwnProperty("say"));//false </script>
2.constructor
🌹🌹🌹
函數的原型prototype的值是一個對象,初始化會有一個屬性爲constructor, 對應的值爲擁有這個原型的函數 注意:prototype的值是能夠修改的,修改了prototype的值, 要手動將constructor指向函數
<script> function Fn() { console.log("構造函數"); } console.log(Fn.prototype.constructor); // Fn(){console.log("構造函數"); //由於arr 是經過字面量的方式生成一個數組,可是函數內部仍是會經過new Array 生成arr對象 //因此Array是arr的構造函數 //arr沒有constructor,會根據原型鏈查找,找到JS內部的Array.prototype上的constructor方法。 //Array.prototype.constructor指向Array var arr = [1, 2, 3]; console.log(arr.constructor); // Array() { [native code] } //由於obj 是經過字面量的方式生成一個對象,可是函數內部仍是會經過new Object 生成obj對象 //因此Object是obj的構造函數 //obj沒有constructor,會根據原型鏈查找,找到JS內部的Object.prototype上的constructor方法。 //Object.prototype.constructor指向Object var obj = {}; console.log(obj.constructor); //Object() { [native code] } </script>
3.instanceof
🌹🌹🌹
instanceof 是一個二元運算符,返回布爾值 運算檢測 一個 對象原型 是否 在要檢測的對象的原型鏈上 使用:object instanceof constructor
<script> var arr = []; console.log( typeof arr );//"object" console.log( arr instanceof Array);//true console.log( arr instanceof Object);//true //str是字面量生成的,是由JS內部的String構造函數new出來的。 //可是str會馬上"壓扁"本身,讓本身不是對象。 //因此str都不是對象了,天然instanceof String 的獲得的值爲fasle //但str.indexOf(),str仍是能夠調用indexOf()方法的緣由是,當它調用方法的時候,會從新將本身包裝成對象。 //使用結束後會從新"壓扁"本身,讓本身不是對象。 var str = "123"; console.log( str instanceof Array );//false console.log( str instanceof String);//false console.log( str instanceof Object);//false var obj = {}; console.log( obj instanceof Array );//false console.log( obj instanceof Object);//true // Array.prototype -> Object.prototype </script>
1.誰調用就指向誰。
2.誰觸發就指向誰。
舉例說明1
<script> function fn() { console.log(this); } fn();//打印結果:Window 解析:至關於 window.fn(); 因此指向window document.onclick=fn;//打印結果:document 解析: 由document的觸發,因此指向document </script>
舉例說明2
<script> var obj={ n:"k", foo:function () { console.log(this); console.log(this.n); } }; obj.foo(); //運行結果爲: //{n: "k", foo: ƒ} //k //解析:obj.foo();是obj調用foo對應的函數。因此this指向obj。 var b = obj.foo; b(); //運行結果爲: //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} //undefined //解析:var b = obj.foo; ==== var b=function () { console.log(this); console.log(this.n);} //變量b是Window 的自定義屬性,因此b(); === window.b(); //因此其中的this指向window,this.n === window.n //因爲window上面沒有n這個自定義屬性,則打印出來爲 undefined </script>
1.call
1. 函數**`會`**馬上執行 2. 函數執行時候,函數**`第一個參數`**是內部的**`this指向`** 3. **`第一個參數以後的參數,都是指 函數執行時候 ,內部的實參`**
直接擼代碼,舉例說明
<script> function Fn() { console.log(this); } Fn.call(); //結果爲:Window // 1.函數會當即執行 // 2.不傳入任何參數,this的指向不變,仍是指向window。 Fn.call(document); //結果爲:#document // 1.函數會當即執行 // 2.括號中的內容 第一個參數 就是 函數執行時候 ,內部的this指向 function Go(a,b) { console.log(this); console.log(a,b); } Go.call(document,2,3); //結果爲: // #document // 2 3 // 1.函數會當即執行 // 2.括號中的內容 第一個參數 就是 函數執行時候 ,內部的this指向 // 3.第一個參數以後的參數,都是指 函數執行時候 ,內部的實參 </script>
2.bind
1. 函數**`不會`**馬上執行 2. 函數執行時候,**函數第一個參數是內部的this指向** 3. **第一個參數以後的參數,都是指 函數執行時候 ,內部的實參** 4. **`返回的是 修改了 this指向的新函數`** 與call的區別就是函數不會馬上執行。
舉例說明
<script> function foo (a,b) { console.log( this ); console.log( a,b ); } var fn = foo.bind( document,2,3);// 函數 不會 馬上執行,返回的是 修改了 this指向的新函數 fn();//調用以後纔會執行 this指向的新函數 //運行結果: //#document //2 3 </script>
3.apply
與call很類似,只是第二個參數值接受數組
舉例說明
<script> function foo (a,b) { console.log( this ); console.log( a,b ); } foo.apply( document,[2,3] ); // 和call 類似 直接調用 , 不過第二個參數接受數組 //運行結果: //#document //2 3 </script>
由於由typeof打印出來,數組和對象的結果都是object。有時候咱們須要判斷變量是不是數組
。
方法一:
var arr = [1,2,3]; console.log( arr.toString() );//1,2,3 Array.prototype.toString = Object.prototype.toString;//從新賦值Array.prototype.toString的方法。可是下次在別的狀況調用Array.prototype.toString,此方法已被從新覆蓋。因此不太好 console.log( arr.toString() );//[object Array]
var arr = [1,2,3];
console.log( Object.prototype.toString.call(arr) );
// 使用 Object.prototype.toString
// 同時 修改內部的this指向 arr
console.log( arr );//[object Array]
方法二:
繼承
在JavaScript中,繼承就是讓一個對象(子類)擁有另外一個對象(父類)的屬性/方法(還有原型上的屬性和方法)。其中原則就是:
1.子類的修改不能影響父類
2.子類能夠 在 父類 基礎上 添加本身的屬性 和 方法
1.經過prototype 賦值 (行不通,可是仍是要看行不通的緣由)
舉例說明:上代碼
<script> function CreatPerson() {} CreatPerson.prototype.say = function () { console.log("我會說漢語"); }; function Coder(){} // 此處 子類 的 prototype和父類的 prototype 指的是 同一個對象。 // 的確是繼承CreatPerson的原型上面的方法,可是當Coder.prototype重寫say方法 // CreatPerson.prototype中的say方法也會被改寫 Coder.prototype = CreatPerson.prototype; Coder.prototype.say=function () {// console.log("我會說漢語,還會碼代碼"); }; var person = new CreatPerson(); person.say(); var coder = new Coder(); coder.say(); </script>
2.原型鏈繼承
子類的原型 = 父類的實例 注意 : 在爲 子類 原型 賦值的時候去修正 constructor 弊端 : 子類構造函數內的地址的修改會修改其餘子類。 由於全部子類構造函數的原型共享一個實例。
舉例說明
<script> function CreatPerson() { this.age=18; this.arr=[1,2,3]; } CreatPerson.prototype.say=function () { console.log("我會說漢語"); }; CreatPerson.prototype.eat=function () { console.log("我想吃飯"); }; function Coder() {} Coder.prototype = new CreatPerson();//子類構造函數內的地址的修改會修改其餘子類。由於全部子類構造函數的原型共享一個實例 Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor Coder.prototype.say=function () { console.log("我會說漢語,還會碼代碼"); }; var person=new CreatPerson(); var coder1=new Coder(); person.say();//我會說漢語 coder1.say();//我會說漢語,還會碼代碼 coder1.eat();//我想吃飯 //----------能夠繼承父類,修改子類也不會影響到父類。可是子類修改會影響到子類------------------- var coder2=new Coder(); coder2.age=10; //coder2.age -> Coder.prototype.age === new CreatPerson().age // 存儲的是值,Coder.prototype.age的改變,只會影響當前對象的age // 別的子類影響不到 coder2.arr.push(4); //coder2.arr -> Coder.prototype.arr === new CreatPerson().arr // 存儲的是地址,Coder.prototype.arr 修改,new CreatPerson().arr 取到的內容就是修改後的內容 console.log(coder1.age);//18 console.log(coder1.arr);//[1, 2, 3, 4] console.log(coder2.age);//10 console.log(coder2.arr);//[1, 2, 3, 4] console.log(person.age);//18 console.log(person.arr);//[1, 2, 3] </script>
對原型鏈繼承遇到問題的解決的方案一:
改成:
function Coder() { this.arr=[1,2,3];//這樣查找的時候,對象上面就有了,不會查找到上一層,既不會修改到。 }
解析:此方法麻煩,若是父類有不少自定義屬性都是對象或者方法,那麼子類都要從新複製一遍。
對原型鏈繼承遇到問題的解決的方案二:
借用構造函數 在子類中執行父類的構造函數 修改子類構造函數中的 this指向 只能繼承父類構造函數中的方法和屬性 繼承不到父類構造函數原型鏈中的方法和屬性
改成:
function Coder() { CreatPerson.call(this);// // 此處的 this 指的 是 Coder 的 實例 }
總結:經過原型鏈繼承的正確寫法。
<script> //父類構造函數 function CreatPerson( name ) { this.age = 18; this.arr = [123]; this.name = name; } CreatPerson.prototype.say = function () { console.log("我會說漢語"); }; //子類構造函數 function Corder(name,job) { CreatPerson.call(this,name);//繼承父類上非原型上的屬性和方法。 this.job=job;//子類擴展的自定義屬性 } Corder.prototype=new CreatPerson();//繼承父類上原型上的屬性和方法。 Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor Corder.prototype.say=function () {//重寫父類上面的say方法,並不修改父類的say方法 console.log("我會說漢語,我是程序員!!!"); }; //父類的對象實例化 console.log("------------ 父類1 -----------"); var person=new CreatPerson("jack"); console.log(person.age);//18 console.log(person.name);//jack //子類1的對象實例化 console.log("------------ 子類1 -----------"); var corder1=new Corder("rose","worker"); corder1.arr=[234]; corder1.say();//我會說漢語,我是程序員!!! console.log(corder1.age);//18 console.log(corder1.arr);//[234] console.log(corder1.name);//rose console.log(corder1.job);//worker //子類2的對象實例化 console.log("------------ 子類2 -----------"); var corder2=new Corder("Mary","corder"); console.log(corder2.age);//18 console.log(corder2.arr);//[123]子類與子類之間的 自定義屬性 沒有受到影響 console.log(corder2.name);//Mary console.log(corder2.job);//corder corder2.say();//我會說漢語,我是程序員!!! console.log("------------ 父類1 -----------"); person.say();///我會說漢語 父類原型上面的方法 沒有受到影響 console.log(person.arr);//[123] 父類自定義屬性 沒有受到影響 </script>
提醒本身: Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor。 不要忘記修正子類的constructor。
3.拷貝式繼承
1. 完成拷貝式繼承首先要知道如何拷貝對象。因此先來拷貝對象
<script> //拷貝對象的內容 function cloneFn( sourse ) { var obj= (Object.prototype.toString.call(sourse). indexOf("Array")!==-1)?[]:{};//若是對象是數組,就應該建立數組。若是是非數組的對象,就應該建立對象。 for(var attr in sourse){ if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//若是對象內的鍵值仍是對象,進行更深一步的拷貝 obj[attr]=cloneFn( sourse[attr] ); }else{ obj[attr]=sourse[attr]; } } return obj; } var a={ abc:1, abc2:2, arr:[1,23,4] }; var clone=cloneFn( a ); clone.abc=9; clone.arr.push(5);//不會影響a中的arr console.log(clone);//{abc: 9, abc2: 2, arr: Array(4)} console.log(a.abc);//1 console.log(a.arr);//[1, 23, 4] </script>
2.拷貝繼承
<script> //拷貝對象的內容 function cloneFn( sourse ) { var obj= (Object.prototype.toString.call(sourse). indexOf("Array")!==-1)?[]:{};//若是對象是數組,就應該建立數組。若是是非數組的對象,就應該建立對象。 for(var attr in sourse){ if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//若是對象內的鍵值仍是對象,進行更深一步的拷貝 obj[attr]=cloneFn( sourse[attr] ); }else{ obj[attr]=sourse[attr]; } } return obj; } //父類構造函數 function CreatPerson( name ) { this.age = 18; this.arr = [123]; this.name = name; } CreatPerson.prototype.say = function () { console.log("我會說漢語"); }; //子類構造函數 function Corder(name,job) { CreatPerson.call(this,name);//繼承父類上非原型上的屬性和方法。 this.job=job;//子類擴展的自定義屬性 } Corder.prototype=cloneFn(CreatPerson.prototype);//拷貝父類上原型上的屬性和方法。 Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor Corder.prototype.say=function () {//重寫父類上面的say方法,並不修改父類的say方法 console.log("我會說漢語,我是程序員!!!"); }; //父類的對象實例化 console.log("------------ 父類1 -----------"); var person=new CreatPerson("jack"); console.log(person.age);//18 console.log(person.name);//jack //子類1的對象實例化 console.log("------------ 子類1 -----------"); var corder1=new Corder("rose","worker"); corder1.arr=[234]; corder1.say();//我會說漢語,我是程序員!!! console.log(corder1.age);//18 console.log(corder1.arr);//[234] console.log(corder1.name);//rose console.log(corder1.job);//worker //子類2的對象實例化 console.log("------------ 子類2 -----------"); var corder2=new Corder("Mary","corder"); console.log(corder2.age);//18 console.log(corder2.arr);//[123]子類與子類之間的 自定義屬性 沒有受到影響 console.log(corder2.name);//Mary console.log(corder2.job);//corder corder2.say();//我會說漢語,我是程序員!!! console.log("------------ 父類1 -----------"); person.say();///我會說漢語 父類原型上面的方法 沒有受到影響 console.log(person.arr);//[123] 父類自定義屬性 沒有受到影響 </script>