call
function test(){
}
test(); ---> test.call();
其实test.call()
才是这个函数真正的执行。
call的根本作用是改变this的指向。利用别人的函数实现自己的功能。
function Person(name,age){
this.name=name;
this.age=age;
}
Person(); ---> Person.call();
var obj={};
Person.call(obj,'minmin',18)
call()的第一个参数如果传的是一个对象的话,他就让Person的this指向发生了改变,this===obj
,this指向了obj,就实现了借助Person的方法构造了自己的属性。obj就有了name和age。
function Person(name,age){
this.name=name;
this.age=age;
}
function Student(name,age,sex){
//var this={};
//this.name=name;
//this.age=age;
Person.call(this,name,age);
this.sex=sex;
}
var student=new Student('minmin',18,'female');
Person.call(this,name,age);
中的this是var this={}
中的this,改变Person中的this指向,Student中就有了Person的name和age,利用Person函数实现自己的功能。
call的实现
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
call 的作用无非就是当执行一个方法的时候希望能够使用另一个对象来作为作用域对象而已,简单来说就是当我执行 A 方法的时候,希望通过传入参数的形式将一个对象 B 传进去,用以将 A 方法的作用域对象替换为对象 B
function person(){
console.log(this.name);
}
var egg={name:'mm'}
egg.fn=person;
egg.fn(); // mm
call从哪里来?
每个js函数都是Function对象
Function.prototype(原型对象) --> Function对象(构造函数)
call来自于原型对象的属性,是一个方法。
在原型对象里面添加新的和call一样的属性,同时也是一种方法。
用call进行绑定,相当于在egg对象里面增加了一个person函数,再使用egg.person()进行调用。
function person(){
console.log(this.name);
}
var egg={
name:'mm'
}
Function.prototype.newCall=function(obj){
console.log(this); // person
}
person.newCall(egg);
this 指向谁?
this 指向person函数,这个时候还没开始硬绑定。person才是真正的调用位置。
现在给形参对象添加方法
function person(){
console.log(this.name);
}
var egg={
name:'mm'
}
Function.prototype.newCall=function(obj){
// 这样相当于给对象添加一个person方法
obj.fn=this;
obj.fn();
delete obj.fn;
}
person.newCall(egg);
函数本身的参数是没有this的,而call的第一个参数是this的指向,后面的参数就是函数本身的参数了,那么就需要把另外的参数保存起来,并且不保留第一个this参数。
要获取newCall函数的参数,要用到arguments对象
设置newCall的时候,内部执行的是不带参数的函数,我们并没有使用任何一个参数。
function person(a,b,c,d){
console.log(this.name); // mm
console.log(a,b,c,d); // 4个undrfined
}
var egg={
name:'mm'
}
Function.prototype.newCall=function(obj){
// 这样相当于给对象添加一个person方法
obj.fn=this;
var newArguments=[];
for(var i=1;i<arguments.length;i++){
newArguments.push(arguments[i]);
}
console.log(newArguments); // 1,2,3,4
obj.fn();
delete obj.fn;
}
person.newCall(egg,'1','2','3','4');
如果把新的数组直接赋值给函数fn,obj.fn(newArguments);
,结果会显示第一个参数的数组值,其他三个都是undefined。所以我们需要把参数拆分开来显示。
for循环的时候每次执行一次函数就好了,这样是不可行的。这样会重新执行函数,导致this.name重复输出。所以这些参数我们只能接受一次性的输出。
obj.fn('1','2','3','4')
obj.fn(arguments[1],arguments[2],arguments[3],arguments[4])
但是我们不知道有多少个参数,因此需要用字符串拼接。
"obj.fn(arguments[1],arguments[2],arguments[3],arguments[4])"
newArguments=[arguments[1],arguments[2],arguments[3],arguments[4]]
"obj.fn(" + newArguments +")"
数组会调用toString()方法
newArguments=['1','2','3','4']
数组会直接显示参数
"obj.fn(1,2,3,4)"
这些参数没有了引号
所以需要用字符串的方式隐藏掉这些参数
newArguments=['arguments[1]','arguments[2]','arguments[3]','arguments[4]']
"obj.fn(arguments[1],arguments[2],arguments[3],arguments[4])"
function person(a,b,c,d){
console.log(this.name); // mm
console.log(a,b,c,d); // '1','2','3','4'
}
var egg={
name:'mm'
}
Function.prototype.newCall=function(obj){
// 这样相当于给对象添加一个person方法
obj.fn=this;
var newArguments=[];
for(var i=1;i<arguments.length;i++){
newArguments.push('arguments['+i+']');
}
eval('obj.fn('+newArguments+')');
delete obj.fn;
}
person.newCall(egg,'1','2','3','4');
我们一般创建函数以后要么就执行某些动作,要么就返回值,当我们返回一个对象的时候,输出undefined
function person(a,b,c,d){
return{
name:this.name,
a:a,b:b,c:c,d:d
}
}
var egg={name:'mm'}
Function.prototype.newCall=function(obj){
var obj=obj || window;
obj.fn=this;
var newArguments=[];
for(var i=1;i<arguments.length;i++){
newArguments.push('arguments['+i+']');
}
eval('obj.fn('+newArguments+')');
delete obj.fn;
}
var o=person.newCall(egg,'1','2','3','4');
console.log(o); // undefined
最后实现
function person(a,b,c,d){
return{
name:this.name,
a:a,b:b,c:c,d:d
}
}
var egg={name:'mm'}
Function.prototype.newCall=function(obj){
var obj=obj || window;
obj.fn=this;
var newArguments=[];
for(var i=1;i<arguments.length;i++){
newArguments.push('arguments['+i+']');
}
var res=eval('obj.fn('+newArguments+')');
delete obj.fn;
return res;
}
var o=person.newCall(egg,'1','2','3','4');
console.log(o);
function person(a,b,c,d){
return{
name:this.name,
a:a,b:b,c:c,d:d
}
}
var egg={name:'mm'}
Function.prototype.newCall=function(obj){
var obj=obj || window;
obj.fn=this;
let newArguments=[...arguments].slice(1);
let res=obj.fn(...newArguments);
delete obj.fn;
return res;
}
var o=person.newCall(egg,'1','2','3','4');
console.log(o);
apply
function Person(name,age){
this.name=name;
this.age=age;
}
function Student(name,age,sex){
//var this={};
//this.name=name;
//this.age=age;
Person.apply(this,[name,age]);
this.sex=sex;
}
var student=new Student('minmin',18,'female');
apply的实现
第二个参数是数组,那么不会有无限个参数,也就是说要么有第二个参数,要么没有。
function person(a,b,c,d){
return{
name:this.name,
a:a,b:b,c:c,d:d
}
}
var egg={name:'mm'}
Function.prototype.newApply=function(obj,arr){
var obj=obj || window;
var res;
obj.fn=this;
if(!arr){
res=obj.fn();
}else{
var newArguments=[];
for(var i=0;i<arr.length;i++){
newArguments.push('arr['+i+']');
}
res=eval('obj.fn('+newArguments+')');
}
delete obj.fn;
return res;
}
var o=person.newApply(egg,['1','2','3','4']);
console.log(o);
function person(a,b,c,d){
return{
name:this.name,
a:a,b:b,c:c,d:d
}
}
var egg={name:'mm'}
Function.prototype.newApply=function(obj,arr){
var obj=obj || window;
var res;
obj.fn=this;
if(!arr){
res=obj.fn();
}else{
res=obj.fn(...arr)
}
delete obj.fn;
return res;
}
var o=person.newApply(egg,['1','2','3','4']);
console.log(o);
function person(a,b,c,d){
return{
name:this.name,
a:a,b:b,c:c,d:d
}
}
var egg={name:'mm'}
Function.prototype.newApply=function(obj){
var obj=obj || window;
var res;
obj.fn=this;
if(!arguments[1]){
res=obj.fn();
}else{
res=obj.fn(...arguments[1])
}
delete obj.fn;
return res;
}
var o=person.newApply(egg,['1','2','3','4']);
console.log(o);
bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
返回的函数里面其实就是执行call,apply的功能,this指向的是person函数,现在考虑参数问题,同样不需要第一个参数,只需要后面的参数,需要进行切割。此时arguments其实是对象,不是数组。
需要利用call方法把slice切割方法赋值给arguments对象, 这样就可以切割掉第一个数组元素
function person(a,b,c,d){
console.log(this.name);
console.log(a,b,c,d);
}
var egg={name:'mm'}
Function.prototype.newBind=function(obj){
console.log(arguments);// 包含第一个括号的参数
// 因为函数里面有返回的函数,在执行中很容易造成this的丢失
// 所以要提前进行this的保存
var that=this;
var arr1=Array.prototype.slice.call(arguments,1);
return function(){
console.log(arguments);// 包含第二个括号的参数
var arr2=Array.prototype.slice.call(arguments);
var sumArr=arr1.concat(arr2);
that.apply(obj,sumArr);
}
}
person.newBind(egg,'1','2')('3','4');
生成新函数
// 手动指定作用域
传入参数
新函数被调用时,执行传入的函数(手动指定作用域)
Function.prototype.newBind = function(obj){
// 生成一个新的函数,赋值为调用bind的函数
const fn=this;
// 获得运行bind过程中的参数
let args=[...arguments].slice(1);
// 定义返回(绑定)函数
const binded=function(){
fn.apply(obj,args.concat([...arguments]));
}
return binded;
}
如果返回的bind绑定函数是一个构造函数
bind的作用域不是传入对象obj的作用域,构造函数的作用域会在new关键字的作用下,被赋值到新生成的实例上。
Function.prototype.newBind = function(obj){
// 生成一个新的函数,赋值为调用bind的函数
const fn=this;
// 获得运行bind过程中的参数
let args=[...arguments].slice(1);
// 定义返回(绑定)函数
const binded=function(){
// 判断this是否是binded的实例
if(this instanceof binded){
// 用new来调用
fn.apply(this,args.concat([...arguments]));
}
fn.apply(obj,args.concat([...arguments]));
}
return binded;
}
例子
var obj={
name:'mm';
}
function person(){
this.age=age;
}
const newPerson=person.newBind(null,18);
const stuff=new newPerson();
// stuff.age 18
在生成新的构造函数的时候,如果处在原型链上,新的构造函数要继承旧的构造函数的原型。
Function.prototype.newBind = function(obj){
// 生成一个新的函数,赋值为调用bind的函数
const fn=this;
// 获得运行bind过程中的参数
let args=[...arguments].slice(1);
// 定义返回(绑定)函数
const binded=function(){
// 判断this是否是binded的实例
if(this instanceof binded){
// 用new来调用
fn.apply(this,args.concat([...arguments]));
}
fn.apply(obj,args.concat([...arguments]));
}
binded.prototype=this.prototype;
return binded;
}
const newPerson=person.newBind(null,18);
const stuff=new newPerson();
在newPerson.prototype进行更改的时候,不希望更改person
Function.prototype.newBind = function(obj){
// 生成一个新的函数,赋值为调用bind的函数
const fn=this;
// 获得运行bind过程中的参数
let args=[...arguments].slice(1);
// 定义返回(绑定)函数
let emptyFun=function(){};
const binded=function(){
// 判断this是否是binded的实例
if(this instanceof binded){
// 用new来调用
fn.apply(this,args.concat([...arguments]));
}
fn.apply(obj,args.concat([...arguments]));
}
emptyFun.prototype=this.prototype
binded.prototype=new emptyFun();
return binded;
}
总结
介绍:
每个函数都包含两个非继承而来的方法:call()和apply();
call与apply都属于Function.prototype的一个方法,所以每个function实例都有call、apply属性;
1.相同点:改变this的指向。
2.区别:传值的方式不一样。
call(obj,arg1,arg2,arg3) 通过调用call 方法,在传第一个参数的时候,函数里的this指向第一个参数。从第二参数起依次传入给函数的参数值。需要把实参按照形参的个数传进去。
apply(obj,args)调用 apply方法,第一个参数同call一样,是函数里this指向第一个参数,第二个参数为数组。此方法会遍历传入参数的值。
call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,因此后面还需要()来进行调用才可以。