我们在使用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方法:
- 语法:apply([thisObj[,argArray]])
- 定义:应用某一对象的一个方法,用另一个对象替换当前对象。 说明:apply的第一个参数thisObj和call方法的一样,第二个参数argArray为一个传参数组。thisObj如果未传,那么 Global 对象被用作 thisObj。
- call方法:
- 语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
- 定义:调用一个对象的一个方法,以另一个对象替换当前对象。 说明: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()中相同的部分外,还存在以下不同之处:
- 没有显示的创建对象;
- 直接将属性和方法赋给了this对象;
- 没有return语句。
注意: 通过new实例化的对象,我们就可以明确知道了其类型,
要创建Person的新实例,必须使用new操作符,那么new的过程中都经历了那几个步骤呢:
- 创建一个新对象;
- 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象;
我们尝试自己用代码来实现一下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)复制代码
不知道大家看明白了没有,欢迎留言交流~~~~
码字不易,如果对你有帮助的话,不忙点个赞再走~~~~~