面试官很忙,但我不单纯是蹭热点,今天聊的主题绝对是面试中命中率很高的知识点。我在复习javascript函数这块知识时,注意到一个有意思的点,就是构造函数显式return,并由此引起了一波头脑风暴......javascript
咱们知道,若是不作特殊处理,new构造函数时会发生下面这几步。前端
__proto__
属性指向构造函数的
prototype
属性
this
指向这个新对象
this
给新对象添加新的成员属性或方法。
下面咱们来验证下:java
function Test() {
console.log(JSON.stringify(this)); console.log(this.__proto__.constructor === Test); this.name = 'jack'; this.age = 18; console.log(JSON.stringify(this)); } var a = new Test(); // Chrome控制台会输出如下内容 // {} // true // {"name":"jack","age":18} 复制代码
这彻底符合咱们的认知,没毛病。web
那么在认识到new实例化过程的几个关键步骤后,咱们也能解答一道面试中常见的题目:如何实现一个new?面试
实现一个new也就意味着不能用new关键字,那么要完成这么一系列步骤,固然是经过函数实现了。app
// func是构造函数,...args是须要传给构造函数的参数
function myNew(func, ...args) { // 建立一个空对象,而且指定原型为func.prototype var obj = Object.create(func.prototype); // new构造函数时要执行函数,同时指定this func.call(obj, ...args); // 最后return这个对象 return obj; } 复制代码
以这四个关键步骤做为指导思想,咱们很快就写出了代码实现。从这一点我也能体会到思路的重要性,别当工具人,代码才是工具!编辑器
从实现逻辑上来看没什么问题,咱们来验证下。ide
function Test(name, age) {
this.name = name; this.age = age; } myNew(Test, '小明', 18); // Chrome控制台会输出如下内容 // Test {name: "小明", age: 18} 复制代码
所谓显式return,就是在构造函数中主动return一个对象,这里说的对象不只包括Object
,也包含Array
,Date
等对象哦。函数
咱们能够试一试:工具
function Test() {
this.name = 'jack'; this.age = 18; return { content: '我有freestyle' } } new Test(); // Chrome控制台会输出如下内容 // {content: "我有freestyle"} 复制代码
那么return一个普通类型数据有没有用呢?好比字符串,数字?试试便知。
function Test() {
this.name = 'jack'; this.age = 18; return '我有freestyle' } new Test(); // Chrome控制台会输出如下内容 // Test {name: "jack", age: 18} 复制代码
能够看到,当咱们return一个普通类型数据时,不会影响结果,依然会返回new出来的这个新对象。
咱们也应该知道,new构造函数就是为了建立对象,你return一个字符串之类的普通类型数据是没有任何意义的,因此咱们的关注点应该是return一个特殊的对象。请接着往下看。
所谓“无new实例化”,就是指不经过new关键字实例化对象(固然,这里说的不经过new,只是调用层面的,底层仍是用了new)。这一点咱们使用jQuery的时候已经体验过了。
// 实例化了一个jQuery对象,可是没有用到new
var ele = jQuery('<div>freestyle</div>'); 复制代码
那么这种黑科技是怎么实现的呢?
前面已经提到了,咱们能够在构造函数中经过显式return来返回一个自定义的对象,那么这里就有发挥的空间了。咱们经过一个简单的例子来感觉下:
function Shadow() {
this.name = 'jack'; this.age = 18; } function jQuery() { return new Shadow(); } var obj1 = jQuery(); console.log(obj1) // Chrome控制台会输出如下内容 // Shadow {name: "jack", age: 18} 复制代码
jQuery()
用了移花接木的障眼法完成了对象实例化,一手隐藏的new Shadow()
让咱们误觉得不用new
直接调用函数也能建立实例。
咱们再来试下new jQuery()
,会发现,“卧槽,怎么跟jQuery()
执行结果如出一辙!”
var obj2 = new jQuery();
console.log(obj2) // Chrome控制台会输出如下内容 // Shadow {name: "jack", age: 18} 复制代码
这是由于new构造函数显式return了new Shadow()
,这样返回的结果也就是new Shadow()
实例化出来的对象,而不使用new
直接调用jQuery()
,只是把jQuery()
当成一个普通的函数执行,其结果不言而喻是new Shadow()
实例化出来的对象。
因此,这里new jQuery()
和jQuery()
是等价的。
虽然jQuery已经用得愈来愈少,可是其设计思路很是值得咱们学习。那么jQuery到底妙在哪里?能够说是不少,链式操做,插件体系这些特点都是咱们耳熟能详的。不扯太多了,就让咱们来简单分析下jQuery实例化的过程。
我这里拿到了jQuery v1.12.4版本的代码,大概1W行,很舒服。
翻啊翻啊,翻到了第71行,看到了这么一串代码:
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context ); } 复制代码
这不就是咱们熟悉的移花接木技术吗?jQuery.fn.init
彷佛就是上面例子中的Shadow
。看着有点像了,可是仍是要好好研究下。
jQuery.fn是jQuery.prototype的别名,是为了代码简洁的考虑。这一点参考源码第91行就能够知道。
jQuery.fn = jQuery.prototype = {
// ...... 复制代码
咱们知道,若是仅仅经过new jQuery.fn.init(selector, context)
是存在一个问题的,问题就是获得的实例不是jQuery
的实例,而是jQuery.fn.init
的实例。那么如何处理这个问题呢?
咱们翻到源码2866行,能够看到:
init = jQuery.fn.init = function( selector, context, root ) {
// 建立实例的具体逻辑 } 复制代码
具体init
方法怎么建立一个jQuery对象,作了哪些判断逻辑,这些都不是本文关注的重点。咱们须要关注的是,jQuery是如何保证明例化的对象的原型指向是正确的?否则实例化的对象如何使用jQuery.prototype
上面挂载的诸多方法呢,好比this.show()
、this.hide()
?
紧接着翻到2982行,我有了答案:
init.prototype = jQuery.fn;
复制代码
妙啊,这一手修改原型指向的操做,完美解决了这个问题。这样一来,new init()
获得的实例天然也是jQuery
的实例。
jQuery.prototype.init.prototype === jQuery.prototype; // true
var a = $('<div>123</div>') a instanceof jQuery // true a instanceof jQuery.fn.init // true 复制代码
这样一来,咱们能够获得一个基本的设计思路:
function myModule(params) {
return new myModule.fn.init(params); } myModule.fn = myModule.prototype = { constructor: myModule } myModule.fn.init = function(params) { // 能够对实例对象进行各类操做 } myModule.fn.init.prototype = myModule.prototype; 复制代码
在这个基础上,咱们能够扩展静态方法和原型方法,这个myModule模块就变得愈来愈丰富。
妙啊,一个构造函数,让我陷入了思考......扶我起来,我还能学!
本文使用 mdnice 灵动蓝主题 排版