JS模拟实现函数call、apply、bind方法 arguments

1. 用js(模拟)实现apply、call、bind

因为它原生是用c++

接下来外面来实现一下apply、call、bind函数:

  • 注意:我们的实现是练习函数、this、调用关系,不会过度考虑一些边界情况

1.1 实现call

1.1.1 在Function的原型上定义hycall

在Function的原型上添加一个属性hycall,这样通过声明函数或者使用new Function的形式创建的函数都有这个方法

通过函数实例.方法名(),这个方法内部绑定的this是指向这个函数实例的(函数也属于是一个对象,隐式绑定的一种)。所以我们只要用一个变量fn在hycall方法内部接受this。然后通过fn()来调用这个函数。

//给所有的函数添加一个hycall的方法
Function.prototype.hycall=function(){
  var fn=this;
  fn()
}

function foo(){
  console.log("foo函数被执行");
}


function sum(){
  console.log("sum函数被调用了");
}
foo.hycall()
sum.hycall()


1.1.2 在hycall尝试绑定改变函数内部的this
  • 基本数据类型是不能直接在本身添加属性和方法的,所以需要将其转化为对象类型。使用Object(参数)(基本数据类型本身可以添加属性,但是这添加属性期间,会先将基本数据类型转化为临时的包装类,然后在临时的包装类添加属性,执行当前语句后,临时包装类就会被销毁。所以会在后期产生一个错觉,误以为基本数据类型是可以添加属性的,但是如果你去用基本数据类型去.的时候,发现会报错)
  • 但是由于使用Object(null)、Object(undefined)最后都会转化为空对象,而实际函数.call传入null或undefined,是要将this指向window(全局对象)
  • 所以需要先判断是否为真(不是null、undefined),为真,则使用Object(thisArg),为假则赋值为window
//给所有的函数添加一个hycall的方法
Function.prototype.hycall=function(thisArg){
  var fn=this;
  //null、undefined要指向全局对象
  thisArg=thisArg?Object(thisArg):window
  // 在这个对象上定义一个新属性指向这个函数
  // 然后通过这个对象调用这个函数,这个函数this会隐式绑定到这个对象上
  thisArg.fn=fn;
  thisArg.fn()
}

function foo(){
  console.log("foo函数被执行",this);
}


function sum(){
  console.log("sum函数被调用了",this);
}
foo.hycall("aaa")
sum.hycall(null)
1.1.3 实现参数列表
1.1.3.1 剩余参数 (...参数名)

在hycall的函数的第二个参数使用的是ES6的剩余参数

语法

...参数名 

这个参数是一个数组,将后面传过来的、多余的全都放入到这个数组中

function sum(...nums){
  console.log(nums);
}
sum(1) //[ 1 ]
sum(1,2) // [ 1, 2 ]
sum(1,2,3) //[ 1, 2, 3 ]
sum(1,2,3,4)  // [ 1, 2, 3, 4 ]
1.1.3.2 展开运算符 (...变量名)
var a=[1,2,3,4];
console.log(...a); //1 2 3 4
1.1.3.3 实现参数列表
//给所有的函数添加一个hycall的方法
Function.prototype.hycall=function(thisArg,...args){
  var fn=this;
  //null、undefined要指向全局对象
  thisArg=thisArg?Object(thisArg):window
  // 在这个对象上定义一个新属性指向这个函数
  // 然后通过这个对象调用这个函数,这个函数this会隐式绑定到这个对象上
  thisArg.fn=fn;
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}

function foo(){
  console.log("foo函数被执行",this);
}


function sum(num1,num2){
  console.log("sum函数被调用了",this,num1,num2);
  return num1+num2;
}
foo.hycall("aaa")
console.log(sum.hycall('aa',10,20));


1.2 实现apply

1.2.1 在Function.prototype定义hyapply

在Function.prototype定义的属性或方法,后面声明函数或使用new关键字创建函数时,创建的实例也会有这个属性和方法。

