手写call

96 阅读2分钟

call() :在使用一个指定this值 和 若干个指定参数值 的前提下,调用某个函数或方法。

//例1
let foo = {
    value: 1
}

function bar() {
    console.log(this.value)
}

bar.call(foo)//1

/*  注意
    1.call改变了this的指向,指向了foo;
    2.bar函数执行了 
*/

例1改写如下

//例2
let foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
}

foo.bar()//1

/* 
    这个时候 this 就指向foo,但这样却给 foo 对象本身添加了一个属性,所以们用 delete 再删除它即可。
    
    所以模拟call的步骤为:
    1.将函数设为对象属性;
    2.执行该函数;
    3.删除该函数;

    所以代码为:
    第一步-------fn是对象的属性名,起什么都可以,后面会删除它
        foo.fn = bar
    第二步
        foo.fn()
    第三步
        delete foo.fn
 */

手写call 第一版--指定this

/* 写在原型上目的是为了让所有实例对象都可以调用 */
Function.prototype.call2 = function(context) {
    //第一步
    context.fn = this;
    //第二步
    context.fn();
    //第三步
    delete context.fn;
}

//测试一下
let foo = {
    value:1
}

function bar() {
    console.log(this.value)
}

bar.call2(foo);//1

手写call 第二版--指定参数

//例1
var foo={
    value:1
}

function bar(name, age){
    console.log(name);
    console.log(age);
    console.log(this.value)
}
bar.foo('jinjin', 12);//'jinjin',12,1

//改造call2
Function.prototype.call2 = function(context) {
    context.fn = this;
    
    let arg = [...arguments].slice(1);//截取除了第一个参数的后面参数
    context.fn(...arg);//展开数组

    delete context.fn;
}

//测试一下
let foo = {
    value: 1
}

function bar(name, age){    
    console.log(name);
    console.log(age);
    console.log(this.value);
}

bar.call(foo, 'jinjin', 12)//'jinjin',12,1
bar.call2(foo, 'jinjin', 12)//'jinjin',12,1

手写call 第三版 this为null 和 实现返回值

//例1 传入的参数this(函数)可以是null。 当为null时,视为指向window
var value =  1function bar(){
    console.log(this.value);
}

bar.foo(null);//1

//例2 针对函数 可以实现返回值
var obj={
    value:1
}

function bar(name, age){
    return {
        value:this.value,
        name:name,
        age:age,
    }
}

console.log(bar.call(obj, 'jinjin', 12))//Object{value:1,name:'jintig',age:12}

//改造call2
Function.prototype.call2 = function(context){
    var context = context||window;//1.this为null 也可以写为=> context ?? window  或者  context = context ?? window 
    context.fn =this;

    let args = [...arguments].slice(1);
    let result = context.fn(...args);

    delete context.fn;

    return result;//2.有返回值
}

//测试一下
var obj={
    value:1
}

function bar(name, age){
    return {
        value:this.value,
        name:name,
        age:age,
    }
}

console.log(bar.call(obj, 'jinjin', 12))
console.log(bar.call2(obj, 'jinjin', 12))//Object{value:1,name:'jinjin',age:12}

手写call 最终简化版--确保唯一函数名称

Function.prototype.call2 = function(context, ...args){
    // console.log(...args)
    // console.log(...arguments)
    if(typeof context==='undefind' || context==null){//等同于 var context = context||window;
        context= window
    } 

    let fnSymbol = Symbol();//每个从 Symbol() 返回的 symbol 值都是唯一的
    context[fnSymbol] = this;

    let result = context[fnSymbol](...args);//入参...args === [...arguments].slice(1);

    delete context[fnSymbol];//删除目的是不污染原来数据

    return result;
}

//测试一下
var obj={
    value:1
}

function bar(name, age){
    return {
        value:this.value,
        name:name,
        age:age,
    }
}

console.log(bar.call(obj, 'jinjin', 12))
console.log(bar.call2(obj, 'jinjin', 12))

手写apply

apply的实现与call类似,只是入参不一样,apply为数组

Function.prototype.apply2 = function(context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if(!arr) {
        result= context.fn();
    } else {
        result =context.fn(...arr);
    }

    delete context.fn;

    return result;
}

简化版

Function.prototype.apply2 = function(context, args) {
    if(typeof context ==='undefind' || context === null) {
        context = window
    }

    let fnSymbol = Symbol();
    let fn = context[fnSymbol](...args);

    delete context[fnSymbol];

    return fn;
}