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()
网友评论