必须让你一看就能明白系列之———JavaScript 中 new 实例化对象的实现原理?

我们在使用new操作符创建新实例的时候,其内部也就那么四个步骤,网上很多文章也把这四步都讲清楚了,并且代码也贴出来了,但是还是有朋友们看不懂,我就想着怎么让朋友们能一看就能明白。

先贴出一个网上的例子:

function New(func) { 
    var res = {}; 
    if (func.prototype !== null) { 
        res.__proto__ = func.prototype; 
    } 
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1)); 
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { 
        return ret; 
    } 
    return res; 
} 
var obj = New(A, 1, 2); 
// equals to 
var obj = new A(1, 2);复制代码

是不是看的不是很明白呢??别着急,我带你一点点深入!!


这里要看明白,首先需要认识apply,说到apply,还有一个call,他们就是同父异母的兄弟,还有人会说这个 Array.prototype.slice.call这个是什么鬼?


那好吧,我们就先做点铺垫工作:
apply()、call()都是函数对象的一个方法,他们的作用都是改变函数的调用对象,也可以说改变this指向,先来看一下apply、call的用法吧,其原理我也会在本篇文章后面实现。
  • apply方法:
  1. 语法:apply([thisObj[,argArray]])
  2. 定义:应用某一对象的一个方法,用另一个对象替换当前对象。 说明:apply的第一个参数thisObj和call方法的一样,第二个参数argArray为一个传参数组。thisObj如果未传,那么 Global 对象被用作 thisObj。
  • call方法:
  1. 语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
  2. 定义:调用一个对象的一个方法,以另一个对象替换当前对象。 说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。 arg1 … argN为被调用方法的传参。
文字有时候便于理解,但是有时候不如代码来的实在,其实最好的方式就是将文字和代码结合起来一点点理解消化,也就能深刻记住了。
来看代码吧:

var name = 'globalName'; //定义一个全局name

var obj = {
	name: 'objName'
}

var foo = {
	name: 'fooName',
	getName: function() {
		return this.name;
	}
}

console.log(foo.getName())             //  fooName
console.log(foo.getName.apply(obj));   //  objName
console.log(foo.getName.apply())       //  globalName
console.log(foo.getName.apply(window)) //  globalName

console.log(foo.getName())             //  fooName
console.log(foo.getName.call(obj));    //  objName
console.log(foo.getName.call())        //  globalName
console.log(foo.getName.call(window))  //  globalName
复制代码

 foo.getName()  
 //这里foo调用自己的方法,返回自身name属性值fooName,如果大家对于this指向还不清楚,请自行补课复制代码

foo.getName.apply(obj) 
//这里通过使用apply方法切换getName函数执行的上下文环境,将this指向了obj,所以输出了objName,有一种借壳生蛋的作用复制代码

foo.getName.apply()
//这里在调用apply并没有传入需要指向的参数,默认全局window对象复制代码

foo.getName.apply(window)
//这里显示的传入window对象,将this指向了window,输出了globalName复制代码

相信大家已经明白了apply的用法,call也是同样的道理,这里我们只用到了apply和call方法的第一个参数,我们再看看他们第一个参数后面的参数怎么回事?

通过apply和call实现数组追加:

var arr1 = [1,2,3,4];
Array.prototype.push.apply(arr1, [5,6,7]);  //调用数组原型上的push方法,相当于是arr1借用了push方法实现尾部追加元素,第二个元素是以数组形式
console.log(arr1)  //[1, 2, 3, 4, 5, 6, 7]复制代码

var arr1 = [1,2,3,4];
Array.prototype.push.call(arr1,5,6,7); //而call方法是已参数列表形式传入追加的元素
console.log(arr1)  //[1, 2, 3, 4, 5, 6, 7]复制代码

以上就是apply和call的使用方法和区别了,要想更好的理解调用new实例化对象具体做了什么,我们还需要了解一下JavaScript创建对象的几种方式,通过比对我们来发现其中的原理。
  • 工厂模式

function createPerson(name,age,job) {    
    var obj = new Object(); //创建一个对象    
    obj.name = name; //给对象添加属性 和方法    
    obj.age = age;    
    obj.job = job;    
    obj.sayName = function() {        
        console.log(this.name)    
    }    
    return obj; //返回这个对象
}

var person1 = createPerson("lili", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

console.log(person1 instanceof createPerson)  //false
console.log(person2 instanceof createPerson)  //false复制代码

优点:解决了创建多个对象的时候的重复代码问题。

缺点:不能解决对象识别问题,也就是不知道一个对象的类型,上面的instanceof 说明问题。


ECMAScript中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在运行环境中,我们可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。下面就是使用构造函数模式将前面的例子重写如下:

  • 构造函数模式

function Person(name,age,job) {    
    this.name = name;    
    this.age = age;    
    this.job = job;    
    this.sayName = function () {        
        console.log(this.name);    
    }
}
var person1 = new Person("lili", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1 instanceof Person); // true 这里可以判断其属于Person类型的实例对象了
console.log(person2 instanceof Person); // true复制代码

我们可以注意到,Person()中的代码除了与createPerson()中相同的部分外,还存在以下不同之处:

  1. 没有显示的创建对象;
  2. 直接将属性和方法赋给了this对象;
  3. 没有return语句。

注意: 通过new实例化的对象,我们就可以明确知道了其类型,

要创建Person的新实例,必须使用new操作符,那么new的过程中都经历了那几个步骤呢:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象;


我们尝试自己用代码来实现一下new过程吧!!!

function New(Person,name,age,job) { //Person是上面那个构造函数   
    //1.创建一个对象,    
    var obj = {};    
    //2.将构造函数的作用域赋给新对象,因此this就指向了这个新对象,这里我们将obj的__proto__指向了Person的prototype,因为通用new出来的实例的__proto__属性都指向构造函数的原型(prototype) 
    obj.__proto__ = Person.prototype;
    //执行构造函数Person中的代码,这里通过apply将作用域切换为当前obj,这里的arguments是New方法传入的参数,通过slice去掉第一个参数,传入剩下的参数,    
    var ret = Person.apply(obj,Array.prototype.slice.call(arguments,1));
    // 如果ret是对象或者是函数,就返回,如果不是就返回obj;
    if((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {        
        return ret;    
    }
    return obj;
}
var o = New(Person,'jiji',1,'mother');
console.log(o)复制代码

不知道大家看明白了没有,欢迎留言交流~~~~

码字不易,如果对你有帮助的话,不忙点个赞再走~~~~~




转载于:https://juejin.im/post/5c9f224bf265da307352c9f6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值