JavaScript之call和apply原理

关于call

call函数可以做什么?

call() 函数在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

举个例子:

var name = '爱丽丝';
var obj = {name:'伊玥'};
function love(){
  console.log(this.name);
}
love();//爱丽丝
love.call(obj);//伊玥

注意call函数做了两件事:

  • call把 love 函数 this 指向改为了obj
  • 执行了love函数

实际上我们也可以把调用call的过程改为如下:

var name = '爱丽丝';
var obj = {
	name:'伊玥',
	love: function(){
		console.log(this.name);
	}
}
obj.love();//伊玥

这个时候this的指向就是obj了,最后obj对象多出一个love属性,我们再用delete删除就好了。
所以我们模拟call可以分为三部:

  1. 将函数设置为对象的属性
  2. 执行该函数
  3. 删除该函数

第一版call实现:

Function.prototype.call2 = function(context){
	//fn是设置函数为对象的属性名,起什么名字都无所谓,反正最后都要删除
	//此时this是指调用call的函数
	context.fn = this;
	//执行该函数
	context.fn();
	//删除该函数
}
//测试一下喽
var obj = {name:'伊玥'};
function love(){
	console.log(this.name);
}
love.call2(obj);

第二部call实现

call 函数还能给指定执行的函数传递参数。
我们可以从arguments 对象中取出第二个到最后一个参数,然后放到一个数组里面。

Function.prototype.call2 = function(context){
	context.fn = this;
	var args = [];
	//执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
	for(var i = 1,len = arguments.length;i < len;i++){
		args.push('arguments[' + i + ']');
	}
	//args 会自动调用Array.toString()这个方法
	eval('context.fn(' + args + ')');
	delete context.fn;
}
//测试一下喽
function love(name,age){
	console.log('我叫' + name + ';今年' + age + '岁了');
}
love.call2(obj,'伊玥',21);//我叫伊玥;今年21岁了

第三部call的实现

this 参数传null的时候,指向视为window。当然函数也可以有返回值

Function.prototype.call2 = function(context){
	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 gender = 'women';
var obj = {gender:'men'};
function love(name,age){
	console.log(this.gender);
	return {
		gender:this.gender,
		name:name,
		age:age
	}
}
love.call2(null);//women
love.call2(obj,'伊玥',21)
//men
//{gender:'men',name:'伊玥',age:'21'}

在ES5严格模式中,call()和apply()的第一个实参都会变为this的值,哪怕传入的实参是原始值甚至是null或undefined。非严格模式下,传入的null和undefined都会被全局对象代替,而其他原始值则会被相应的包装对象(wrapper object)所替代。

apply的实现模拟

Function.prototype.apply1 = function(context,arr){
	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;
}

用call实现JS的继承

//父类
function Father(){
	this.sayName = function(){
		console.log('我叫' + this.name);
	}
}
//子类
function Children(name){
	//借助call实现继承
	Father.call(this);
	this.name = name;
	this.ch = function(){
		console.log('我是小孩');
	}
}
var a = new Children('fei');
a.sayName();//我叫fei

call() 方法调用函数且不指定第一个参数时

非严格模式下调用call()方法,如果没有指定第一个参数,this的值将会被绑定为全局对象。

var name = 'WaHaHa';
function display(){
	console.log('名字是:' + this.name);
}
display.call();//名字是:WaHaHa

严格模式下,this的值将会是undefined

'use strict'
var name = 'WaHaHa';
function display(){
	console.log('名字是:' + this.name);
}
display.call();//Uncaught TypeError: Cannot read property 'name' of undefined

call() 方法的经典例子

function fn1(){
	console.log(1);
}
function fn2(){
	console.log(2);
}
fn1.call(fn2);//1
fn1.call.call(fn2);//2

获取最大最小值

var ary = [12,212,1,2,3]
var max = Math.max.apply(null, ary); 
var min = Math.min.apply(null, ary);
console.log(min, max);//1 212

将类数组转化为数组

var oLis = document.getElementsByTagName('div');
var ary = Array.prototype.slice.call(oLis);
console.log(ary);

参考资料:
讶羽博客
MDN
iceman_dev

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值