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可以分为三部:
- 将函数设置为对象的属性
- 执行该函数
- 删除该函数
第一版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