經過原型這種機制,JavaScript 中的對象從其餘對象繼承功能特性;這種繼承機制與經典的面向對象編程語言的繼承機制不一樣.javascript
JavaScript 常被描述爲一種基於原型的語言 (prototype-based language)——每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性.原型對象也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推.這種關係常被稱爲原型鏈 (prototype chain),它解釋了爲什麼一個對象會擁有定義在其餘對象中的屬性和方法.html
準確地說,這些屬性和方法定義在 Object 的構造器函數(constructor functions)之上的 prototype 屬性上,而非對象實例自己.java
在傳統的 OOP 中,首先定義「類」,此後建立對象實例時,類中定義的全部屬性和方法都被複制到實例中.在 JavaScript 中並不如此複製——而是在對象實例和它的構造器之間創建一個連接(它是proto屬性,是從構造函數的 prototype 屬性派生的),以後經過上溯原型鏈,在構造器中找到這些屬性和方法.git
理解對象的原型(能夠經過 Object.getPrototypeOf(obj)或者已被棄用的proto屬性得到)與構造函數的 prototype 屬性之間的區別是很重要的.前者是每一個實例上都有的屬性,後者是構造函數的屬性.也就是說,Object.getPrototypeOf(new Foobar())和 Foobar.prototype 指向着同一個對象.github
函數能夠有屬性, 每一個函數都有一個特殊的屬性叫做原型(prototype).編程
測試一下數組
function pro(){} pro.inner = "pro-inner" pro.prototype.out = "out function" console.log(pro) var pro2 = function(){} pro2.inner = "pro2-inner" pro2.prototype.out = "var varible" console.log(pro2.prototype)
構造函數廣泛使用首字母大寫的命名方式,這不是 js 這個語言強制規定的,而是人們在使用的過程當中一種約定成俗瀏覽器
控制檯打印出來的對象安全
out: "var varible" constructor: ƒ () inner: "pro2-inner" length: 0 name: "pro2" arguments: null caller: null prototype: {out: "var varible", constructor: ƒ} __proto__: ƒ () [[FunctionLocation]]: pen.js:7 [[Scopes]]: Scopes[1] __proto__: constructor: ƒ Object() __defineGetter__: ƒ __defineGetter__() __defineSetter__: ƒ __defineSetter__() hasOwnProperty: ƒ hasOwnProperty() __lookupGetter__: ƒ __lookupGetter__() __lookupSetter__: ƒ __lookupSetter__() isPrototypeOf: ƒ isPrototypeOf() propertyIsEnumerable: ƒ propertyIsEnumerable() toString: ƒ toString() valueOf: ƒ valueOf() toLocaleString: ƒ toLocaleString() get __proto__: ƒ __proto__() set __proto__: ƒ __proto__()
使用 new 運算符來在如今的這個原型基礎之上,建立一個 proIns 實例.使用 new 運算符的方法就是在正常調用函數時,在函數名的前面加上一個 new 前綴. 經過這種方法,在調用函數前加一個 new ,它就會返回一個這個函數的實例化對象.閉包
var pro2 = function(){} pro2.inner = "pro2-inner" pro2.prototype.out = "var varible" var proIns = new pro2() proIns.prop = "add prop" console.log(proIns) console.log(proIns.prototype) console.log(pro2.prototype)
控制檯輸出的內容
prop: "add prop" __proto__: out: "var varible" constructor: ƒ () __proto__: Object
proIns 的 proto 屬性就是 pro2.prototype,而 pro2.prototype 的proto就是 Object,當須要訪問 proIns 的某個屬性或者方法的時候,瀏覽器就會沿着原型鏈一直向上進行查找是否有該屬性/方法.
執行路徑:
var Pro = function(){} Pro.prototype.inner="i am pro" var proIns = new Pro() console.log(proIns.__proto__) console.log(proIns.__proto__.__proto__) console.log(proIns.__proto__.__proto__.__proto__)
__proto__
也就是中查找這個屬性**(Pro.prototype)**__proto__
沒有這個屬性, 瀏覽器就會去查找 proIns 的 __proto__
的 __proto__
的屬性**(Object.prototype)**proIns.__proto__.__proto__.__proto__
爲空,瀏覽器判斷原型鏈上不存在該屬性,該屬性獲取 undefined定義一個構造器函數 Person
function Person(name, age, work) { this.name = name; this.age = age; this.work = work; this.study = function() { console.log(this.name + "學習了" + this.work); }; } Person.prototype.eat = function() { console.log(this.name + "依舊須要吃飯"); }; var programmer = new Person("稱序員", 42, "coding"); programmer.study(); programmer.eat(); console.log(programmer.toString())
此時輸出 programer 在控制檯上
Person {name: "稱序員", age: 42, work: "coding", study: ƒ} name: "稱序員" age: 42 work: "coding" study: ƒ () __proto__: eat: ƒ () constructor: ƒ Person(name, age, work) __proto__: constructor: ƒ Object() __defineGetter__: ƒ __defineGetter__() __defineSetter__: ƒ __defineSetter__() hasOwnProperty: ƒ hasOwnProperty() __lookupGetter__: ƒ __lookupGetter__() __lookupSetter__: ƒ __lookupSetter__() isPrototypeOf: ƒ isPrototypeOf() propertyIsEnumerable: ƒ propertyIsEnumerable() toString: ƒ toString() valueOf: ƒ valueOf() toLocaleString: ƒ toLocaleString() get __proto__: ƒ __proto__() set __proto__: ƒ __proto__()
該案例總共調用了三個方法.過程以下:
原型鏈中的方法和屬性沒有被複制到其餘對象——它們被訪問須要經過前面所說的「原型鏈」的方式.上面的執行也是先查找 programmer,以後查找
programmer.__proto__
,最後查找programmer.__proto__.__proto__
沒有官方的方法用於直接訪問一個對象的原型對象=>原型鏈中的「鏈接」被定義在一個內部屬性中,在 JavaScript 語言標準中用 [[prototype]] 表示(參見 ECMAScript).然而,大多數現代瀏覽器仍是提供了一個名爲 __proto__
(先後各有 2 個下劃線)的屬性,其包含了對象的原型.
programmer 繼承的屬性和方法是定義在 prototype 屬性之上的(你能夠稱之爲子命名空間 (sub namespace) )——那些以 Object.prototype. 開頭的屬性,而非僅僅以 Object. 開頭的屬性.prototype 屬性的值是一個對象,咱們但願被原型鏈下游的對象繼承的屬性和方法,都被儲存在其中.
因而 Object.prototype.watch()、Object.prototype.valueOf() 等等成員,適用於任何繼承自 Object() 的對象類型,包括使用構造器建立的新的對象實例.
Object.is()、Object.keys(),以及其餘不在 prototype 對象內的成員,不會被「對象實例」或「繼承自 Object() 的對象類型」所繼承.這些方法/屬性僅能被 Object() 構造器自身使用.
例如
const str = 'today is sunshine'; console.log(str.indexOf(1));
str 就至關於經過 new String()擁有了一些有用的方法
咱們常用的var obj = {}
,經過 Object.create 表示:
var obj = {}; // 以字面量方式建立的空對象就至關於: var obj = Object.create(Object.prototype);
定義在 prototype 上的方法,必須在實例調用以前進行聲明.
用構造函數建立每個實例對象,有些屬性和方法都是如出一轍的內容,每一次生成一個實例,都必須爲重複的內容.這樣會消耗更多內存,也缺少效率.使用 prototype 讓共用屬性和方法在內存中只生成一次,而後全部實例都指向那個內存地址.
Javascript 規定,每個構造函數都有一個 prototype 屬性,指向原型對象.這個對象的全部屬性和方法,都會被構造函數的實例繼承
Function 構造函數建立一個新的 Function 對象.直接調用此構造函數可用動態建立函數,但會遭遇來自 eval 的安全問題和相對較小的性能問題.然而,與 eval 不一樣的是,Function 構造函數只在全局做用域中運行.每一個 JavaScript 函數實際上都是一個 Function 對象 => (function(){}).constructor === Function
...... var singer = Object.create(programmer) ...... console.log(programmer) console.log(singer.__proto__) console.log(singer.__proto__ === programmer)
singer.__proto__ === programmer
獲得的結果爲 true,實際Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的proto.
每一個實例對象都從原型中繼承了一個 constructor 屬性,該屬性指向了用於構造此實例對象的構造函數.
console.log(programmer.constructor) console.log(singer.constructor)
測試都將返回 Person() 構造器,由於該構造器包含這些實例的原始定義.
構造器是一個函數,故能夠經過圓括號調用;只需在前面添加 new 關鍵字,便能將此函數做爲構造器使用.
var loser = new programmer.constructor("落魄者",108,"快餓死了") loser.eat() // 落魄者依舊須要吃飯
原型鏈的經典圖:
每一個實例對象( object )都有一個私有屬性(稱之爲 __proto__
)指向它的構造函數的原型對象(prototype ).該原型對象也有一個本身的原型對象( __proto__
) ,層層向上直到一個對象的原型對象爲 null.根據定義,null 沒有原型,並做爲這個原型鏈中的最後一個環節.
幾乎全部 JavaScript 中的對象都是位於原型鏈頂端的 Object 的實例.
JavaScript 對象有一個指向一個原型對象的鏈.當試圖訪問一個對象的屬性時,它不只僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾.
遵循 ECMAScript 標準,someObject.[[Prototype]] 符號是用於指向 someObject 的原型.從 ECMAScript 6 開始,[[Prototype]] 能夠經過 Object.getPrototypeOf() 和 Object.setPrototypeOf() 訪問器來訪問.這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性 __proto__
.
它不該該與構造函數 func 的 prototype 屬性相混淆.被構造函數建立的實例對象的 [[prototype]] 指向 func 的 prototype 屬性.Object.prototype 屬性表示 Object 的原型對象.
function Person(name, age, work) { this.name = name; this.age = age; this.work = work; this.study = function() { console.log(this.name + "學習了" + this.work); }; } Person.prototype.name = "Person"; Person.prototype.type= "地球人"; Person.prototype.eat = function() { console.log(this.name + "依舊須要吃飯"); }; var programmer = new Person("稱序員", 42, "coding"); programmer.study(); programmer.eat(); console.log(programmer) console.log(programmer.__proto__) console.log(programmer.constructor.prototype)
根據經典圖能夠追溯 programmer 的原型
programmer.__proto__
指向 Person.prototypeprogrammer.__proto__.__proto__
指向 Object.prototypeprogrammer.__proto__.__proto__.__proto__
指向 null在實例上有一個屬性,在原型上也有屬性,優先執行近的,javascript 中的兩大鏈式 => 原型鏈和做用域鏈都是'就近原則'
function Person(name, age, work) { this.name = name; this.age = age; this.work = work; this.study = function() { console.log(this.name + "學習了" + this.work); }; } var programmer = new Person("稱序員", 42, "coding"); programmer.eat = function(){ console.log("幹掉了Person的eat方法") } programmer.type="外星人" Person.prototype.name = "Person"; Person.prototype.type= "地球人"; Person.prototype.eat = function() { console.log(this.name + "依舊須要吃飯"); }; programmer.eat() // 幹掉了Person的eat方法 console.log(programmer.type) // 外星人
上面特別更改了賦值的順序,依舊是執行實例上的方法和屬性,這種狀況被稱爲"屬性遮蔽 (property shadowing)",java 語言中這就是方法重寫.
var o = {a: 1}; // o 這個對象繼承了 Object.prototype 上面的全部屬性 // o 自身沒有名爲 hasOwnProperty 的屬性 // hasOwnProperty 是 Object.prototype 的屬性 // 所以 o 繼承了 Object.prototype 的 hasOwnProperty // Object.prototype 的原型爲 null // 原型鏈以下: // o ---> Object.prototype ---> null var a = ["yo", "whadup", "?"]; // 數組都繼承於 Array.prototype // (Array.prototype 中包含 indexOf, forEach 等方法) // 原型鏈以下: // a ---> Array.prototype ---> Object.prototype ---> null function f(){ return 2; } // 函數都繼承於 Function.prototype // (Function.prototype 中包含 call, bind等方法) // 原型鏈以下: // f ---> Function.prototype ---> Object.prototype ---> null
在 JavaScript 中,構造器其實就是一個普通的函數.當使用 new 操做符 來做用這個函數時,它就能夠被稱爲構造方法(構造函數).
function Graph() { this.vertices = []; this.edges = []; } Graph.prototype = { addVertex: function(v){ this.vertices.push(v); } }; var g = new Graph(); // g 是生成的對象,他的自身屬性有 'vertices' 和 'edges'. // 在 g 被實例化時,g.[[Prototype]] 指向了 Graph.prototype.
ECMAScript 5 中引入了一個新方法:Object.create().能夠調用這個方法來建立一個新對象.新對象的原型就是調用 create 方法時傳入的第一個參數:
var a = {a: 1}; // a ---> Object.prototype ---> null var b = Object.create(a); // b ---> a ---> Object.prototype ---> null console.log(b.a); // 1 (繼承而來) var c = Object.create(b); // c ---> b ---> a ---> Object.prototype ---> null var d = Object.create(null); // d ---> null console.log(d.hasOwnProperty); // undefined, 由於d沒有繼承Object.prototype
ECMAScript6 引入了一套新的關鍵字用來實現 class.使用基於類語言的開發人員會對這些結構感到熟悉,但它們是不一樣的.JavaScript 仍然基於原型.這些新的關鍵字包括 class, constructor,static,extends 和 super.
"use strict"; class Polygon { constructor(height, width) { this.height = height; this.width = width; } } class Square extends Polygon { constructor(sideLength) { super(sideLength, sideLength); } get area() { return this.height * this.width; } set sideLength(newLength) { this.height = newLength; this.width = newLength; } } var square = new Square(2);
在原型鏈上查找屬性比較耗時,對性能有反作用,這在性能要求苛刻的狀況下很重要.另外,試圖訪問不存在的屬性時會遍歷整個原型鏈.
遍歷對象的屬性時,原型鏈上的每一個可枚舉屬性都會被枚舉出來.要檢查對象是否具備本身定義的屬性,而不是其原型鏈上的某個屬性,則必須使用全部對象從 Object.prototype 繼承的 hasOwnProperty 方法.
hasOwnProperty 和 Object.keys() 是 JavaScript 中處理屬性而且不會遍歷原型鏈的方法.
檢查屬性是否爲 undefined 是不可以檢查其是否存在的.該屬性可能已存在,但其值剛好被設置成了 undefined.
prototype 是用於類的,而 Object.getPrototypeOf() 是用於實例的(instances),二者功能一致.
console.log(typeof x); // undefined console.log(typeof 10); // number console.log(typeof 'abc'); // string console.log(typeof true); // boolean console.log(typeof function () {}); //function console.log(typeof [1, 'a', true]); //object console.log(typeof { a: 10, b: 20 }); //object console.log(typeof null); //object console.log(typeof new Number(10)); //object
判斷值類型的用 typeof,判斷引用類型的用 instanceof
var obj = { a:10, b:function (){}, c:function (){} }
對象裏面一切都是屬性,方法也是屬性,以鍵值對的形式表現出來
var func = function () { } func.a = 10; func.b = function () { console.log('hello world'); } func.c = { name:'123', year:1988 }
function Func(){ this.name = 'lili'; this.year = 1988; } var fn1 = new Func();
var obj = {a:20,b:30}; var arr = [1,2,3]; <!--等同於--> var obj = new Object(); obj.a = 20; obj.b = 30; var arr = new Array(); arr[0] = 1; arr[1] = 2; arr[2] = 3;
對象是函數建立的,函數是一種對象
var func = function (){}; console.log(func instanceof Object); // true
//var obj = { a: 10, b: 20 }; //var arr = [5, 'x', true]; <!--以上代碼的本質--> var obj = new Object(); obj.a = 10; obj.b = 20; var arr = new Array(); arr[0] = 5; arr[1] = 'x'; arr[2] = true;
console.log(a.prototype.isPrototypeOf(b)); console.log(b.prototype.isPrototypeOf(b));
for(var key in obj) { f(obj.propertyIsEnumerable(key) { <!--do somethings--> }; };
- 判斷給定的屬性是否能夠用 for...in 語句進行枚舉同時也是對象的自有屬性.
- for ... in 枚舉是包含原型鏈上的屬性的,propertyIsEnumerable 做用於原型方法上時,始終是返回 false 的
- for...in 能夠枚舉對象自己的屬性和原型上的屬性,而 propertyIsEnumerable 只能判斷自己的屬性是否能夠枚舉
- 預約義的屬性不是可列舉的,而用戶定義的屬性老是可列舉的.因此若是你只想遍歷對象自己的屬性
function Phone() { } var mi = new Phone(); console.log(mi instanceof Object); //true console.log(mi instanceof Array); //false console.log(mi instanceof Function); //false console.log(mi instanceof Phone); //true // 實例對象的隱式原型鏈 console.log('實例對象指向Phone.prototype:',mi.__proto__); console.log('Phone.prototype指向Object.prototype:',mi.__proto__.__proto__); console.log('Objec.prototype指向null:',mi.__proto__.__proto__.__proto__); // 構造函數的隱式原型鏈 console.log('Phone.__proto__指向Function.prototype',Phone.__proto__); console.log('Function.prototype指向Object.prototype',Phone.__proto__.__proto__); console.log('Object.prototype指向null',Phone.__proto__.__proto__.__proto__);
var obj = new Object(); // 通常建立對象的原型鏈 console.log('建立對象的__proto__',obj.__proto__); console.log('Object.prototype指向null',obj.__proto__.__proto__); function Object(){ } console.log('指向Function.prototype',Object.__proto__); console.log('Object.prototype',Object.__proto__.__proto__); function Function(){ } console.log(Function.__proto__); console.log('Object.prototype',Function.__proto__.__proto__);
上面例子能夠知道
graph BT Object.prototype-->|__proto__| null Function.prototype-->|__proto__| Object.prototype Phone.prototype-->|__proto__| Object.prototype mi-->|__proto__| Phone.prototype Phone-->|__proto__| Function.prototype obj-->|__proto__| Object.prototype Function-->|__proto__| Function.prototype Object-->|__proto__| Function.prototype arr-->|__proto__| Array.prototype Array.prototype-->|__proto__| Object.prototype
function Ba(str) { this.name = str ? str : 'baobo'; this.sayHello = function() { alert('hello'); } } Ba.prototype = { alertA:function (){ alert(this.name+'-A'); }, alertB: function() { alert(this.name+''); }, } var instance_b = new Ba('haha'); // constructor console.log('原型的構造函數', Ba.prototype.constructor); //function Object(){} console.log('實例的構造函數', instance_b.constructor); //function Object(){} // 新定義的原型對象,並不具備constructor屬性 console.log(Ba.prototype.hasOwnProperty('constructor')); //false
上面的方法至關於重寫 Ba.prototype 對象,新定義的原型對象不包含 constructor,所以構造函數指向的 function Object(){},須要顯式的給原型添加構造函數
雖然實例對象的 constructor 和構造函數原型的 constructor 都指向構造函數,可是實例對象並不具備 constructor 這個屬性,是繼承至 Ba.prototype
console.log(ba.hasOwnProperty('constructor')); //false console.log(Ba.prototype.hasOwnProperty('constructor')); //true
graph LR instance_b.constructor-->|實例的構造函數| Ba Ba.prototype.constructor-->|原型對象的構造函數| Ba
Ba.prototype = { constructor: Ba, alertA: function() { alert(this.name + '-A'); }, alertB: function() { alert(this.name + ''); }, } <!--這樣就可正確指向構造函數Ba了-->
function Ba(str) { this.name = str ? str : 'baobo'; this.sayHello = function() { alert('hello'); } } Ba.prototype.alertA = function() { alert(this.name + '-A'); } Ba.prototype.alertB = function() { alert(this.name + 'B'); } var instance_b = new Ba('haha'); // constructor console.log('原型的構造函數', Ba.prototype.constructor); //f Ba(){} console.log('實例的構造函數', instance_b.constructor); //f Ba(){}
graph TB 聲明構造函數Ba-->構造函數有prototype對象 構造函數有prototype對象-->prototype對象自動有constructor屬性 prototype對象自動有constructor屬性-->建立實例對象instance_b 建立實例對象instance_b-->繼承prototype,有instance_b.constructor 繼承prototype,有instance_b.constructor-->instance_b.constructor指向Ba instance_b.constructor指向Ba-->對象有__proto__ 對象有__proto__-->instance_b指向Ba.prototype
function a() { this.name = 'alisy'; } a.prototype.alertA = function () { alert(this.name); } function b() { this.name = 'baobo'; } b.prototype.alertB = function () { alert(this.name); } function c() { this.name = 'cmen' } c.prototype = a.prototype; //b得prototype對象指向一個c的實例,那麼全部的b的實例就能繼承c b.prototype = new c(); b.prototype.constructor = b; var newb = new b(); var newc = new c(); newb.alertA(); //執行baobo newc.alertA(); //執行cmen //instanceof console.log(b instanceof a); console.log(b instanceof b);
Animal.apply(this, arguments); Animal.call(this, arguments);
Cat.prototype = new Animal(); Cat.prototype.constructor = Cat;
實現繼承除了用 call 和 apply 還可使用原型鏈實現
function add(a+b){ alert(a+b); } function sub(a,b){ alert(a-b); } add.call(sub,3,1); //我的理解call和apply的做用就是切換函數的對象上下文 解:用括號的第一個參數來代替this的指向,將add的執行的上下文由window切換爲sub,至關於this指向由window換成sub,add.call(sub,3,1) == add(3,1),結果爲alert(4); 注意 : js中的函數是對象,函數名是對Function對象的引用
function Animal(){ this.name = "animal"; this.showName = function(){ alert(this.name); } } function Cat(){ this.name = "cat"; } var animal = new Animal(); var cat = new Cat(); //經過call()和apply(),將本來屬於Animal對象的方法showName交給Cat對象使用了,也就是將this指向Animal動態更改成Cat //輸出的結果是cat animal.showName.call(cat,"",""); //animal.showName.apply(cat,[]);
function Animal(name) { this.name = name; this.showName = function(name, a, b) { console.log('this是:' + this.name + '\na是:' + a + '\nb是:' + b); } } function Cat(name) { Animal.call(this, name); this.showLog = function() { console.log('hello'); } } Cat.prototype.showAge = function() { console.log('world'); } var cat = new Cat('hello world'); cat.showName('abc', 12, 5); //能夠直接調用showName()方法 注意:Animal.call(this);是使用Animal對象代替this對象, this指向Animal,Cat就有了Animal對象中的方法和屬性,Cat對 象就能夠直接調用Animal對象的方法和屬性 call第二個參數開始會映射到Animal相應的參數位置
function Animal() { this.showSub = function(a, b) { console.log(a - b); } } function Cat() { this.showAdd = function(a, b) { console.log(a + b); } } function Dog() { Animal.call(this); Cat.call(this); } var a = new Dog(); a.showSub(5,3);//2 a.showAdd(5,3);//8 使用兩個或者更多的call實現多重繼承 call和apply這兩個方法差很少,區別在於call的第二個參數是任意類型,而apply的第二個參數必須是數組,也能夠是arguments
Function.prototype.call2 = function(context) { console.log(arguments); // 若是傳入的爲null,則指向window context = context || window; // 函數調用的時候,this指向調用的函數 context.fn = this; var args = []; for (var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } // 解析字符串,執行其中的js代碼 // 獲取返回值 var result = eval('context.fn(' + args + ')'); // 執行完後將添加的屬性刪除 delete context.fn; return result; }; var heo = 'hello world'; var foo = { name: 'lili' } function func(age, sex) { console.log(age); console.log(sex); console.log(this.name); return { name: this.name, age: age, sex: sex, } } func.call2(null); console.log(func.call2(foo, 23, '男'))
Function.prototype.newApply = function(context, arr) { var result, i, len; context = context || window; context.fn = this; if (!arr) { result = context.fn; } else { var args = []; for (i = 0, len = arr.length; i < len; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')'); } delete context.fn; return result; } var obj = { name: 'alice' } function func(age, sex) { console.log(age); console.log(sex); console.log(this.name); return { name: this.name } } console.log(func.newApply(obj, [23, '女']));
寫程序的一個錯誤,this 丟失原先的對象,將對象的方法進行賦值以後再執行,因而變成 window.new_showA(),this 指向全局對象 window,
var a = 1; var obj = { a: 11, showA: function () { console.log(this.a); } }; obj.showA(); var new_showA = obj.showA; new_showA(); //1
因此此時須要修改 this 指向
var new_showA = obj.showA.bind(this); new_showA(); //1
實現 bind 方法
Function.prototype.newBind = function (context) { //this是該函數的調用者 var self = this; //arguments.slice(1),從1開始截取數組 var args = Array.prototype.slice.call(arguments, 1); return function () { // 第二個arguments是返回的函數的參數 var bindargs = Array.prototype.slice.call(arguments); return self.apply(context, args.concat(bindargs)); } }; var a = 9; var obj = { a: 99, showA: function (name, age) { console.log(this.a); console.log(name); console.log(age); } }; obj.showA('alice', 12); var new_showA = obj.showA.newBind(obj, 'lilith'); new_showA(23); //至關於執行obj.showA.apply(obj);
function Animal(name, age) { this.name = name; this.age = age; this.voice = 'miao'; } Animal.prototype.type = 'mao'; Animal.prototype.saying = function () { console.log(this.voice); }; var cat = _new(Animal, 'mimi', 10); console.log('instance =>', cat); console.log({ name: cat.name, age: cat.age, type: cat.type }); cat.saying(); function _new() { // 將僞數組arguments從頭部刪除一個,並將其其返回,此處是爲了獲取傳入的構造函數 var Constructor = Array.prototype.shift.call(arguments); // obj.__proto__指向建立obj的函數的原型,也就是function Object(){}的原型,obj是一個實例對象,沒有prototype屬性 var obj = Object.create(Constructor.prototype); var result = Constructor.apply(obj, arguments); // 若是構造函數有返回值,作一下處理,若是返回的是對象,就返回對象,不然該是什麼就是什麼 return typeof result === 'object' ? result : obj; }
<!--Object.create相似於--> function Func(){}; Func.prototype = Constructor.prototype; return new Func();