this可以動態的取值,實現了隱式傳遞一個對象的引用
var obj = { a : { b : 2, c : function () { console.log(this.b); } } }
上面的例子中想在c中取b的值用this就能簡單搞定,必定程度上實現了動態取值;obj.a.c(),這個this就是綁定了obj.a,這個就是上下文對象也就是調用棧javascript
this始終是一個對象,函數在被調用時發生綁定,具體綁定了什麼看調用的位置,有必定的綁定規則java
默認綁定
當函數不帶任何修飾被調用時,運用默認綁定,this就是window全局對象;但要注意的是在嚴格模式下,則不行,this會綁定成undefined數組
function f() { //"use strict" console.log(this.a); } var a = 123; f(); //123
function f() { "use strict" console.log(this.a); } var a = 123; f(); //報錯,undefined不可以添加屬性
隱式綁定
當上下文對象(調用棧)調用函數時,會隱式綁定this,哪一個對象調用就綁定誰app
function f() { console.log(this.a); } var obj = { a : 123, b : f, } obj.b();
上面例子實際上就是this = obj,而不是this = obj.a
這是最簡單的,可是這種綁定經常會發生隱式丟失,而採用默認綁定函數
function f() { console.log(this.a); } var obj = { a : 123, b : f, } var a = 345 var f1 = obj.b; //採起函數別名而發生了隱式丟失 f1(); //345,這等同於直接執行了f函數
個人理解是函數取別名是最好賦值上下文對象,也就是調用棧(var f1 = b)
例以下面例子也是隱式丟失this
function f() { console.log(this.a); } var obj = { a : 123, b : f, } function fn(f) { //等同於setTimeout(obj.b, 100) f(); } fn(obj.b);
要注意這種別名:prototype
function f() { console.log(this.a); } var obj = {a : 1}; obj.fn = f; obj.fn()
實際上就是:code
function f() { console.log(this.a); } var obj = { a : 1, fn : f, }; obj.fn()
call
能夠填入多個參數對象
func.call(thisValue, arg1, arg2, arg3,...)
第一個參數是this要綁定的對象,若參數爲空,undefined,null,則默認綁定window;若參數爲原始值,則this綁定對應的包裝類對象
後面參數則是傳入func的參數繼承
function f(n, m) { console.log(n, m, this.a); } var obj = {a : 1}; f.call(obj, 3, 4); //3 4 1
function f() { console.log(this); } f.call("123"); //String {"123"}
apply
apply與call相似只是參數不一樣,就是將傳入原函數的參數變成數組形式
func.call(thisValue, [arg1, arg2, arg3,...])
apply與call能夠這樣理解:就是thisValue借用func函數功能來實現效果
相似於thisValue.fn(arg1, arg2, arg3...)
利用這一特色能夠實現有趣的效果
找數組中最大的數
var arr = [11, 2, 3, 4, 5]; var nmax = Math.max.apply(null, arr); //11
null的做用是由於用不到this,用null代替,僅用後面的參數,有柯里化的感受
建議咱們通常定義一個比null還空的空對象:var ø = Object.create(null)
將數組中的空元素變成undefined,這個樣就可遍歷了,其屬性描述的enumerable:true
var arr = [11, , 3, 4, 5]; var arr1 = Array.apply(null, arr); //[11, undefined, 3, 4, 5]
轉換類數組對象,利用Array的實例方法slice,前提是必須有length屬性
var arr = Array.prototype.slice.apply({0 :1, 1 : 2, length : 3}); //[1, 2, empty] var arr = Array.prototype.slice.apply({0 :1}); //[] var arr = Array.prototype.slice.apply({length : 1}); //[empty]
bind
bind函數參數形式和call相似
func.bind(thisValue, arg1, arg2, arg3,...)
我對他的理解是對原函數進行改造,生成本身的新函數,主要改造就是this綁定成thisValue,而且能夠固定部分參數,固然後面arg選填
相似於thisValue.fn
咱們能夠自定義一個簡單bind函數:
function myBind(fn, obj) { return function () { fn.apply(obj, arguments); } } function f(n) { console.log(n + this.a) } var obj = {a : 1}; var bind = myBind(f, obj); bind(4); //5
原生態bind用法也相似
function f(n,m) { console.log(this.a + n + m); } var obj = {a : 1}; var bar = f.bind(obj, 2); bar(3); //6
注意點bind每次返回一個新函數,在監聽事件時要注意,否則romove不掉
element.addEventListener('click', o.m.bind(o)); element.removeEventListener('click', o.m.bind(o));
由於o.m.bind(o)返回的時新函數,因此remove的也不是開啓時的函數了
正確作法:添加一個值,記錄開啓時的函數
var listener = o.m.bind(o) element.addEventListener('click', listener); element.removeEventListener('click', listener);
有趣的地方:
利用call與bind實現原始方法
var slice = Function.prototype.call.bind(Array.prototype.slice); slice([1, 2, 3], 1); //[2, 3]
能夠拆分bind看
Array.prototype.slice.Function.prototype.call([1, 2, 3], 1)
其中Function.prototype.call 就是call方法
Array.prototype.slice.call([1, 2, 3], 1)
拆分call
[1, 2, 3].Array.prototype.slice(1)
而Array.prototype.slice就是slice
因此
[1, 2, 3].slice(1)
我的理解能夠當作,Array.prototype.slice實現了slice功能,Function.prototype.call實現了arguments中this的綁定以及參數的帶入。因此函數最總調用時顯示:slice([1, 2, 3], 1);
同理
var push = Function.prototype.call.bind(Array.prototype.push); var pop = Function.prototype.call.bind(Array.prototype.pop);
同時bind也能被改寫
function f() { console.log(this.a); } var obj = { a : 123 }; var bind = Function.prototype.call.bind(Function.prototype.bind); bind(f, obj)() // 123
new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定
隱式綁定與默認綁定比較
function f() { console.log(this.a); } var obj = { a : 123, f : f, } var a = 456; obj.f(); // 123
obj.f()覆蓋了f(),所以隱式大於默認
隱式綁定與顯示綁定比較
function f() { console.log(this.a); } var obj = { a : 123, f : f, } var obj1 = { a : 1123 } obj.f.call(obj1); //1123
由輸出結果能夠看出:call綁定的obj1覆蓋了obj,因此顯示大於隱式
顯示綁定與new綁定
function f(n) { this.a = n; console.log(this); } var obj = { b : 2}; var bar = f.bind(obj); console.log(bar(2)); console.log(new bar(2)); //{b:2,a:2} //undfined //{a: 2} //{a: 2}
由輸出結果能夠看出:new bar(2),{a:2}說明是新生成的空對象,添加了a的屬性,其次輸出兩個說明函數返回了一個this對象也就是{a:2},而不是undefined
因此new大於顯示
注意多層this*
var obj = { f: function () { console.log(this); var f1 = function () { //console.log(this); }(); } } obj.f(); //{f:func} //winodw
由於傳統的this沒有繼承機制,因此這個匿名函數的this沒有任何修飾,採起默認綁定
有兩種方法解決
var obj = { f: function () { var self= this console.log(self); var f1 = function () { //console.log(self); }(); } } obj.f(); //{f:func} //{f:func}
var obj = { f: function () { console.log(this); return () => { console.log(this); }; } } obj.f()(); //{f:func} //{f:func}
注意處理數組時使用this
var obj = { a : 123, arr : [1, 2, 3], f : function () { this.arr.forEach(function (elem) { console.log(this.a, elem); }); } } var a = "this是window"; obj.f(); //this是window 1 //this是window 2 //this是window 3
forEach中的this指向的是window
解決辦法:
var obj = { a : 123, arr : [1, 2, 3], f : function () { this.arr.forEach(function (elem) { console.log(this.a, elem); },this); } } var a = "this是window"; obj.f(); //123 1 //123 2 //123 3
還有一種就是賦值給一個變量self暫時保存,給遍歷時用,如同上面的self
注意回調函數中的this
var obj = new Object(); obj.f = function () { console.log(this === obj); } $('#button').on('click', obj.f); //false
this指向的是DOM對象而不是obj,這個有點難以察覺,須要注意
解決辦法:硬綁定obj對象
var obj = new Object(); obj.f = function () { console.log(this === obj); } function fn(){ obj.f.apply(obj); } $('#button').on('click', fn); //true //$('#button').on('click', o.f.bind(obj)); //true
上面的硬綁定至關於固定了this的對象,不會變了。
咱們能夠作個軟綁定
if(!Function.prototype.sorfBind) { Function.prototype.sorfBind = function (obj) { //這個this:執行sorfBind時的調用位置綁定的this var fn = this; var arg1 = [].slice.call(arguments, 1); var bound = function() { //這個this:執行bound時綁定的this return fn.apply( //arg1.concat.call(arg1, arguments 做用是整合兩次執行傳入的參數 //(!this || this === (window || global)) 猜想是爲了在嚴格模式下也試用 (!this || this === (window || global)) ? obj : this, arg1.concat.call(arg1, arguments) //arguments是bound函數執行時參數的參數, ); }; bound.prototype = Object.create(fn.prototype); return bound; } } function f() { console.log("name: " + this.name); } var obj = {name : "obj"}, obj1 = {name : "obj1"}, obj2 = {name : "obj2"}; var fObj = f.sorfBind(obj); fObj(); //name : obj obj1.foo = f.sorfBind(obj); obj1.foo(); //name : obj1 fObj.call(obj2); //name : obj2 setTimeout(obj1.foo, 10); //name : obj
初次綁定this後,箭頭函數內部的this就固定了
function f() { return (b) => { console.log(this.a); } } var obj = {a : 1}; var obj1 = {a : 2}; var fn = f.call(obj); fn(); //1 fn.call(obj1); //1
與第一點相似,初次綁定後內部this固定了也就有了繼承
function f() { setTimeout(() => { console.log(this.a); },10); } var obj = {a : 1}; f.call(obj); //1
arguments.callee 也有改變this指向
function f() { console.log(this); if(this ==window) { arguments.callee(); } } f(); //window對象 //arguments對象