为什么说作为一个前端必须要知道并且理解call 和apply,bind的,因为向我们日常用的很多库,vuejs,react,lodash,momnetjs里面实现的方法很多都会用到,所以作为一个前端,如果想深入学习一些框架源码,必须要对call,apply,bind了如指掌。
一. Function.prototype.call()
基本概念:
call() : 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。并执行这个函数。
语法:function.call(thisArg, arg1, arg2, ...),后面arg1,arg2为参数列表:
示例一:
- 执行b():因为在全局调用了这个函数。this是指向window的,所以this.name输出为: "鬼泣",
- 执行obj1.func():func内部的this是指向obj1, 所以this.name输出为:"剑魂"
- 执行obj1.func.call:当我们使用call的时候,并把对象A1作为第一个参数传递过去的时候,此时的this指向了A1,所以this.name输出为:"阿修罗",
let name = '鬼泣';
let A1 = { name: '阿修罗' };
let obj1 = {
name: '剑魂',
func: function (name, level) {
console.log(this.name);
console.log(name);
console.log(level);
}
}; l
let b = obj1.func;
b();
obj1.func();
obj1.func.call(a2, '狂战士', 1000);示例一:在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,从而不需要自己手动的一个一个去添加同一个属性:
function Profession(name, level) {
this.name = name;
this.level = level;
}
function Role(name, level) {
Profession.call(this, name, level);
}
let createRole = new Role("剑魂", 95);
console.log(createRole.level); // 95模拟一个call():
Function.prototype.call2 = function (context) {
var context = context || window; // 如果没有传递参数,context是指向window
context.fn = this; // 此时this为调用者函数
var args = []; // 获取传进来的参数
for (var i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}
// 执行调用这函数函数,
var result = eval("context.fn(" + args + ")");
delete context.fn; // 并且把函数执行完后的返回执行结果
return result;
};二. Function.prototype.apply()
基本概念:
方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
语法:function.call(thisArg, [params1, params2, ...])
示例1:其实apply 很 call 的功能差不多。只是参数传递方式不一样:第一个参数都是this的指向。不同点在于:
- call的后面参数传递是以参数列表的方式,
- apply的后面参数传递是以数组的方式,
引用上面的例子:
let name = "鬼泣";
let A1 = { name: "阿修罗" };
let obj1 = {
name: "剑魂",
func: function (name, level) {
console.log(this.name);
console.log(name);
console.log(level);
},
};
let b = obj1.func;
b();
obj1.func();
obj1.func.apply(a2, ["狂战士", 1000]); // 把call改为apply的方式,执行的结果是一样的模拟一个apply():
Function.prototype.apply22 = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}三. Function.prototype.bind()
基本概念:
bind()方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
name = "狂战士";
var comp = {
name: "剑魂",
getName: function (level) {
return this.name;
},
};
comp.getName();
// "剑魂"
var comp11 = comp.getName;
comp11(); // "狂战士" var comp22 = comp11.bind(comp, 85); // bind创建了一个新的函数 comp22(); // "剑魂"这里有一个小坑:
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
示例2:
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');虽然在创建创建对象的时候bind传递的参数为foo,但是在new 的时候,bar内部的this是指向obj的,所以this.value输出为undefind
模拟bind:
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}