对javascript中的call()和apply()及bind()的理解

 在工作中经常看到有使用call和apply这连个方法,每次看到后都会去网上搜一下用法,然而过一段时间就又不会了,归根到底是自己没有理解他的原理,这次准备彻底弄懂,并把自己的理解总结下来

作用主要有两点:

1、允许为不同的对象分配和调用属于另一个对象的函数/方法,也就是改变(或者说是扩大)函数运行的作用域,优点:对象不需要跟方法有任何耦合关系;
2、提供新的 this 值给当前调用的函数/方法。

1、fun.call(thisArg, arg1, arg2, ...)
参数描述
thisArg在fun函数运行时指定的this值。
arg1,arg2指定的参数列表,必须一个一个的列出来

下面来看几个例子:

function Person(name, age) {
	this.name = name;
	this.age = age;
}
Person.prototype.sayHi = function(a,b) {
	console.log("this的值:" + this)
	console.log("sayhi:" + this.name + ":" + this.age + ",参数的个数:" + arguments.length);
}
var p1 = new Person("小白", 2);
p1.sayHi.call();
p1.sayHi.call(null);
p1.sayHi.call(undefined);
p1.sayHi.call("1");
p1.sayHi.call(true);
var obj = {
	name: "张三",
	age: 21
}
p1.sayHi.call(obj);
function Teacher(name, age) {
	this.name = name;
	this.age = age;
}
var t1 = new Teacher("张三", 18);
p1.sayHi.call(t1, 1, 2, 3); 
复制代码

输出结果如下:

  结果分析:当给fun.call()中的thisObj赋值为空、null、undefined时,函数fun中的this值指向window。
下面call()函数的源码的大概实现:

    Function.prototype.Call = function(context) {
	var context = context || window;
	var args = Array.prototype.slice.call(arguments, 1);
	var type = typeof context;
	switch(type) {
		case "boolean":
			context = new Boolean(context);
			break;
		case "number":
			context = new Number(context);
			break;
			case "string":
			context = new String(context);
			break;
		default:
			break;
	}
	context.fn = this;
	const result = context.fn(...args);
	delete context.fn;
	return result;
}
var obj={c:4};
var arr=[1,2,3];
//Array
foo.Call(arr,1,2,3);
foo.call(arr,1,2,3);
//object
foo.Call(obj,1,2,3);
foo.call(obj,1,2,3);
//Number
foo.Call(1,1,2,3)
foo.call(1,1,2,3);
//Boolean
foo.Call(true,1,2,3)
foo.call(true,1,2,3);
//null
foo.es6Call(null,1,2,3)
foo.call(null,1,2,3);
//undefined
foo.Call(undefined,1,2,3)
foo.call(undefined,1,2,3);
//String
foo.Call("123",1,2,3)
foo.call("123",1,2,3);
复制代码

调用结果如下所示:

上面代码的不足之处有2点:

1、只考虑到Boolean、String、Number、null、undefined和Object以及Array这几种数据类型的功能实现;
2、调用后会给fn.call(obj)中的obj多增加一个属性fn。

2、fun.apply(thisArg, [arguments])
参数描述
thisArg在fun函数运行时指定的this值。
arguments指定的参数数组

  apply()和call()这两个方法的用途是一样的,都是在特定的作用域中调用函数,实际上就是设置函数fun内的this对象的值。call()和apply()的区别在于call()里传递的参数必须一个一个的列出来,apply里传递的参数可以是数组也可以是arguments对象。

区别可见如下代码:

function Person(name, age) {
	this.name = name;
	this.age = age;
}
Person.prototype.sayHi = function(a,b) {
	console.log("a的值:"+a);
	console.log("b的值:"+b);
	console.log("this的值:" + this)
	console.log("sayhi:" + this.name + ":" + this.age + ",参数的个数:" + arguments.length);
}	
var obj={
	name:"李四",
	age:25
}
var p1=new Person("张三",30);
p1.sayHi.call(obj, 1, 2, 3);
p1.sayHi.call(obj, [1,2,3]);
p1.sayHi.apply(obj, [1,2,3]); 
复制代码

输出结果如下:

下面call()函数的源码的大概实现(缺点跟call()的实现是一样的。):

    Function.prototype.es6Apply = function(context, arr) {
	var context = context || window;
	var type = typeof context;
	switch(type) {
		case "boolean":
			context = new Boolean(context);
			break;
		case "number":
			context = new Number(context);
			break;
		case "string":
			context = new String(context);
			break;
		default:

			break;
	}
	context.fn = this;
	let result;
	if(!arr) {
		result = context.fn();
	} else {
		if(!(Object.prototype.toString.call(arr) == "[object Array]")) {
			throw new Error('CreateListFromArrayLike called on non-object');
		}
		result = context.fn(...arr);
	}
	delete context.fn;
	return result
}
复制代码
3、call()和apply的常见用法:apply常常被用于数组操作。

1、如合并两个数组,且改变原数组。

var array = ['a', 'b'];     
var elements = [0, 1, 2];   
array.push.apply(array, elements);  
console.log(array); // ["a", "b", 0, 1, 2]  
复制代码

2、如求取数组中的最大最小值。

var numbers = [5, 6, 2, 3, 7];  
var max = Math.max.apply(null, numbers);    
/* 基本等同于 Math.max(5, 6, 2, 3, 7)  */   
var min = Math.min.apply(null, numbers);  
 /* 基本等同于 Math.min(5, 6, 2, 3, 7)  */ 
复制代码

3、实现继承

function Person(name, age) {
	this.name = name;
	this.age = age;
	this.showName = function() {
		console.log(this.name);
	};
	this.showAge = function() {
		console.log(this.age);
	}
}
function Teacher(name, age) {
	Person.apply(this, [age,name]);
}
var p1=new Person("李四",25);
var t1 = new Teacher("张三", 30);
t1.showName();//"张三"
t1.showAge();//30
复制代码

  调用var p1=new Person("李四",25)时返回的是如下对象。Person构造函数中的this指的是Person。

  调用var 11=new Teacher("张三",30)时,因为调用了Person.apply(this, [age,name])。Person构造函数中的this指的是Teacher。所有返回的是如下对象。

  分析:Person.apply(this, [age,name])中的this在new Teacher()时指的是Teacher,所以Person.apply(this, [age,name])这句代码的返回的是一个Teacher实例对象。

4、bind()

  该方法会创建一个函数的实例,其this值会绑定到传给bind()函数的值。 例子:
        window.color="red";
        var o={color:"blue"};
        function sayColor(){
            console.log(this.color);
        }
        var objSayColor=sayClor.bind(o);
        objSayColor();//"blue"
        sayColor();//"red"
复制代码

  上面的例子相当于改变了sayColor()函数中this的引用。

bind()函数的源码的实现:

    Function.prototype.bind = function(oThis) {
    	if(typeof this !== 'function') {
    		// closest thing possible to the ECMAScript 5
    		// internal IsCallable function
    		throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    	}
    
    	var aArgs = Array.prototype.slice.call(arguments, 1),
    		fToBind = this,
    		fNOP = function() {},
    		fBound = function() {
    			// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
    			return fToBind.apply(this instanceof fBound ?
    				this :
    				oThis,
    				// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
    				aArgs.concat(Array.prototype.slice.call(arguments)));
    		};
    
    	// 维护原型关系
    	if(this.prototype) {
    		// Function.prototype doesn't have a prototype property
    		fNOP.prototype = this.prototype;
    	}
    	// 下行的代码使fBound.prototype是fNOP的实例,因此
    	// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    	fBound.prototype = new fNOP();
    
    	return fBound;
    };
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值