Function.prototype.hyapply=function(){

}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
1.2.2 进行this的绑定
Function.prototype.hyapply=function(thisArg){
  var fn=this;
  thisArg=thisArg?Object(thisArg):window;
  thisArg.fn=fn;
  thisArg.fn()

}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
sum.hyapply("aaa")
1.2.3 实现数组参数
Function.prototype.hyapply=function(thisArg,args){
  var fn=this;
  thisArg=thisArg?Object(thisArg):window;
  thisArg.fn=fn;
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
console.log(sum.hyapply("aa",[10,20]));
  • 这里还是存在一些问题,比如说 我们没有传第二个参数,再去使用展开运算符,可能会报错
1.2.3.1 解决办法1 (设置默认值)
Function.prototype.hyapply=function(thisArg,args=[]){
  var fn=this;
  thisArg=thisArg?Object(thisArg):window;
  thisArg.fn=fn;
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
console.log(sum.hyapply("aa",[10,20]));

function bar(){
  console.log("调用了bar函数");
}
bar.hyapply()
1.2.3.2 解决办法2 (if-else)
Function.prototype.hyapply=function(thisArg,args){
  var fn=this;
  thisArg=thisArg?Object(thisArg):window;
  thisArg.fn=fn;
  var result;
  if(!args){
    result=thisArg.fn();
  }else{
    result=thisArg.fn(...args)
  }
  delete thisArg.fn;
  return result;
}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
console.log(sum.hyapply("aa",[10,20]));

function bar(){
  console.log("调用了bar函数");
}
bar.hyapply()
1.2.3.3 解决方法3 (三元表达式)

args=args?args:[];

Function.prototype.hyapply=function(thisArg,args){
  var fn=this;
  thisArg=thisArg?Object(thisArg):window;
  thisArg.fn=fn;
  args=args?args:[];
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
console.log(sum.hyapply("aa",[10,20]));

function bar(){
  console.log("调用了bar函数");
}
bar.hyapply()
1.2.3.4 解决办法4 (逻辑或)
Function.prototype.hyapply=function(thisArg,args){
  var fn=this;
  thisArg=thisArg?Object(thisArg):window;
  thisArg.fn=fn;
  args=args|| [];
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
console.log(sum.hyapply("aa",[10,20]));

function bar(){
  console.log("调用了bar函数");
}
bar.hyapply()

1.3 实现call和apply方法的修改

  • 前面的实现存在两个问题 :0、"",this也会指向window,这是不对的,所以需要进行修改
1.3.1 call方法的修改
//给所有的函数添加一个hycall的方法
Function.prototype.hycall=function(thisArg,...args){
  var fn=this;
  //null、undefined要指向全局对象
  thisArg=thisArg!==null&&thisArg!==undefined?Object(thisArg):window
  // 在这个对象上定义一个新属性指向这个函数
  // 然后通过这个对象调用这个函数,这个函数this会隐式绑定到这个对象上
  thisArg.fn=fn;
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}

function foo(){
  console.log("foo函数被执行",this);
}


function sum(num1,num2){
  console.log("sum函数被调用了",this,num1,num2);
  return num1+num2;
}
foo.hycall("aaa")
console.log(sum.hycall('aa',10,20));


1.3.2 apply方法的修改
Function.prototype.hyapply=function(thisArg,args){
  var fn=this;
  thisArg=thisArg!==null&&thisArg!==undefined?Object(thisArg):window;
  thisArg.fn=fn;
  args=args|| [];
  var result=thisArg.fn(...args)
  delete thisArg.fn;
  return result;
}
function sum(num1,num2){
  console.log("sum",this,num1,num2);
  return num1+num2;
}

// * 系统调用
// sum.apply("aa",[10,20])
console.log(sum.hyapply("aa",[10,20]));

function bar(){
  console.log("调用了bar函数",this);
}
bar.hyapply(0)

1.4 实现bind

function foo(){
  console.log("foo函数被执行了",this);
}
function sum(num1,num2,num3,num4){
  console.log(num1,num2,num3,num4);
}
// * 系统调用
// var bar=foo.bind("aaa")
// bar()

//* bind的时候,传入参数
// var newSum=sum.bind("bbb",10,20,30,40);
// newSum()

// * bind的时候 不传入参数
// var newSum=sum.bind("ccc");
// newSum(50,60,70,80);

//* bind的时候传入部分参数,在调用的时候,传入剩余参数
var newSum=sum.bind("ddd",10);
newSum(70,90,100)
1.4.1 在Function.prototype添加一个属性为hybind赋值为一个函数
Function.prototype.hybind=function(){
 
 
}
function foo(){
  console.log("foo函数被执行了",this);
}
function sum(num1,num2,num3,num4){
  console.log(num1,num2,num3,num4);
}

1.4.2 绑定this并返回一个新的函数
Function.prototype.hybind=function(thisArg){
  var fn=this;
  return function proxyFn(){
    thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window
    thisArg.fn=fn;
    var result=thisArg.fn();
    delete thisArg.fn;
    return result;
  }
 
}
function foo(){
  console.log("foo函数被执行了",this);
}
function sum(num1,num2,num3,num4){
  console.log(num1,num2,num3,num4);
}

1.4.3 实现参数
  • 在实现绑定的时候可能需要传入参数,在调用的时候可能也会传入参数
  • 所以要对两个参数,最后进行调用的时候,需要参数合并
Function.prototype.hybind=function(thisArg,...args){
  var fn=this;
  return function proxyFn(...args2){
    thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window
    thisArg.fn=fn;
    var result=thisArg.fn(...args,...args2);
    delete thisArg.fn;
    return result;
  }
 
}
function foo(){
  console.log("foo函数被执行了",this);
}
function sum(num1,num2,num3,num4){
  console.log(num1,num2,num3,num4);
}

2. arguments

arguments是一个对应于 传递给函数的参数类数组(array-like) 对象

它会将传递给函数的所有参数放在一个类数组中(但实际上是一个对象),它是放在AO对象中的

  • array-like表示像一个数组,但实际上是一个对象
  • 但是它拥有数组的一些特性,例如:length,比如可以通过索引来获取
  • 但是却没有数组的一些方法,例如forEach、map

2.1 arguments的三个常见操作

2.1.1 获取参数个数
arguments.length
2.1.2 通过索引获取指定的参数
arguments[0]
arguments[1]
2.1.3 通过callee获取arguments所在的函数
arguments.callee

2.2 arguments参数遍历

2.2.1 自身遍历

arguments自己遍历

function foo(num1,num2){
  let arr=[];
  console.log(arguments);
  for(let i=0;i<arguments.length;i++){
    arr.push(arguments[i]*10)
  }
  console.log(arr);
}
foo(1,2,3,5,4,56)
2.2.2 通过Array.slice将arguments转化为数组
 //*通过Array.slice将 arguments转化为数组
  var newArr=Array.prototype.slice.call(arguments);
  var newArr2=[].slice.call(arguments)
  console.log(newArr);
  console.log(newArr2);
2.2.3 通过Array.from和展开运算符将arguments转化为数组
 var newArr3=Array.from(arguments);
  var newArr4=[...arguments]
  console.log("newArr3:",newArr3);
  console.log("newArr4:",newArr4);
}

2.3 实现Array.slice数组

可能存在一些没有考虑到的问题

Array.prototype.hyslice=function(start,end){
  let newArray=[];
  var arr=this;
  for(let i=start;i<end;i++){
    newArray.push(arr[i])
  }
  return newArray;
}
var newArr=Array.prototype.hyslice.call(["111","222","333"],0,2)
console.log(newArr);

2.3.1 优化1
Array.prototype.hyslice=function(start,end){
  let newArray=[];
  var arr=this;
  start=start?start:0;
  end=end?end:arr.length;

  for(let i=start;i<end;i++){
    newArray.push(arr[i])
  }
  return newArray;
}

var newArr=Array.prototype.hyslice.call(["111","222","333"])
console.log(newArr);

2.4 箭头函数没有arguments

箭头函数没有arguments,它会上层作用域去找,如果还是没找到,会一直往上层作用域找,直到全局作用域,如果全局作用域还是没找到,会抛出一个错误。

  • 全局环境window没有arguments,全局环境node有arguments
  • 其实不太建议arguments,推荐使用剩余参数
function foo(){
  var bar=()=>{
    console.log(arguments);
  }
  return bar;
}
var fn=foo("123");
fn()
var name="abc";
var foo=()=>{
  console.log(arguments); //会去上层作用域找arguments
  //* 全局环境window没有arguments、全局node环境下有arguments
}
foo()

3.总结

实现函数call、apply、bind.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容