讲解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)
复制代码
相关文章
谢谢以上做者大大~
第一篇文章~完结撒花~*★,°*:.☆( ̄▽ ̄)/$:*.°★*