javascript模拟new的实现

本文详细解析了JavaScript中new操作符的工作原理,包括创建对象、this绑定、原型链继承及返回值处理,揭示了new操作符如何实例化对象并正确处理方法的返回值。

在日常使用new时,我们很清楚它的作用。

准备工作

我们先创建一个Person类,他接受两个参数name姓名和age年龄:

function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayHello = function(){
    console.log('我叫' + this.name + ', 今年' + this.age + '岁了');
}
复制代码

new 的使用

我们先用new实例化一个person,并打印出来,看看结构。

var person = new Person('小明', 20);
console.log(person); // Person {name: "小明", age: 20}
person.sayHello(); // 我叫小明, 今年20岁了
复制代码

最终person的结果是一个object

模拟过程

结合Person方法,我们容易发现,仿佛有一个object替代了this的位置,执行了赋值操作,输出了最后的结果。

1、替代this赋值

思考替代过程:

    // 1、创建了一个对象
    var result = {}; 
    
    // 2、对象代替了this的位置,执行了赋值
    {
        result.name = "小明";
        result.age = 20;   
    } 
    
    // 3、输出 {name: "小明", age: 20}
    return result; 
复制代码

那么问题来了,我该如何将这个result替代this的位置呢?

这就用到了call或者apply:

    var result = {};
    Person.call(result, '小明', 20);
    result; // {name: "小明", age: 20}
复制代码

这样我们就完成了第一步,你可以在console控制台中尝试一下!

接下来就是处理原型部分了。

2、原型移植

这就很简单了,我们有很多办法:

//方案1
result.__proto__ = Person.prototype; //有一定副作用(可枚举)
//方案2
Object.setPrototypeOf(result, Person.prototype);
//方案3
result = Object.create(Person.prototype);
复制代码
3、初步结果

综上我们容易整理出这样的结果:

    function likeNew(fn){
        //我们先完成原型移植,以免构造函数中调用了原型方法。
        var result = Object.create(fn.prototype);
        var args = [].slice.call(arguments,1);
        fn.apply(result, args);
        return result
    }
    var person = likeNew(Person, '小明', 20); 
    console.log(person); // Person {name: "小明", age: 20}
    person.sayHello(); // 我叫小明, 今年20岁了
复制代码

虽然我们按照思路是先创建对象->执行->处理原型。
但是实际上正确的顺序是 创建包含对应原型的对象->执行。

问题

如果被实例化的方法如果本身包含返回值,new的结果会是什么呢?

    function Person(name, age){
        this.name = name;
        this.age = age;
        return name
    }
    var person = new Person('张三', 20);
    console.log(person); // ?
    var person1 = new Person(['张三'], 20);
    console.log(person); // ?
复制代码

通过尝试,输出结果分别为Person {name: '张三', age: 20}['张三']
为什么会产生完全不同的结果呢?
猜想:方法返回值的类型决定实例化后的结果

1、基本类型

js中的基本类型有number、string、boolean、undefined、null、symbol(es6)共6种。

    function Test(value){
        this.value = value;
        return value;
    }
    //number
    var number = new Test(123);
    console.log(number); // Test {value: 123}
    
    //string
    var string = new Test('abc');
    console.log(string); // Test {value: 'abc'}
    
    //boolean
    var boolean = new Test(true);
    console.log(boolean); // Test {value: true}
    
    //undefined
    var Undefined = new Test();
    console.log(Undefined); // Test {value: undefined}
    
    //null
    var Null = new Test(null);
    console.log(Null); // Test {value: null}
    
    //symbol
    var symbol = new Test(Symbol('key'));
    console.log(symbol); // Test {value: Symbol(key)}
复制代码

上述例子所有返回值均为实例化后的对象,由此可见,所有基本类型返回的都是正常的。

2、引用类型

js中的引用类型有 object、array、function。我们接着上面的Test类继续创建对象:

    //object
    var object = new Test({}});
    console.log(object); // {}
    
    //array
    var array = new Test([]);
    console.log(array); // []
    
    //function
    var functions = new Test(function(){});
    console.log(functions); // function(){}
    
    //特殊的number
    var number = new Test(new Number(1));
    console.log(number); // Number {1}
    console.log(typeof number); // object。
复制代码

可见,方法的返回值若为引用类型,new操作符就“失效”了。
那他真的失效了吗?让我们看看方法内部执行的过程:

    function Test(value){
        this.value = value;
        console.log(this);
        return [1,2,3];
    }
    var result = new Test(1); // Test {value: 1}
    console.log(result);// [1, 2, 3]
复制代码

由此可见,this在Test的实例化过程中,确实被创建了,只不过由于Test本身的返回值为引用类型,所以实例化后的结果被其替换了。

最后的整理

根据以上的推论,再次完善了likeNew:

    function likeNew(fn){
        var result = Object.create(fn.prototype);
        var args = [].slice.call(arguments,1);
        var fnResult = fn.apply(result, args);
        if(typeof fnResult === 'object' || typeof fnResult === 'function' && fnResult !== null){
            return fnResult
        }
        return result
    }
    
    var person = likeNew(Person, '小明', 20); 
    console.log(person); // Person {name: "小明", age: 20}
    person.sayHello(); // 我叫小明, 今年20岁了
    
    //原始类型 number
    var number = likeNew(Test, 1);
    console.log(number); // Test {value: 1}
    
    var Null = likeNew(Test, null);
    console.log(Null); // Test {value: null}
    
    //引用类型
    var object = likeNew(Test, {});
    console.log(object); // {}
    
    var numberObject = likeNew(Test, new Number(1));
    console.log(numberObject); // Number{1}
复制代码

总结:
1、new操作符在进行实例化时,首先会创建一个包含指定__proto__的对象,再带入方法中执行,并选择性输出此对象。
2、被操作的方法的返回值若为引用类型,则会替换原本实例化的结果。

以上如有不当请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值