講解call、apply及bind對this修改指針指向方法的使用、區別及實現。前端
本文主要內容是對call、apply及bind的功能及使用方法進行介紹,以後會經過js原生實現這三種方法,讓咱們更深刻地瞭解其中的做用與原理。但因爲多數介紹的實現方法的過程當中對this、arguments、rest、解構賦值及擴展運算符有必定涉及,本文會在先導中先作一個簡單介紹,可能會對以後正文對bind等方法內容的講解有必定理解上的幫助。面試
不少對call、apply及bind的講解中都會對this有較爲詳細的介紹,本文在前人的基礎上簡略作個介紹。須要重點突出重複 的就關於this的一個問題:this永遠指向最後被調用的地方。數組
如下是一個簡單的例子:app
var name = 'windowPart'
const testObj = {
name: 'objPart',
func: function(){console.log(this.name)}
}
// testObj調用func
testObj.func() // objPart this -> obj
// outer最終調用func
const outer = testObj.func
outer() // windowPart this -> window
複製代碼
簡單就能看出在對象被調用後this會改變其指向,指到最後被調用的地方。所以在開發過程當中對this指向多加留意是很必要的,一不留心可能就會出現bug,此時有對call、apply及bind方法能熟練掌握的話就能派上很大的用場。函數
對於...擴展運算符,該運算符主要用於函數調用,一般可用於對數組的解構,ES8將其引入對象,對於整形、布爾值、字符串等等,擴展運算符也可將其擴展賦值,其原理是將其餘類型的變量轉化爲對象後,將其展開,相似於Object.assign()方法。post
如下是簡單用法:優化
let arr = [1, 2, 3]
console.log(...arr) // 1 2 3
複製代碼
而對於解構賦值,其能夠在數組、對象、字符串甚至數值和布爾值中運用。 ui
用法以下:this
let arr = [1, 2, 3]
let [a, b, c] = arr
console.log('a:', a) // a: 1
console.log('b:', b) // b: 2
console.log('c:', c) // c: 3</code></pre>
複製代碼
基於這樣「匹配賦值」的模式,咱們能夠完成更多複雜的解構賦值,例如嵌套解構等等。spa
但對於解構在對象上的運用,須要注意的是,其是取出參數對象的全部可遍歷屬性,而且在完成相似深拷貝時,申明的變量名必須爲擴展對象中存在的key值,此處至關於調用了一次get(keyname)方法,其簡單運用以下:
let obj = {
me: '我',
you: '你'
}
let {me, err} = {...obj}
console.log(me) // 我
console.log(err) // undefined
複製代碼
arguments是一種類數組對象,其只可以在函數內部調用,主要包含着該函數的參數,其中也有所指代的Argument對象的一些其餘屬性,例如簡單的length屬性,此處不作詳解。
所謂類數組對象,其在基本使用上與數組並沒有異同,但對於自身屬性,arguments不能使用數組中push、pop等方法,基本使用以下:
function argsFunc () {
console.log('arguments:', arguments)
}
argsFunc(1, 2, 3) // arguments: [Arguments] { '0': 1, '1': 2, '2': 3 }
複製代碼
值得注意的是類數組對象經過擴展運算符能夠很方便轉化爲數組,經過如下例子能夠理解:
function argsFunc (...arguments) {
console.log('展開後的arguments:', arguments)
}
argsFunc(1, 2, 3) // 展開後的arguments: [ 1, 2, 3 ]
複製代碼
ES6引入的rest參數,至關於數組擴展運算符的逆運算,在函數參數中,運用rest能夠將arguments對象進行解構取值,在定義函數時若其中存在能夠歸爲一類的參數,此時咱們加以運用rest會顯得很亮眼,對函數的書寫有很大的精簡做用,相較於類數組對象arguments,rest做爲數組去包含參數會有更加優秀的使用效率。
如下是一個簡單的使用:
function restFunc (str, ...rest) {
console.log('str:', str)
console.log('rest:', rest)
}
restFunc('Me', 1, 2) // str: 'Me'
// rest: [ 1, 2 ]
複製代碼
當被定義的函數在外部調用時,經過call、apply或bind方法將指向window的this指定回該函數中this應該指向的對象,這是保證this指向的狀況之一。
簡單使用以下:
var name = 'windowPart'
const testObj = {
name: 'objPart',
func: function (...rest) {
console.log('this.name:', this.name)
console.log('args:', rest)
}
}
let arr = [1, 2]
// testObj調用func
testObj.func(...arr) // objPart this -> obj
// 修改this指向後outer調用func
const outer = testObj.func
outer.call(testObj, ...arr)
outer.apply(testObj, arr)
outer.bind(testObj, ...arr)()
//以上輸出均爲:
// this.name: objPart
// args: [ 1, 2 ]
複製代碼
在coding時,對於用到this的地方,必定要多加註意this指向丟失問題,不論後期在何處調用必定先將this綁定好,上例爲更好理解是在調用處指回函數內部this本應指回的對象。在開發過程當中,咱們不只能夠在賦值給全局變量後調用時經過call、apply及bind方法將丟失指向了window的this綁定回,也可如如下方法不經過賦值給全局變量後調用並在用到this時就提早綁定好避免this丟失的狀況發生:
// 方法一:經過bind(this)綁定好上一級this
var name = 'windowPart'
const testObj = {
name: 'objPart',
func: function () {
setTimeout(function(){
console.log('綁定後 this.name:', this.name) // this -> testObj
}.bind(this), 1000)
}
}
testObj.func() // 綁定後 this.name: objPart
//方法二:經過_this保留上級this
var name = 'windowPart'
const testObj = {
name: 'objPart',
func: function () {
let _this = this
setTimeout(function(){
console.log('綁定後_this.name:', _this.name) // _this -> testObj
console.log('未綁定 this.name:', this.name) // this -> window
}, 1000)
}
}
testObj.func() // 綁定後_this.name: objPart
// 未綁定 this.name: windowPart
//方法三:經過箭頭函數this指向上一級對象
var name = 'windowPart'
const testObj = {
name: 'objPart',
func: function () {
setTimeout(() => {
console.log('箭頭函數內 this.name:', this.name) // this -> testObj
}, 1000)
}
}
testObj.func() // 箭頭函數內 this.name: objPart
複製代碼
經過上一示例,能夠看出對於call、apply及bind的區別有如下:
以上若是很差理解,經過下一節本身js手寫這三個方法會有很清晰的認識。ヾ(◍°∇°◍)ノ゙加油
const arr = [1, 2]
function testFunc (num1, num2) {
console.log('this.name:', this.name)
console.log('num1:', num1)
console.log('num2:', num2)
}
const testObj = {
name: 'objName'
}
Function.prototype.myCall = function (testObj, ...rest) {
if(arguments.length == 2 && Array.isArray(arguments[1])) {
try{
throw new Error('myCall參數錯誤!')
} catch (e) {
console.log(e.name + ': ' + e.message)
}
return
}
console.log(this) // 此處輸出便於理解輸出一下this內容
// this -> 最後調用myCall方法的[Function: testFunc]
testObj._fn = this
var ret = testObj._fn(...rest)
delete testObj._fn
return ret
}
testFunc.myCall(testObj, ...arr) // this.name: objName
// num1: 1
// num2: 2
複製代碼
if()部分就是稍微寫的細節一點的一個對參數的判斷問題。其中最須要解釋的一點,在testObj中申明一個_fn,將testFunc賦給_fn,而後經過ret調用testObj._fn(...rest)能夠簡便的將testFunc做爲testObj一個內部屬性,從而達到修改testFunc內部this指向testObj的效果。須要注意的是testObj內部利用完的_fn要在最後進行回收處理。
簡化原理以下:
const func = function () {
console.log(this.name)
}
const testObj = {
name: 'testObj',
_fn: func
}
testObj._fn()
複製代碼
對於apply的js原生實現,僅僅與call在參數傳遞上有細微的不一樣,讀懂call的原生實現,能夠嘗試本身完成apply的過程。
const arr = [1, 2]
function testFunc (argsArr) {
console.log('this.name:', this.name)
console.log('argsArr:', argsArr)
}
const testObj = {
name: 'objName'
}
Function.prototype.myApply = function (testObj, rest) {
if(!Array.isArray(rest)) {
try{
throw new Error('myApply參數錯誤!')
} catch (e) {
console.log(e.name + ': ' + e.message)
}
return
}
testObj._fn = this
var ret = testObj._fn(rest)
delete testObj._fn
return ret
}
testFunc.myApply(testObj, arr) // this.name: objName
// argsArr: [ 1, 2 ]
複製代碼
對於bind方法,在使用時就曾說起過一個不一樣:其返回的是一個函數,因此與call及apply相比在使用上會多出一個bind方法返回的函數能夠做爲構造函數的狀況,如下對bind方法的實現我將由淺入深,逐漸完善,更方便理解。
const arr = [1, 2]
const testObj = {
name: 'objName'
}
function testFunc(...argsArr) {
console.log('this.name:', this.name)
console.log('argsArr:', argsArr);
}
Function.prototype.myBind = function (testObj, ...rest) {
if(arguments.length == 2 && Array.isArray(arguments[1])) {
try{
throw new Error('myCall參數錯誤!')
} catch (e) {
console.log(e.name + ': ' + e.message)
}
return function () {}
}
const _this = this
let resFn = function () {
_this.apply(testObj, rest)
}
return resFn
}
testFunc.myBind(testObj, ...arr)() // this.name: objName
// argsArr: [1, 2]
複製代碼
當bind返回函數做爲構造函數使用時 即 new Func() ,此時咱們須要注意兩個地方:
(1). 由於new不只自身優先級大且new對this指向改變優先級大於bind方法的問題,會將內部this的指向實例,此處咱們須要在作一個判斷,對內部調用apply方法需綁定的地方作一個選擇。其實此時bind指定的this值會失效,但傳入值依然有效。
(2). 對於prototype,在這個狀況下,函數被做爲構造函數返回就須要將實例需繼承該原型中的值。
const arr = [1, 2]
const testObj = {
name: 'objName'
}
function testFunc(...argsArr) {
console.log('this.name:', this.name)
console.log('argsArr:', argsArr)
}
Function.prototype.myBind = function (testObj, ...rest) {
if(arguments.length == 2 && Array.isArray(arguments[1])) {
try{
throw new Error('myCall參數錯誤!')
} catch (e) {
console.log(e.name + ': ' + e.message)
}
return function () {}
}
const _this = this
let resFn = function () {
_this.apply(this instanceof resFn ? this : testObj, rest)
}
resFn.prototype = this.prototype
return resFn
}
testFunc.myBind(testObj, ...arr)() // this.name: objName
// argsArr: [1, 2]
new (testFunc.myBind(testObj, ...arr)) // this.name: undefined
// argsArr: [1, 2]
複製代碼
對於實例需繼承該原型中的值,原型鏈上的操做,若如上resFn.prototype = this.prototype
定義,會產生引用賦值共用一個內存地址的狀況,發生如下問題:
Function.prototype.testBind = function () {
let retFunc = function () { }
retFunc.prototype = this.prototype
return retFunc
}
function Test1 () {}
let Test2 = Test1.testBind()
Test2.prototype.a = function () {}
const test = new Test2
console.log(Test2.prototype) // {a: ƒ, constructor: ƒ}
console.log(Test1.prototype) // {a: ƒ, constructor: ƒ}
複製代碼
所以這個時候咱們須要一個空函數中轉一下或者使用Object.create()
,防止對父級原型鏈的污染。
const arr = [1, 2]
const testObj = {
name: 'objName'
}
function testFunc(...argsArr) {
console.log('this.name:', this.name)
console.log('argsArr:', argsArr)
}
Function.prototype.myBind = function (testObj, ...rest) {
if(arguments.length == 2 && Array.isArray(arguments[1])) {
try{
throw new Error('myCall參數錯誤!')
} catch (e) {
console.log(e.name + ': ' + e.message)
}
return function () {}
}
const _this = this
let resFn = function () {
_this.apply(this instanceof resFn ? this : testObj, rest)
}
resFn.prototype = Object.create(this.prototype)
// const TempFunc = function () {}
// TempFunc.prototype = this.prototype
// resFn.prototype = new TempFunc()
return resFn
}
const BindFunc = testFunc.myBind(testObj, ...arr)
BindFunc.prototype.a = function () {}
var test = new BindFunc
console.log(BindFunc.prototype)
console.log(testFunc.prototype)
複製代碼
相關文章
謝謝以上做者大大~
第一篇文章~完結撒花~*★,°*:.☆( ̄▽ ̄)/$:*.°★*