上一篇讲this指向时候讲到四大绑定时说到:call…这三个可以函数显示绑定一个对象到一个函数的this上,这篇主要讲一下三者区别以及手写他们。至于你问为啥要手写它呢,别问,问就是在某个面试上遇到过 。:)
提示:以下是本篇文章正文内容,下面案例可供参考
可以参考这篇文章或者看MDN文档
// 'use strict' var obj = { a: 1 } function person() { console.log(this); } var a = 3; person.call()//window person.call(obj) //obj person.call(null) //window strict null person.call(undefined) //window strict undefined person.call('11') //String{"11"} strict '11' person.call(11) //Number{11} strict 11 person.apply(null) //window person.apply(undefined) //window person.apply(11) //Number{11} var fn = person.bind(null) //window fn() var fn2 = person.bind(undefined) //window fn2() var fn3 = person.bind(11) //Number{11} fn3()
调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg。
与我下面测试不符,有知道可告知吾。
function person() { console.log(this); } var obj2 = { fn4: function() { console.log(this);//obj2 setTimeout(() => { var fn6 = person.bind(null); fn6() //window }, 100) } } obj2.fn4()
这个就有点多了,略
有了上面的准备开始写吧,还记得上一篇隐式绑定吗?
/* * 不做函数监测的判断 * 实现apply的类型功能 * 利用隐式绑定 * 非严格模式 * 不判断类数组 */ Function.prototype.myApply = function(context, args) { args = args ? args : []//参数为空 if (context === null || context === undefined) { context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window) } else { context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象 } var fn = this //this就是你要改变的函数 const key = Symbol() //给context新增一个独一无二的属性以免覆盖原有属性 context[key] = fn //相当于context 增加fn方法,让context拥有这个函数 const res = context[key](...args) //通过隐式绑定的方式调用函数 相当于obj.fn() , ...args es6语法 delete context[key] //删除添加的属性 console.log(context[key]); return res //返回函数调用的返回值 } var obj = { a: 2 } var a = 3; var fn = function() { console.log(this); } fn.myApply(obj) //obj fn.myApply(null) //window fn.myApply(11) //Number{11}
唯一区别第一行,注意两个…的区别
Function.prototype.mybind = function(context, ...args) { args = args ? args : [] if (context === null || context === undefined) { context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window) } else { context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象 } var fn = this //就是你要改变的函数 const key = Symbol() //给context新增一个独一无二的属性以免覆盖原有属性 context[key] = fn //相当于context 增加fn方法,让context拥有这个函数 const res = context[key](...args) //通过隐式绑定的方式调用函数 相当于obj.fn() , ...args es6语法把类数组转化为数组 delete context[key] //删除添加的属性 console.log(context[key]); return res //返回函数调用的返回值 }
function testRest(...args) { //rest运算,剩余运算符(the rest operator)用于解构数组和对象 console.log(args); //[1,2,3,4] console.log(...args); //1,2,3,4扩展运算符:拆解数组,将一个数组转为用逗号分隔的参数序列 } testRest(1, 2, 3, 4)
function person() { console.log(this); //person } var a = 3 var obj = { a: 2 } var newPerson = person.bind(obj) var np = new newPerson(); console.log(np); //person new优先级 var newFn = person.bind(obj); newFn() //{a:2} Function.prototype.myBind = function(context, ...args) { const fn = this args = args ? args : [] return function newFn(...secondArgs) { //二次传参问题 if (this instanceof newFn) { //解决new的问题 return new fn(...args, ...secondArgs)//如果是new 就返回一个对象 } return fn.apply(context, [...args, ...secondArgs]) } } var newPerson2 = person.myBind(obj) var np2 = new newPerson2(); console.log(np2); //person new优先级 var newFn2 = person.myBind(obj); newFn2() //{a:2}
function fn() { console.log(this) } var obj2 = { name: '233' } fn.call(obj2) fn.call(fn) fn.call.call(function() { console.log(this, 1) }) fn.call.call.call.call(function() { console.log(this) }, obj2)
会输出什么呢?
你可能觉得很乱
那我换一下
function fn() { console.log(this) } var obj2 = { name: '233' } function fn3() { console.log(this, 'fn3'); } console.log(fn.prototype); fn.call(obj2) //obj2 fn.call(fn) fn.call.call(fn3) //window fn3 fn.call.call.call.call(fn3, obj2) // obj fn3
先说个结论:
原型链不懂得可以看一下这
console.log(fn.prototype);
里面找call然后在找call 一直找下去//这里改为对象会好点 // function fx() { // var fx = function() { // var fx = function() { // console.log('1'); // } // } // } // console.log(fx.fx.fx());
第四点:如我们上面手写一样,fn.call(context,args) 内部是隐式绑定conext.fn(args)
所以
- fn.call.call…call = calll
- fn.call.call(fn3) = call.call(fn3)
- 内部就是 context => fn3 fn=>call
- conext.fn(args) => fn3.call(args) args为空绑定到window
- fn.call.call(fn3,obj) => fn3.call(obj) obj绑定到fn3上
总结:
call数>=2 看最后call参数 call(fn3,obj)