call
Try it
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
:::tip
该方法的语法和作用与 apply()
方法类似,只有一个区别,就是 call()
方法接受的是一个参数列表,而 apply()
方法接受的是一个包含多个参数的数组。
:::
function Product (name, price) {
this.name = name;
this.price = price;
}
function Food (name, price) {
Product.call(this, name, price);
this.category = 'food';
}
console.log(new Food('cheese', 5).name); // cheese
语法
function.call(thisArg, arg1, arg2, ...)
参数
thisArg
可选,在
function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。arg1, arg2, ...
可选,指定要传递给函数的参数。
返回值
使用调用者提供的 this
值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
。
描述
call()
允许为不同的对象分配和调用属于一个对象的函数/方法。
call()
提供新的 this
值给当前调用的函数/方法。可以使用 call
来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
示例
使用 call
方法调用父构造函数
在一个子构造函数中,可以通过调用父构造函数的 call
方法来实现继承,类似于 Java 中的写法。
function Product (name, price) {
this.name = name;
this.price = price;
}
function Food (name, price) {
Product.call(this, name, price);
this.category = 'food';
}
function Toy (name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
var cheese = new Food('cheese', 5);
var fun = new Toy('robot', 10);
使用 call
方法调用匿名函数
var animals = [
{ species: 'Lion', name: 'King' },
{ species: 'Whale', name: 'Fail' }
];
for (var i = 0; i < animals.length; i++) {
(function (i) {
this.print = function () {
console.log('#' + i + ' ' + this.species + ': ' + this.name);
};
this.print();
}).call(animals[i], i);
}
使用 call
方法调用函数并且指定上下文的 'this'
function greet () {
var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
console.log(reply);
}
var obj = {
animal: 'cats',
sleepDuration: '12 and 16 hours'
};
greet.call(obj); // cats typically sleep between 12 and 16 hours
使用 call
方法调用函数并且不指定第一个参数(argument
)
var sData = 'Wisen';
function display () {
console.log('sData value is', this.sData);
}
display.call(sData); // sData value is Wisen
:::warning
在严格模式下,this
的值将会是 undefined
。
:::
'use strict';
var sData = 'Wisen';
function display() {
console.log('sData value is %s ', this.sData);
}
display.call(); // TypeError: Cannot read properties of undefined (reading 'sData')
实现
思路
基础思路
- 将函数设置为对象的属性
- 执行该函数
- 删除该函数
Function.prototype.myCall = function (context) {
context.fn = this;
context.fn();
delete context.fn;
}
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.myCall(foo); // 1
- 处理参数
Function.prototype.myCall = function (context) {
context.fn = this;
var args = [];
// 不使用 join ,因为 join 是 es6 语法,call 是 es3 语法
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
eval('context.fn(' + args + ')');
delete context.fn;
}
var foo = {
value: 1
};
function bar (name, price) {
console.log(this.value);
console.log(name);
console.log(price);
}
bar.myCall(foo, 'Wisen', 10); // 1 Wisen 10
- 其他边界情况
Function.prototype.myCall = function (context) {
var context = context || window;
context.fn = 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;
}
var value = 2;
var obj = {
value: 1
};
function bar (name, age) {
console.log(this.value);
return {
name: name,
age: age
};
}
bar.myCall(null); // 2
console.log(bar.myCall(obj, 'Wisen', 10));
// 1
// { name: 'Wisen', age: 10 }
apply
Try it
apply()
方法调用一个具有给定 this
值的函数,以及以一个数组(或类数组对象
)的形式提供的参数。
::: tipcall()
方法的作用和 apply()
方法类似,区别就是 call()
方法接受的是参数列表,而 apply()
方法接受的是一个参数数组。
:::
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max); // 7
const min = Math.min.apply(null, numbers);
console.log(min); // 2
语法
func.apply(thisArg, [argsArray])
参数
thisArg
必选的。在 func
函数运行时使用的 this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null
或 undefined
时会自动替换为指向全局对象,原始值会被包装。
argsArray
可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func
函数。如果该参数的值为 null
或 undefined
,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。
返回值
调用有指定 this
值和参数的函数的结果。
描述
在调用一个存在的函数时,可以为其指定一个 this
对象。 this
指当前对象,也就是正在调用这个函数的对象。 使用 apply
, 可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
apply
与 call()
非常相似,不同之处在于提供参数的方式。apply
使用参数数组而不是一组参数列表。apply
可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas'])
,或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))
。
也可以使用 arguments
对象作为 argsArray
参数。 arguments
是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,在使用 apply
函数的时候就不需要知道被调用对象的所有参数。可以使用 arguments
来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。
示例
用 apply
将数组各项添加到另一个数组
// concat 符合需求,但它并不是将元素添加到现有数组,而是创建并返回一个新数组
// 由于 push 接受可变数量的参数,所以也可以一次追加多个元素。
// 如果 push 的参数是数组,它会将该数组作为单个元素添加,而不是将这个数组内的每个元素添加进去
var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.log(array); // ['a', 'b', 0, 1, 2]
使用 apply
和内置函数
对于一些需要写循环以遍历数组各项的需求,可以用 apply
完成以避免循环。
/* 找出数组中最大/小的数字 */
var numbers = [5, 6, 2, 3, 7];
/* 使用 Math.min/Math.max 以及 apply 函数时的代码 */
var max = Math.max.apply(null, numbers); /* 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers);
::: warning
如果按上面方式调用 apply
,有超出 JavaScript 引擎参数长度上限的风险。一个方法传入过多参数(比如一万个)时的后果在不同 JavaScript 引擎中表现不同。
:::
如果参数数组非常大,可以使用混合策略:将数组切块处理
function minOfArray (arr) {
var min = Infinity;
var QUANTUM = 32768;
for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
min = Math.min(submin, min);
}
return min;
}
var min = minOfArray([5, 6, 2, 3, 7]); // 2
实现
Function.prototype.myApply = function (context, args) {
var context = context || window;
context.fn = this;
var result;
if (!args) {
result = context.fn();
} else {
var args_arr = [];
for (var i = 0, len = args.length; i < len; i++) {
args_arr.push('arguments[' + i + ']');
}
result = eval('context.fn(' + args_arr + ')');
}
delete context.fn;
return result;
}
bind
Try it
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
const obj = {
x: 42,
getX: function() {
return this.x;
}
};
const unboundGetX = obj.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined
const boundGetX = unboundGetX.bind(obj);
console.log(boundGetX()); // The function gets invoked with the this bound to obj
// expected output: 42
语法
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数
thisArg
调用绑定函数时作为 this
参数传递给目标函数的值。 如果使用 new
运算符构造绑定函数,则忽略该值。当使用 bind
在 setTimeout
中创建一个函数(作为回调提供)时,作为 thisArg
传递的任何原始值都将转换为 object
。如果 bind
函数的参数列表为空,或者 thisArg
是 null
或 undefined
,执行作用域的 this
将被视为新函数的 thisArg
。
arg1, arg2, ...
当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
返回值
返回一个原函数的拷贝,并拥有指定的 this
值和初始参数。
描述
bind() 函数会创建一个新的绑定函数(bound function,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
绑定函数具有以下内部属性:
- [[BoundTargetFunction]] - 包装的函数对象
- [[BoundThis]] - 在调用包装函数时始终作为 this 值传递的值。
- [[BoundArguments]] - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。
- [[Call]] - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数的列表。
当调用绑定函数时,它调用 [[BoundTargetFunction]] 上的内部方法 [[Call]],就像这样 Call(boundThis, args)
。其中,boundThis 是 [[BoundThis]],args 是 [[BoundArguments]] 加上通过函数调用传入的参数列表。
绑定函数也可以使用 new
运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this
值会被忽略,但前置参数仍会提供给模拟函数。
示例
创建绑定函数
bind()
最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的 this
值。
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX(); // 9
// 创建一个新函数, 把 this 绑定到 module 对象上
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
偏函数
使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为 bind()
的参数写在 this
后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。
function list() {
return Array.prototype.slice.call(arguments);
}
function addArguments (arg1, arg2) {
return arg1 + arg2;
}
var list_1 = list(1, 2, 3); // [1, 2, 3]
var result_1 = addArguments(1, 2); // 3
// 创建一个函数, 它拥有预设参数列表
var leadingThirtysevenList = list.bind(null, 37);
// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);
var list_2 = leadingThirtysevenList(); // [37]
var list_3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
var result_2 = addArguments(5); // 37 + 5 = 42
var result_3 = addThirtySeven(5, 10); // 37 + 5 = 42 , 第二个参数会被忽略
配合 setTimeout
function LaterBloomer () {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
LaterBloomer.prototype.bloom = function () {
window.setTimeout(this.declare.bind(this), 1000);
};
LaterBloomer.prototype.declare = function () {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LaterBloomer();
flower.bloom();
实现
特点
- 返回一个函数
- 可以传入参数
- 返回函数模拟
Function.prototype.myBind = function (context) {
var self = this;
return function () {
self.apply(context);
}
}
var foo = {
value: 1
};
function bar () {
console.log(this.value);
}
var bindFoo = bar.myBind(foo);
bindFoo(); // 1
- 传入参数的模拟
Function.prototype.myBind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
return function () {
var bindArgs = Array.prototype.slice.call(arguments); // 返回函数的参数
self.apply(context, args.concat(bindArgs)); // 旧的参数 + 新的参数
};
}
var foo = {
value: 1
};
function bar (name, age) {
console.log(this.value, name, age);
}
var bindFoo = bar.myBind(foo, 'moe');
bindFoo(32); // 1, moe, 32
- 构造函数效果的模拟实现
// TODO