通过上一篇 全面认识this指向 接触到了call和apply,所以这篇文章就来深入了解一下call、apply以及bind。
本文的主要内容包括:
- call、apply、bind的模拟实现
- 使用场景
模拟实现
call的实现
Function.prototype.callFn = function(context) {
var context = context || window; // this 参数可以传 null,视为指向 window
context.fn = this;
var args = [];
// 传入的参数可以不确定,所以将除了第一个对象之后的参数作为fn的形参传入
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
// 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
var result = eval('context.fn(' + args +')');
delete context.fn;
return result;
};
<!--下面测试一下-->
var foo = {
value: 1
};
function bar(name, age) {
console.log('name: ', name)
console.log('age:', age)
console.log(this.value);
}
bar.callFn(foo, 'armor', 18);
<!--输出:-->
// name: armor
// age: 18
// 1
apply的实现
Function.prototype.applyFn = 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;
}
<!--下面测试一下-->
var foo = {
value: 1
};
function bar(name, age) {
console.log('name: ', name)
console.log('age:', age)
console.log(this.value);
}
bar.applyFn(foo, ['armor', 18]);
<!--输出:-->
// name: armor
// age: 18
// 1
bind的实现
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
bind与apply和call的区别就是bind不会被立即调用,它返回的是一个函数。
首先是使用apply来改写一个简单版的,
Function.prototype.bindFn = function (context) {
var self = this;
return function () {
return self.apply(context);
}
}
但是现在只能给bind传递一个参数,真正的bind函数是可以传递多个参数的,第一个参数是要绑定给调用它的函数的上下文,其他的参数将会作为预设参数传递给这个函数。
改进一下,传递多个参数:
Function.prototype.bindFn = function () {
var self = this;
var args = Array.prototype.slice.call(arguments);
var context = args.splice(0,1)[0];
return function () {
return self.apply(context, args);
}
}
// <!--下面测试一下-->
var foo = {
value: 1
};
function bar(name, age) {
console.log('name: ', name)
console.log('age:', age)
console.log(this.value);
}
var test = bar.bindFn(foo, 'armor', 18);
test();
<!--输出:-->
// name: armor
// age: 18
// 1
注意:bind还有一个特点就是,在使用bind后,得到的函数使用new操作符进行操作之后,这个结果的上下文并不受传递给bind的上下文影响
最后一版:
Function.prototype.bindFn = function(){
var args = Array.prototype.slice.call(arguments);
var context = args.splice(0,1)[0];
var fn = this;
var noop = function(){}
var res = function(){
let rest = Array.prototype.slice.call(arguments);
// 检测 new,如果当前函数的this指向的是构造函数中的this 则判定为new 操作
return fn.apply(this instanceof noop ? this : context,args,rest)
}
// 把函数的原型保留下来,修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
if(this.prototype){
noop.prototype = this.prototype;
}
res.prototype = new noop();
return res;
}
使用场景
合并数组
var a = [1, 2, 3];
var b = [4, 5];
// 将第二个数组融合进第一个数组
// 相当于 a.push(4, 5);
Array.prototype.push.apply(a, b);
a; // [1, 2, 3, 4, 5]
注意: 当第二个数组太大时不要使用这个方法来合并数组,因为一个函数能够接受的参数个数是有限制的。
获取数组中的最大值和最小值
由于JS的数组中并没有max方法,但是Math中有,所以可以借助于Math来绑定this实现这个功能:
var numbers = [5, 25 , 1 , -25];
Math.max.apply(Math, numbers); //25
Math.max.call(Math, 5, 25 , 1 , -25); //25
// ES6
Math.max.call(Math, ...numbers); // 25
使用Object.prototype.toString.call(obj)检测对象类型
在JavaScript里使用typeof判断数据类型,只能区分基本类型,要想区分对象、数组、函数,可以使用Object.prototype.toString.call(obj)来检测对象。由于 JavaScript 中一切都是对象,都继承自Object,所以可以调用Object的原型方法来检测类型。
例如:
Object.prototype.toString.call({name: "jerry"}); //[object Object]
Object.prototype.toString.call(function(){}); //[object Function]
类数组对象转数组
将类数组转换成数组的常用方法是Array.prototype.slice.call()
原理是 slice 将 Array-like 对象通过下标操作放进了新的 Array 里面
补充:
当然,类数组转换成数组不止是slice这个方法,下面补充一下几个其他方法。
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"]
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"]
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"]
// 4. apply
Array.prototype.concat.apply([], arrayLike)
实现继承
在我的另一篇 JS继承方式总结中,借用构造函数(经典继承)里使用了call来实现继承。
function Animal() {
this.type = 'animal';
this.skin = ['black', 'white', 'brown']
}
function Cat() {
// 继承Animal
Animal.call(this);
};
var cat1 = new Cat();
cat1.skin.push('gray');
var cat2 = new Cat();
console.log(cat1.skin); // [ 'black', 'white', 'brown', 'gray' ]
console.log(cat2.skin); // [ 'black', 'white', 'brown' ]
参考文章:
- 深度解析 call 和 apply 原理、使用场景及实现
- JavaScript深入之call和apply的模拟实现通过上一篇 全面认识this指向 接触到了call和apply,所以这篇文章就来深入了解一下call、apply以及bind。
本文的主要内容包括:
- call、apply、bind的模拟实现
- 使用场景
模拟实现
call的实现
Function.prototype.callFn = function(context) {
var context = context || window; // this 参数可以传 null,视为指向 window
context.fn = this;
var args = [];
// 传入的参数可以不确定,所以将除了第一个对象之后的参数作为fn的形参传入
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
// 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
var result = eval('context.fn(' + args +')');
delete context.fn;
return result;
};
<!--下面测试一下-->
var foo = {
value: 1
};
function bar(name, age) {
console.log('name: ', name)
console.log('age:', age)
console.log(this.value);
}
bar.callFn(foo, 'armor', 18);
<!--输出:-->
// name: armor
// age: 18
// 1
apply的实现
Function.prototype.applyFn = 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;
}
<!--下面测试一下-->
var foo = {
value: 1
};
function bar(name, age) {
console.log('name: ', name)
console.log('age:', age)
console.log(this.value);
}
bar.applyFn(foo, ['armor', 18]);
<!--输出:-->
// name: armor
// age: 18
// 1
bind的实现
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
bind与apply和call的区别就是bind不会被立即调用,它返回的是一个函数。
首先是使用apply来改写一个简单版的,
Function.prototype.bindFn = function (context) {
var self = this;
return function () {
return self.apply(context);
}
}
但是现在只能给bind传递一个参数,真正的bind函数是可以传递多个参数的,第一个参数是要绑定给调用它的函数的上下文,其他的参数将会作为预设参数传递给这个函数。
改进一下,传递多个参数:
Function.prototype.bindFn = function () {
var self = this;
var args = Array.prototype.slice.call(arguments);
var context = args.splice(0,1)[0];
return function () {
return self.apply(context, args);
}
}
// <!--下面测试一下-->
var foo = {
value: 1
};
function bar(name, age) {
console.log('name: ', name)
console.log('age:', age)
console.log(this.value);
}
var test = bar.bindFn(foo, 'armor', 18);
test();
<!--输出:-->
// name: armor
// age: 18
// 1
注意:bind还有一个特点就是,在使用bind后,得到的函数使用new操作符进行操作之后,这个结果的上下文并不受传递给bind的上下文影响
最后一版:
Function.prototype.bindFn = function(){
var args = Array.prototype.slice.call(arguments);
var context = args.splice(0,1)[0];
var fn = this;
var noop = function(){}
var res = function(){
let rest = Array.prototype.slice.call(arguments);
// 检测 new,如果当前函数的this指向的是构造函数中的this 则判定为new 操作
return fn.apply(this instanceof noop ? this : context,args,rest)
}
// 把函数的原型保留下来,修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
if(this.prototype){
noop.prototype = this.prototype;
}
res.prototype = new noop();
return res;
}
使用场景
合并数组
var a = [1, 2, 3];
var b = [4, 5];
// 将第二个数组融合进第一个数组
// 相当于 a.push(4, 5);
Array.prototype.push.apply(a, b);
a; // [1, 2, 3, 4, 5]
注意: 当第二个数组太大时不要使用这个方法来合并数组,因为一个函数能够接受的参数个数是有限制的。
获取数组中的最大值和最小值
由于JS的数组中并没有max方法,但是Math中有,所以可以借助于Math来绑定this实现这个功能:
var numbers = [5, 25 , 1 , -25];
Math.max.apply(Math, numbers); //25
Math.max.call(Math, 5, 25 , 1 , -25); //25
// ES6
Math.max.call(Math, ...numbers); // 25
使用Object.prototype.toString.call(obj)检测对象类型
在JavaScript里使用typeof判断数据类型,只能区分基本类型,要想区分对象、数组、函数,可以使用Object.prototype.toString.call(obj)来检测对象。由于 JavaScript 中一切都是对象,都继承自Object,所以可以调用Object的原型方法来检测类型。
例如:
Object.prototype.toString.call({name: "jerry"}); //[object Object]
Object.prototype.toString.call(function(){}); //[object Function]
类数组对象转数组
将类数组转换成数组的常用方法是Array.prototype.slice.call()
原理是 slice 将 Array-like 对象通过下标操作放进了新的 Array 里面
补充:
当然,类数组转换成数组不止是slice这个方法,下面补充一下几个其他方法。
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"]
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"]
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"]
// 4. apply
Array.prototype.concat.apply([], arrayLike)
实现继承
在我的另一篇 JS继承方式总结中,借用构造函数(经典继承)里使用了call来实现继承。
function Animal() {
this.type = 'animal';
this.skin = ['black', 'white', 'brown']
}
function Cat() {
// 继承Animal
Animal.call(this);
};
var cat1 = new Cat();
cat1.skin.push('gray');
var cat2 = new Cat();
console.log(cat1.skin); // [ 'black', 'white', 'brown', 'gray' ]
console.log(cat2.skin); // [ 'black', 'white', 'brown' ]
参考文章: