做为面试中面试官最宠爱的一个问题,在这里进行一个详细的介绍,你们重点要放在理解,而不是背。 写的很差或不对的地方,请你们积极指出,好了,话很少说,咱们“圆规正转”javascript
先说一下三者的区别
共同点就是修改this指向,不一样点就是
1.call()和apply()是马上执行的, 而bind()是返回了一个函数
2.call则能够传递多个参数,第一个参数和apply同样,是用来替换的对象,后边是参数列表。
3.apply最多只能有两个参数——新this对象和一个数组argArray
复制代码
Function.prototype.myCall = function(context) {
context.fn = this;
context.fn();
}
const obj = {
value: 'hdove'
}
function fn() {
console.log(this.value);
}
fn.myCall(obj); // hdove
复制代码
function fn() {
return this.value;
}
console.log(fn.myCall(obj)); // undefined
复制代码
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}
复制代码
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() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其他参数将做为新函数的参数,供调用时使用(MDN)
复制代码
Function.prototype.myBind = function(context) {
const self = this;
return function() {
self.apply(context);
}
}
const obj = {
value: 'hdove'
}
function fn() {
console.log(this.value);
}
var bindFn = fn.myBind(obj);
bindFn(); // 'hdove;
复制代码
相比于call、apply,我我的以为bind的实现逻辑更加复杂,须要考虑的东西不少,在这里分开进行优化。java
在这里咱们须要进行一下判断,判断调用bind
的是否是一个函数,不是的话就要抛出错误。es6
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
return function() {
self.apply(context);
}
}
复制代码
咱们看下面这段代码web
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
return function() {
self.apply(context);
}
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, LJ, 25);
bindFn(); // 'hdove' undefined undefined
复制代码
很明显,第一个优化的地方就是传递参数,咱们来改造下面试
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
// 第一个参数是this,截取掉
const args = [...arguments].slice(1);
return function() {
/**
这里咱们其实便可以使用apply又可使用call来更改this的指向
使用apply的目的其实就是由于args是一个数组,更符合apply的条件
*/
return self.apply(context, args);
}
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, 'LJ', 25);
bindFn(); // 'hdove' 'LJ' 25
复制代码
想在看起来没什么问题,可是咱们这样传一下参数数组
var bindFn = fn.myBind(obj, 'LJ');
bindFn(25); // 'hdove' 'LJ' undefined
复制代码
咱们发现后面传递的参数丢了,这里就须要使用柯里化来解决这个问题bash
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
// 第一个参数是this,截取掉
const args1 = [...arguments].slice(1);
return function() {
// 获取调用时传入的参数
const args2 = [...arguments];
return self.apply(context, args1.concat(args2));
}
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, 'LJ');
bindFn(25); // 'hdove' 'LJ' 25
复制代码
其实bind
还具备一个特性就是 做为构造函数使用的绑定函数
,意思就是这个绑定函数能够当成构造函数使用,能够调用new
操做符去建立一个实例,当咱们使用new
操做符以后,this
其实不是指向咱们指定的对象,而是指向new
出来的这个实例的构造函数,不过提供的参数列表仍然会插入到构造函数调用时的参数列表以前。咱们简单实现一下。app
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
const args1 = [...arguments].slice(1);
const bindFn = function() {
const args2 = [...arguments];
/**
这里咱们经过打印this,咱们能够看出来。
当这个绑定函数被当作普通函数调用的时候,this实际上是指向window。
而当作构造函数使用的时候,倒是指向这个实例,因此this instanceof bindFn为true,这个实例能够获取到fn()里面的值。
咱们能够再fn里面添加一个属性test.
若是按照以前的写法 打印出来的是undefined,正好验证了咱们上面所说的this指向的问题。
因此解决方法就是添加验证,判断当前this
若是 this instanceof bindFn 说明这是new出来的实例,指向这个实例, 不然指向context
*/
console.log(this);
return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
}
return bindFn;
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
this.test = '我是测试数据';
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, 'LJ');
var newBind = new bindFn(25);
console.log(newBind.test); // undefined
复制代码
咱们都知道每个构造函数,都会有一个原型对象(prototype),来添加额外的属性。函数
function fn(name, age) {
this.test = '我是测试数据';
}
fn.prototype.pro = '原型数据';
var bindFn = fn.myBind(obj, 'LJ', 25);
var newBind = new bindFn();
console.log(bindObj.pro); // undefined
复制代码
由于咱们没有绑定原型,因此会出现undefined
,咱们简单绑定一下post
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
const args1 = [...arguments].slice(1);
const bindFn = function() {
const args2 = [...arguments];
return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
}
// 绑定原型
bindFn.prototype = self.prototype;
return bindFn;
}
function fn(name, age) {
this.test = '我是测试数据';
}
fn.prototype.pro = '原型数据';
var bindFn = fn.myBind(obj, 'LJ', 25);
var newBind = new bindFn();
console.log(bindObj.pro); // "原型数据"
复制代码
可是这样会出现这样一个问题
function fn(name, age) {
this.test = '我是测试数据';
}
fn.prototype.pro = '原型数据';
var bindFn = fn.myBind(obj, 'LJ');
var bindObj = new bindFn();
bindObj.__proto__.pro = '篡改原型数据';
console.log(bindObj.__proto__ === fn.prototype); // true
console.log(bindObj.pro); // "篡改原型数据"
console.log(fn.prototype.pro); // "篡改原型数据"
当咱们修改bindObj的原型的时候,fn的原型也一块儿修改了
这实际上是由于 bindObj.__proto__ === fn.prototype
咱们在修改bindObj的同时也间接修改了fn
复制代码
解决方法其实很简单,建立一个新方法proFn()
,来进行原型绑定,也就是实现继承的几种方式中的原型式继承,而后咱们把这个新方法的实例对象绑定到咱们的绑定函数的原型中
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一个函数");
}
const self = this;
const args1 = [...arguments].slice(1);
const bindFn = function() {
const args2 = [...arguments];
return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
}
// 绑定原型
function proFn() {} //建立新方法
proFn.prototype = self.prototype; //继承原型
bindFn.prototype = new proFn(); //绑定原型
return bindFn;
}
function fn(name, age) {
this.test = '我是测试数据';
}
fn.prototype.pro = '原型数据';
var bindFn = fn.myBind(obj, 'LJ', 25);
var newBind = new bindFn();
console.log(bindObj.__proto__ === fn.prototype); // false
console.log(bindObj.pro); // "篡改原型数据"
console.log(fn.prototype.pro); // "原型数据"
复制代码
这些东西实际上是面试中比较容易考到的问题,你们不要想着去背,背下来实际上是没什么用处的,容易会被问倒,重点仍是在于理解,理解了也就能够垂手可得的写出来了。但愿这篇文章会给你们带来收获,那怕是一点点。在这里,提早给你们伙拜个早年,鼠年幸运,跳槽顺利,涨薪顺利,哈哈。