关于Call与Apply函数其实已经是老生常谈了,面试基本也是必问环节了,其作用主要是设置函数体内this对象的值,以扩充函数赖以运行的作用域,这也没啥好说的了。
前言
JS中调用函数大概四种方法:
// 1.全局调用,this指向全局对象.
function fn1() {
return this;
}
console.log(fn1()); // window fn1() => window.fn1()
// 2.对象中调用,this指向该对象.
var obj = {
fn2: function() {
return this;
}
}
console.log(obj.fn2()); // {fn2:function(){return this;}}
// 3.构造函数调, this指向构造函数本身
function fn3() {
console.log(this);
}
console.log(new fn3()); // fn3 {}
// 4.使用call()或者apply()调用, this指向第一个参数
var obj = {
name: 'yd'
}
function test(){
console.log(this)
}
test.call(obj) // {name: 'yd'}
最后的call调用,输出的this,为什么不是window呢?
其实以上的调用方式都隐含的传递了一个变量:this,这个this指向基本就是谁调用就指向谁,而Call与Apply的第一个参数就是用来改变这个this的,如果不传,默认为全局对象。
关于Call函数的作用我们简单打个比方
Call就像现实生活中的打电话,首先打电话前要拨号,这个号码就相当于this,必须有号码才有可能拨通电话,而拨打不同的号码,即Call(null)的参数值不同,接电话的人(作用域)也不同。而给接电话方传递的信息可以通过Call(null, param, param, param....)的其他不必须参数传递,接电话的人也可以通过函数的return回复消息!
这是个人的一个理解方法,它不棒嘛?(被打。。。)
Call 用法
// 例子1
var obj = {
name: 'yd'
}
function say() {
console.log(this.name);
}
say.call(obj); // yd
// 例子2
var str = 'abcde';
console.log(str.substr(1)); // bcde
console.log(''.substr.call(str, 1)); // bcde
// 例子3
var arr = [1, 2, 3, 4, 5];
console.log(arr.slice(1)); // [2, 3, 4, 5]
var obj = {
0: 1,
1: 2,
2: 3,
3: 4,
4: 5,
length: 5
};
console.log([].slice.call(obj, 1)); // [2, 3, 4, 5]
Apply 用法
var obj = {
color:'red';
};
window.color = 'blue';
function test(){
console.log(this.color);
}
test();//blue
test.apply(this);//blue
test.apply(window);//blue
test.apply(null);//blue
test.apply(undefined);//blue
test.apply(obj);//red
test.apply('');//undefined
// 继承
function Dog(name){
this.name = name;
console.log(arguments); // ['小米', '第二个参数']
this.sayName = function(){
console.log(this.name);
}
}
function Cat(name){
// Dog.call(this,name);
Dog.apply(this, [name, '第二个参数']);
}
var cat = new Cat('小米');
cat.sayName(); // 小米
区别:两者接收的参数不一样。(call参数为一个一个明确的值;apply参数为一个数组(argument),数组会解析成一个一个参数)
Call简单分析之手写实现
基本使用:
var obj = {
name: 'yd'
}
function test(){
console.log(this.name)
}
test.call(obj) // yd
- 实现一 :相当我要重写Function.prototype.call方法。
/* 首先我们先做个假设如果我们将obj变成这样子:
var obj = {
name: 'yd'
test: function(){
console.log(this.name)
}
}
obj.test()
这样是不是很简单的实现了,当然,还没完呢,这时obj无缘无故多了个属性,这是不好的,所以呢我们还要用delete删除
*/
// 完整代码如下:
Function.prototype.myCall = function(context){
context.fn = this // this: 能得到test()函数[谁调就是谁]; fn: 叫什么名字其实都无所谓,反正要删除的
context.fn()
delete context.fn
}
var obj = {
name: 'yd'
}
function test(){
console.log(this.name) // yd
}
test.myCall(obj)
Call能传递参数
var obj = {
name: 'yd'
}
function test(nickname, age){
console.log(this.name, nickname, age) // yd YDYDYDQ 20
}
test.call(obj, 'YDYDYDQ', 20)
- 实现二 :1.在myCall中接收到全部参数。2.将参数依次注入test()中使用。
/* 首先我们要想方法获取这些参数,因为参数数量是不确定的,所以呢我们只能通过arguments.
每个函数都有一个arguments,在myCall()中arguments如下:
arguments = {
0: obj,
1: 'YDYDYDQ',
2: 20,
length: 3
}
我们要取第二个到最后一个.
*/
// 完整代码如下(第一步):
Function.prototype.myCall = function(context){
context.fn = this
var args = []
for(var i = 1;i < arguments.length;i++){ // 因为arguments是类数组对象,所以可以进行迭代
args.push(arguments[i])
}
console.log(args) // ["YDYDYDQ", 20]
context.fn()
delete context.fn
}
var obj = {
name: 'yd'
}
function test(){
console.log(this.name) // yd
}
test.myCall(obj, 'YDYDYDQ', 20);
/* 那么第二步我们要想办法将ages变成一个一个参数,依次注入到test()函数中了.即我们要将参数放入fn()中.
这里我们要借用eval(),不懂的可以先自行百度一下哦.
*/
// 完整代码如下(第二步):
Function.prototype.myCall = function(context){
context.fn = this
var args = []
for(var i = 1;i < arguments.length;i++){
args.push('arguments[' + i + ']') //因为eval()会解析args每个值,所以将数组值改成arguments[i]字符
}
eval('context.fn(' + args +')') // args数组会自动调用toString(),如eval('[1, 2]') => [1, 2]
// 本质变成这样执行: context.fn(arguments[1], arguments[2])
delete context.fn
}
var obj = {
name: 'yd'
}
function test(nickname, age){
console.log(this.name, nickname, age) // yd YDYDYDQ 20
}
test.myCall(obj, 'YDYDYDQ', 20)
- 现实三 :处理边界情况,其实就是处理一下this为null与其处理函数返回值,直接上代码吧。
Function.prototype.myCall = function(context){
context = context || window
context.fn = this
var args = []
for(var i = 1;i < arguments.length;i++){
args.push('arguments[' + i + ']') //因为eval()会解析args每个值,所以将数组值改成arguments[i]字符
}
var result = eval('context.fn(' + args +')') // args数组会自动调用toString()
// 本质变成这样执行: context.fn(arguments[1], arguments[2])
delete context.fn
return result
}
var obj = {
name: 'yd'
}
function test(nickname, age){
console.log(this.name, nickname, age) // yd YDYDYDQ 20
return '我是函数返回值'
}
console.log(test.myCall(obj, 'YDYDYDQ', 20))
// obj不传默认指向this
至此,Call函数的基本原理也算弄清楚了吧?(不清楚也不要问我....哈哈)
Apply函数手写实现
废话一下:Apply方法和Call方法几乎差不多,就参数列表不同而已,它只接收一个数组参数,废话不多说,直接上代码。
Function.prototype.myApply = function(context, arr){
context = 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 obj = {
name: 'yd'
}
function test(nickname, age){
console.log(this.name, nickname, age) // yd YDYDYDQ 20
return '我是函数返回值'
}
console.log(test.myApply(obj, ['YDYDYDQ', 20]))
完结,撒花撒花。^ .. ^