JavaScript 是面向对象的,在 JavaScript 中我们可以通过多种手法来定义一个对象。
基本手法
首先你可以使用对象字面量“{}”,将这个字面量赋值给一个变量标识符后,就会生成一个对象,并且每次赋值都是一个新的对象,这也就意味着前后两次赋值的结果是不相等的,如
let a = {};
let b = {};
a === b; // false
当前你也可以给对象加上初始值
let a = {
value: '我是值'
};
通过上面的方式创建的对象即使不赋初始属性值,也是有一些属性在的,比如
如图可以看到“空对象” b 上有一个属性 proto,这是我们以后将要讨论的关键这里先不说。
如果你想要一个真正的空对象,可以借助 Object.create() 函数
let d = Object.create(null); // 试试看吧
这时候你会不会好奇,之前通过对象字面量创建时,多出来的那些属性是哪里来的呢?接下来揭晓答案
初识 new
JavaScript 中,所有符合对象定义的数据,最终的上层都可以溯源到 Object,换句话说所有对象的顶层都是 Object。在 JavaScript 中提供了一个关键字 new,你可以通过 new 关键字显式的调用 Object 函数来创建一个对象。
let e = new Object()
创建出来的结果如下图
对比发现,和通过对象字面量创建效果是一致的,并且可以猜测通过 new 调用 Object 每次创建的对象也是全新的不一样的对象
那么 new 关键字到底做了什么呢?
认识 new
ECMA262 标准中,对于 new 操作符的定义如下
通过图中可以看到 new 在使用时的语法是 new NewExpression 和 new MemberExpression Arguments,所以你可以这么使用
let a = new Object;
let b = new Object({a:1});
new 操作符做的事情是:
- 语句执行时,创建一个新的空对象并且将该对象的 proto 属性链接到构造函数的 prototype
- 将新创建的对象作为构造函数的 this 指向,然后调用该构造函数
- 如果构造函数内部 return 一个新的对象,则返回该对象,否则返回这个创建的新对象
知道了上述步骤,现在来验证一下:
function demo1(){
this.a = 1;
let b={b:1};
return b;
}
function demo2(){
this.a = 1;
}
let d1 = new demo1();
let d2 = new demo2();
运行上面的代码后,将得到如下的打印结果
可以看到,因为 demo1 函数中“return”了一个通过对象字面量创建的对象,所以最终得到的 d1 对象的 _proto _属性的 constructor 指向了 Object,而不是指向 demo1,并且这里 this.a 似乎也没有起效。
而观察对象 d2 可以看到,d2 对象的 _proto _属性的 constructor 指向了 demo2,并且可以看到 this.a 起效了。这就验证了前面说到的三段步骤。
你看,由于 demo1 内部有 return 一个新对象,new 操作符帮助我们创建的新对象没有用上,但是在执行构造函数的时候,又偏偏让函数执行时的上下文 this 指向了新的对象,所以 demo1 返回的结果上没有 a,而 demo2 没有 return 时是符合预期的。
因此作为构造函数的函数,返回值必定不是对象。
扩展
我们知道,在函数里不写 return 相当于 return undefined ,所以又能够联想到 new 构造函数时,如果返回的结果不是一个对象会被忽略而返回 new 帮我们创建的变量,也正因此 String 这样的函数才既可以用来做构造函数,又可以用来作为类型转换函数。
有些时候我们定义的函数出于某种目的会强制要求必须通过 new 调用,比如像 Promise,那么怎么来做这样的检查呢?重新思考之前说的 new 的执行步骤。
new 在执行构造函数前,我们已经创建了一个新对象然后还让新对象的 _proto _属性指向了构造函数的 prototype(这是一个只有函数才拥有的属性,打印这个属性你就会发现它也是一个对象,其上有属性 constructor 和 proto ),在这之后将构造函数的 this 指向了新对象然后才执行构造函数。这也就意味着,我们可以在构造函数中通过 this 拿到这个新对象,然后去判断这个对象上的 constructor 是不是当前在执行的构造函数即可,如下所示:
function demo3(){
if(this.constructor !== demo3){
throw new Error('本函数必须作为构造函数使用');
}
this.a = 3;
}
你可以试试。
模拟 new
前面讲了 new 的执行过程,由于在 JavaScript 中 new 是一个操作符,我们是没法通过模拟的方式来真正的实现一个操作符的,但是我们可以通过实现一个函数来模拟 操作符的表现,如下所示:
// 模拟函数
function newFunc(fn){
let newObj = Object.create(null);
newObj.__proto__ = fn.prototype;
return function(){
let rt = fn.apply(newObj, arguments);
return !rt || typeof rt !== 'object' ? newObj: rt;
}
}
let demo6 = function(v1,v2,v3){
this.v1=v1;
this.v2=v2;
this.v3=v3;
}
let d6 = newFunc(demo6)(1,2,3)
注意,这里必须要说明的是,这只是在模拟操作,并没有说实现一个跟 new 一模一样的,这里 _proto _属性也是一个非标准化的属性,所以日常工作中不要使用 。
总结
- 创建对象的手法有:对象字面量“{}”;Object.create();new Constructor
- 在实现构造函数时,如果有预期需要通过 new 来调用,则不要在函数内部返回一个对象
- new 的执行过程上面说了,不再赘述,不清楚的再回头看一下
本篇讲了怎么创建对象,下一篇文章将会重点介绍对象上的 _proto 已经函数上的 _prototype 具体是什么。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!