认识call apply bind
::: tip
函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this 关键字密切相关.
:::
apply
apply()
方法调用一个具有给定this
值的函数,以及作为一个数组(或类似数组对象)提供的参数。
::: warning
call()
方法的作用和 apply()
方法类似,区别就是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。
:::
var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers);
console.log(max);
// expected output: 7
var min = Math.min.apply(null, numbers);
console.log(min);
// expected output: 2
语法
func.apply(thisArg, [argsArray])
thisArg
可选的。在 func
函数运行时使用的 this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null
或 undefined
时会自动替换为指向全局对象,原始值会被包装。
argsArray
可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func
函数。如果该参数的值为 null
或 undefined
,则表示不需要传入任何参数。从ECMAScript 5
开始可以使用类数组对象.
例子
let array=[1,2]
let elements=[3,4]
array.push.apply(array,elements)
console.log(array)
call
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
语法
fun.call(thisArg, arg1, arg2, ...)
thisArg
thisArg
在 fun
函数运行时指定的 this
值。
如果this
的值是 null
或者 undefined
,指向window
if(thisArg == undefined|null) this = window
如果this
的值是 number|boolean|string
,指向构造对象里面
if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()
arg1, arg2, ...
参数列表
例子
使用 call
方法调用父构造函数
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('feta', 5);
var fun = new Toy('robot', 40);
使用 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
bind
bind()
方法创建一个新的函数,在bind()被调用时,这个新函数的this
被bind
的第一个参数指定,其余的参数将作为新函数的参数供调用时使用
语法
function.bind(thisArg[,arg1[,arg2[, ...]]])
arg1, arg2, ...
当目标函数被调用时,预先添加到绑定函数的参数列表中的参数
thisArg
调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用bind
在setTimeout
中创建一个函数(作为回调提供)时,作为thisArg
传递的任何原始值都将转换为object
。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg
。
返回一个原函数的拷贝,并拥有指定的this
值和初始参数。
例子
this.x = 9; // 在浏览器中,this指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
call/apply与bind的区别
call/apply
改变了函数的this
上下文后马上执行该函数
bind
则是返回改变了上下文后的函数,不执行该函数
call/apply
返回fun
的执行结果
bind
返回fun
的拷贝,并指定了fun
的this
指向,保存了fun
的参数。
共同点
改变方法中this
的指向,减少重复代码,节省内存
引用场景
摘自
http://obkoro1.com/web_accumulate/accumulate/JS/JS基础-call和apply还有bind.html
判断数据类型
function isType(data, type) {
const typeObj = {
'[object String]': 'string',
'[object Number]': 'number',
'[object Boolean]': 'boolean',
'[object Null]': 'null',
'[object Undefined]': 'undefined',
'[object Object]': 'object',
'[object Array]': 'array',
'[object Function]': 'function',
'[object Date]': 'date', // Object.prototype.toString.call(new Date())
'[object RegExp]': 'regExp',
'[object Map]': 'map',
'[object Set]': 'set',
'[object HTMLDivElement]': 'dom', // document.querySelector('#app')
'[object WeakMap]': 'weakMap',
'[object Window]': 'window', // Object.prototype.toString.call(window)
'[object Error]': 'error', // new Error('1')
'[object Arguments]': 'arguments',
}
let name = Object.prototype.toString.call(data) // 借用Object.prototype.toString()获取数据类型
let typeName = typeObj[name] || '未知类型' // 匹配数据类型
return typeName === type // 判断该数据类型是否为传入的类型
}
console.log(
isType({}, 'object'), // true
isType([], 'array'), // true
isType(new Date(), 'object'), // false
isType(new Date(), 'date'), // true
)
类数组借用数组的方法
var arrayLike = {
0: 'OB',
1: 'Koro1',
length: 2
}
Array.prototype.push.call(arrayLike, '添加元素1', '添加元素2');
console.log(arrayLike)
// {"0":"OB","1":"Koro1","2":"添加元素1","3":"添加元素2","length":4}
手写apply
Function.apply.myApply = function(context) {
// 如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
if (context === null || context === undefined) {
context = window;
}
// 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数
function isArrayLike(o) {
if (
o && // o不是null、undefined等
typeof o === "object" && // o是对象
isFinite(o.length) && // o.length是有限数值
o.length >= 0 && // o.length为非负值
o.length === Math.floor(o.length) && // o.length是整数
o.length < 4294967296
)
// o.length < 2^32
return true;
else return false;
}
// 用于临时储存函数
const define_fn = Symbol("manael_apply");
// 隐式绑定this指向到context上
context[define_fn] = this;
let args = arguments[1]; // 获取参数数组
let result;
// 处理传进来的第二个参数
if (args) {
// 是否传递第二个参数
if (!Array.isArray(args) && !isArrayLike(args)) {
throw new TypeError(
"myApply 第二个参数不为数组并且不为类数组对象抛出错误"
);
} else {
args = Array.from(args); // 转为数组
result = context[specialPrototype](...args); // 执行函数并展开数组,传递函数参数
}
} else {
result = context[specialPrototype](); // 执行函数
}
delete context[specialPrototype]; // 删除上下文对象的属性
return result; // 返回函数执行结果
};
};
手写 call
Function.prototype.myCall = function (context, ...arr) {
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数
delete context[specialPrototype]; // 删除上下文对象的属性
return result; // 返回函数执行结果
};
手写 bind
Function.prototype.myBind = function (objThis, ...params) {
const thisFn = this; // 存储源函数以及上方的params(函数参数)
// 对返回的函数 secondParams 二次传参
let fToBind = function (...secondParams) {
const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
};
if (thisFn.prototype) {
// 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数
fToBind.prototype = Object.create(thisFn.prototype);
}
return fToBind; // 返回拷贝的函数
};