一起手写吧!call、apply、bind!
apply,call,bind都是js给函数内置的一些api,调用他们可以为函数指定this的执行,同时也可以传参。
call
call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。
let obj = { name: "一个" } function allName(firstName, lastName) { console.log(this) console.log(`我的全名是“${firstName}${this.name}${lastName}”`) } // 很明显此时allName函数是没有name属性的 allName('我是', '前端') //我的全名是“我是前端” this指向window allName.call(obj, '我是', '前端') //我的全名是“我是一个前端” this指向obj
apply
apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。
allName.apply(obj, ['我是', '前端'])//我的全名是“我是一个前端” this指向obj
bind
bind 接收多个参数,第一个是bind返回值是一个函数上下文的this,不会立即执行。
let obj = { name: "一个" } function allName(firstName, lastName, flag) { console.log(this) console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右铭是"${flag}"`) } allName.bind(obj) //不会执行 let fn = allName.bind(obj) fn('我是', '前端', '好好学习天天向上') // 也可以这样用,参数可以分开传。bind后的函数参数默认排列在原函数参数后边 fn = allName.bind(obj, "你是") fn('前端', '好好学习天天向上')
实现call
1.call主要都做了些什么。
- 更改this指向
- 函数立刻执行
2.简单实现
Function.prototype.myCall = function(context) { context.fn = this; context.fn(); } const obj = { value: 'hdove' } function fn() { console.log(this.value); } fn.myCall(obj); // hdove
3.出现的问题
- 1.无法传值
- 2.如果fn()有返回值的话,myCall 之后获取不到
Function.prototype.myCall = function (context) { context.fn = this; context.fn(); } const obj = { value: 'hdove' } function fn() { return this.value; } console.log(fn.myCall(obj));
- 3.call其实就是更改this指向,指向一个Object,如果用户传的是基本类型又或者干脆就不传呢?
- 4.myCall执行之后,obj会一直绑着fn()
4.统统解决
Function.prototype.myCall = function(context) { // 1.判断有没有传入要绑定的对象,没有默认是window,如果是基本类型的话通过Object()方法进行转换(解决问题3) var context = Object(context) || window; /** 在指向的对象obj上新建一个fn属性,值为this,也就是fn() 相当于obj变成了 { value: 'hdove', fn: function fn() { console.log(this.value); } } */ context.fn = this; // 2.保存返回值 let result = ''; // 3.取出传递的参数 第一个参数是this, 下面是三种截取除第一个参数之外剩余参数的方法(解决问题1) const args = [...arguments].slice(1); //const args = Array.prototype.slice.call(arguments, 1); //const args = Array.from(arguments).slice(1); // 4.执行这个方法,并传入参数 ...是es6的语法用来展开数组 result = context.fn(...args); //5.删除该属性(解决问题4) delete context.fn; //6.返回 (解决问题2) return result; } const obj = { value: 'hdove' } function fn(name, age) { return { value: this.value, name, age } } fn.myCall(obj, 'LJ', 25); // {value: "hdove", name: "LJ", age: 25}
二、手动实现Apply
实现了call其实也就间接实现了apply,只不过就是传递的参数不同
Function.prototype.myApply = function (context, args) { var context = Object(context) || window; context.fn = this; let result = ''; //4. 判断有没有传入args if (!args) { result = context.fn(); } else { result = context.fn(...args); } delete context.fn; return result; } const obj = { value: 'hdove' } function fn(name, age) { return { value: this.value, name, age } } fn.myApply(obj, ['LJ', 25]); // {value: "hdove", name: "LJ", age: 25}
三、实现Bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用(MDN)
bind和apply的区别在于,bind是返回一个绑定好的函数,apply是直接调用.其实想一想实现也很简单,就是返回一个函数,里面执行了apply上述的操作而已.
不过有一个需要判断的点,因为返回新的函数,要考虑到使用new去调用,并且new的优先级比较高,所以需要判断new的调用,还有一个特点就是bind调用的时候可以传参,调用之后生成的新的函数也可以传参,效果是一样的,所以这一块也要做处理
因为上面已经实现了apply,这里就借用一下,实际上借用就是把代码copy过来
Function.prototype.myBind = function (context, ...args) { const fn = this args = args ? args : [] return function newFn(...newFnArgs) { if (this instanceof newFn) { return new fn(...args, ...newFnArgs) } return fn.apply(context, [...args,...newFnArgs]) } }