三者的异同
三者的作用:都是 Function 对象自带的方法,作用都是用来改变函数内的 this 指向,传递的第一个参数都是 this 所要指向的对象,而且它们三个都可以传递参数。
三者的区别:
call:传递参数的时候需要一个一个进行传递,而且定义完立即执行,返回值为调用方法的返回值,如果调用的方法没有返回值,则返回 undefinedapply:和call类似,都是立即执行,返回值为调用方法的返回值,如果调用方法没有返回值,则返回 undefined ,唯一不同的是,apply在传递参数的时候需要以数组的形式bind:除了返回值是函数以外,它在传递参数的时候和call一样,都是一个一个传递,它在调用之后返回一个新的函数,不会立即执行
call 和 apply 都是为了改变某个函数运行时的上下文而存在的。换句话说,就是为了改变函数体内部 this 的指向。
对 apply 和 call 而言,二者作用完全一样,只是接收参数的方式不太一样。call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。
内部实现
call
Function.prototype.myCall = function(context = window, ...args) {if (this === Function.prototype) {return undefined; //判断当前this是否为函数,用于防止Function.prototype.myCall()直接调用}context = context || window; //context为可选参数,如果不传的话,默认指向windowconst fn = Symbol(); //利用symbol进行定义,可以保证不会重名context[fn] = this; //将当前函数赋给这个属性,这样之后执行context[fn]的时候,fn里面的this指向就为context了const result = context[fn](...args);delete context[fn]; //调用完后立即删除symbol属性return result;}//调用let person1 = {name:'王',age:18,say(...args){console.log(`名字为${this.name},年龄为${this.age},参数为${args}`);}}let person2 = {name:'李',age:20}person1.say.myCall(person2,1,2,3);//输出 名字为李,年龄为20,参数为1,2,3
apply
Function.prototype.myApply = function(context = window,args){if(this === Function.prototype){return undefined;}context = context || window;const fn = Symbol();context[fn] = this;let result;if(Array.isArray(args)){result = context[fn](...args);}else{result = context[fn]();}delete context[fn];return result;}//调用let person1 = {name:'王',age:18,say(...args){console.log(`名字为${this.name},年龄为${this.age},参数为${args}`);}}let person2 = {name:'李',age:20}person1.say.myApply(person2,[1,2,3]);//输出 名字为李,年龄为20,参数为1,2,3
bind
Function.prototype.myBind = function(obj){if (typeof this !== "function") {throw new TypeError(`${this} is not callable`);}let self = this;const args = [...arguments].slice(1);return function F(){// 因为返回了一个函数,我们可以 new F(),所以需要判断if(this instanceof F) {return new self(...args, ...arguments);}return self.apply(obj, args.concat(...arguments));}}//调用let person1 = {name:'王',age:18,say(...args){console.log(`名字为${this.name},年龄为${this.age},参数为${args}`);}}let person2 = {name:'李',age:20}person1.say.myBind(person2,1,2,3)(4,5);//输出 名字为李,年龄为20,参数为1,2,3,4,5
ES6 的 bind() 方法可以顺带用做构造函数。如果 bind() 返回的函数用做构造函数,将忽略传入 bind() 的 this ,原始函数就会以构造函数的形式调用,其实参也已经绑定(即在运行时将 bind() 所返回的函数用做构造函数时,所传入的实参会原封不动的传入原始函数)。由 bind() 方法所返回的函数并不包含 prototype 属性(普通函数固有的 prototype 属性是不能删除的),并且将这些绑定的函数用做构造函数时所创建的对象从原始的未绑定的构造函数中继承 prototype 。同样,在使用 instanceof 运算符时,绑定构造函数和未绑定构造函数并无两样。
应用
将伪数组转化为数组
function fn() {return Array.prototype.slice.call(arguments);}console.log(fn(1, 2, 3, 4, 5)); // [1, 2, 3, 4, 5]
数组拼接
let arr1 = [1, 2, 3];let arr2 = [4, 5, 6];let arr3 = arr1.concat(arr2);console.log(arr3); // [1, 2, 3, 4, 5, 6]console.log(arr1); // [1, 2, 3]console.log(arr2); // [4, 5, 6][].push.apply(arr1, arr2);console.log(arr1); // [1, 2, 3, 4, 5, 6]console.log(arr2); // [4, 5, 6]
判断变量类型
let arr1 = [1,2,3];let str1 = 'string';let obj1 = {name: 'thomas'};function isArray(obj) {return Object.prototype.toString.call(obj) === '[object Array]';}console.log(fn1(arr1)); // true// 判断类型的方式,这个最常用语判断array和object,null(因为typeof null等于object)console.log(Object.prototype.toString.call(arr1)); // [object Array]console.log(Object.prototype.toString.call(str1)); // [object String]console.log(Object.prototype.toString.call(obj1)); // [object Object]console.log(Object.prototype.toString.call(null)); // [object Null]
