必须让你一看就能明白系列之———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);复制代码

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


这里要看明白,首先须要认识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方法的第一个参数,咱们再看看他们第一个参数后面的参数怎么回事?app

经过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复制代码

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

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


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

  • 构造函数模式

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()中相同的部分外,还存在如下不一样之处:prototype

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

注意: 经过new实例化的对象,咱们就能够明确知道了其类型,code

要建立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)复制代码

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

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

相关文章
相关标签/搜索