30 August 2013
号外号外:我的新书《现代JavaScript库开发:原理、技术与实战》出版啦!!!快点我查看
号外号外:一组小而美的JavaScript迷你库!!!快点我查看
号外号外:猿辅导招聘前端,后端,客户端啦!地点:北京!!!快点我查看

为什么会用这样一个题目呢,这是要说封装的什么问题,本文并不讲高深的封装理论,只是解决一个小问题。

问题来源

今天在百度知道上闲逛,遇到一个网友的问题,问题如下,问题的地址见这里

百度知道问题截图

下面先不看看其他网友给的答案:

百度知道网友回答截图

网友大部分回答不能一起定义,那么我们来分析下为什么这样做是错的,然后给出相应的解决办法。

重现问题

先来说说为什么调用出错,我在自己的浏览器里重现了问题,处于实验并未全部复原代码,并且用到了全局变量哦:

function Dialog(){
    
    Dialog.prototype = {
        init:function(){
            console.log("ok");
        }
    }
}

var a = new Dialog();
a.init();

下面是火狐提示的错误:

火狐下提示错误

分析问题

init不是一个方法,这是为什么呢,我们将调用代码修改下,出于演示,并未遵循JsLint代码规范:

var a = new Dialog();
typeof a.init;

代码执行结果

结果显示为undefined,也就是init没有被定义,或者说在求值过程中未找到init标识符的值,这是因为在调用函数时函数里面的内容才会被解析执行,所以在调用new Dialog(),时其内部的代码尚未执行,所以设置Dialog的原型的语句尚未执行,通过这个例子可以看出绑定this的prototype的过程是在执行构造函数内部代码之前,可以用下面的代码来解释下:

function Dialog(){
    var that = Object.create(Object.getPrototypeOf(this));
    
    Dialog.prototype = {}         
}

“巧妙解决”

很明显获取原型时尚未设置原型,但当我们再次调用new 时情况将发生改变:

var a = new Dialog();
var b = new Dialog();
typeof b.init;

代码执行结果

在此调用时奇迹发生了,我们看到第二次调用时成功获取了值,只有第一次时值是未获去,那我们是不是可以改造下我们的dialog函数呢:

function Dialog(){
    
    Dialog.prototype = {
        init:function(){
            console.log("ok");
        }
    }
}
new Dialog();


var a = new Dialog();
typeof a.init;

我们只需先调用下构造函数便解决了问题,如果你觉得上面的代码还是有悖于封装我们可以再做改变:

var Dialog = (function(){
    function Dialog(){
    
        Dialog.prototype = {
            init:function(){
                console.log("ok");
            }
        }
    }
    
    new Dialog();
    
    return Dialog;
}());

问题中的问题

上面真的解决问题了吗,你难道没有疑问呢,如果你已经看出问题所在在了,那你也一定能想出解决办法,而且你应该是一个高手,那么让我们看看这样巧妙的解决办法有什么问题,为此我们来构造下面的代码:

var a = new Dialog();
typeof a instanceof Dialog;

也许你会问为什么会这样写,也许下面的结果更让你吃惊:

代码执行结果

结果为false,为什么我的a不是Dialog的实例呢,我的a明明是Dialog创建的,要想搞清这个问题我们先得说清楚 instanceof关键字的工作原理,当我们调用类似a instanceof Dialog 这样的语句时,解释器是怎么判断a是Dialog创建的对象的呢,原来解释器是判断a的原型是否为Dialog的prototype属性所指向的对象也就是说如果a的原型和Dialog的prorotype属性指向同一个对象就认为a是Dialog的对象,当然在判断时并不是至判断a的的原型,而是判断原型链中的每个对象,例如:

var a = [];

a instanceof Array;
a instanceof Object;

上面的两条语句都会返回true,因为a的原型链中包含这两个对象。

而上面我们的代码为什么结果为false呢,那是因为当我们每次调用Dialog构造函数时都会在内部重写Dialog的原型,而已经创建的对象的原型会指向原来的原型对象,解释器在判断两个对象是否相等时,要判断两个对象是否引用同一块地址,而不是两个对象是否有相同的属性和方法,所以上面出现false的原因就很清楚了,所以上面的解决办法就出现问题了,而且是很大的问题。显然这种方法行不通。

看清本质

那我们有没有办法解决问题呢,让我们先来看看作者想要实现什么,作者想要实现的封装,也就是构造函数和构造函数的原型分开写的问题,作者想把他们写到一起,作者认为这才是封装,那么我们先来看下封装是什么,作者对封装的理解是否有误:

封装,1、在程序上,隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

上面是对封装的解释,可以看出这跟作者描述的封装并不是一个意思,作者此处所想表达的实际上是更好的代码结构。

建议

既然清楚了作者的意思,来看下解决办法,如何将构造函数的定义和原型的定义写到一起呢,看下我给出的解决办法:

var Dialog = (function(){
    function Dialog(){
           
    }
    
    Dialog.prototype = {
        init:function(){
            console.log("ok");
        }
    }
    
    return Dialog;
}());


var a = new Dialog();
a instanceof Dialog;

代码执行结果

好了问题解决了,结果正确了,而且我们也得到了比较清晰的代码结构。

原文网址:http://yanhaijing.com/javascript/2013/08/30/encapsulation-of-javascript/

微信公众号:颜海镜
关注微信公众号 颜海镜
微信支付二维码
赞赏支持 微信扫一扫


修远兮

修远兮 on 22 Jan 2014

这题太发散了~~

颜海镜

颜海镜 on 22 Jan 2014

怎么发散了?

修远兮

修远兮 on 22 Jan 2014

1,2,3,4,9,10,11,12这些题基本没有固定答案。不同级别的面试者回答的深度也都不太一样,而且还能引出一些新的问题,感觉都是坑~~

颜海镜

颜海镜 on 22 Jan 2014

对啊,但是还是能看出面试者的深度和广度的

mxclion

mxclion on 23 Jan 2014

题不错,挖深度的话能挖出来很多。
广度有些局限。

颜海镜

颜海镜 on 23 Jan 2014

嗯,还是有一定难度的

allenlsy

allenlsy on 28 Jan 2014

必须赞一个,分析透彻,让人理性的羡慕,而不是看完最强大脑就自卑无比

颜海镜

颜海镜 on 28 Jan 2014

感谢支持

Renfei Song

Renfei Song on 10 Feb 2014

18, before 和 after 是伪元素,不是伪类。

颜海镜

颜海镜 on 10 Feb 2014

嗯,感谢支持,以更正

mxclion

mxclion on 13 Feb 2014

加油!

颜海镜

颜海镜 on 13 Feb 2014

嗯,加油

HGR

HGR on 17 Feb 2014

一直以为你是有很多年工作经验的,今天才发现刚毕业。突然感觉自己的大学好苍白,学到的东西好少啊。想你学习。

颜海镜

颜海镜 on 18 Feb 2014

呵呵,一样一样的 挤眼

2012创业家

2012创业家 on 18 Feb 2014

很不错,申请入群咯

颜海镜

颜海镜 on 19 Feb 2014

感谢支持,一起交流

mxclion

mxclion on 27 Feb 2014

Chrome 的 Emulate 面板真是帅爆了!

颜海镜

颜海镜 on 27 Feb 2014

嗯,非常好用

鏾步的魚

鏾步的魚 on 28 Feb 2014

直接弄个图片不行嘛,真麻烦

颜海镜

颜海镜 on 28 Feb 2014

不动了吧,一个图片 得 几k,这样 解决流量

justjavac

justjavac on 06 Mar 2014

文章标题里面就别用 了吧。

justjavac

justjavac on 06 Mar 2014

文章排版很赞。

颜海镜

颜海镜 on 06 Mar 2014

嗯,以后注意

颜海镜

颜海镜 on 06 Mar 2014

嘿嘿

IT男那点事

IT男那点事 on 12 Mar 2014

想问下我的chrome设置override里怎么没Show ‘Emulation’ view in console drawer这个选项?

颜海镜

颜海镜 on 16 Mar 2014

你什么版本

IT男那点事

IT男那点事 on 16 Mar 2014

版本 31.0.1650.63

颜海镜

颜海镜 on 16 Mar 2014

那应该是其他类似选项我是 32

mxclion

mxclion on 31 Mar 2014

你在金山哪个部门的?

颜海镜

颜海镜 on 31 Mar 2014

西山居

张流文

张流文 on 02 Apr 2014

css3中的网格

颜海镜

颜海镜 on 02 Apr 2014

鼓掌

fiona_小凡

fiona_小凡 on 28 Apr 2014

在General里面的Appearance的下面找找

颜海镜

颜海镜 on 29 Apr 2014

nxg

nxg on 30 Apr 2014

写的不错

颜海镜

颜海镜 on 30 Apr 2014

感谢支持,欢迎持续关注

颜海镜

颜海镜 on 13 May 2014

呵呵,必须的,碉堡了

西门

西门 on 19 May 2014

32以上,才有这个功能,升级吧

西门

西门 on 19 May 2014

手机网页主要用flexbox

西门

西门 on 19 May 2014

现在主要用less,BEM命名我很喜欢,可是团队其他人接受不了。。。

xianlaioy

xianlaioy on 04 Jun 2014

错别字:点号不是点好。
我们将看下列技巧:
相等
点好vs括号

颜海镜

颜海镜 on 05 Jun 2014

已改正,感谢指正

林聪

林聪 on 20 Jun 2014

这个貌似typescript的方案

颜海镜

颜海镜 on 21 Jun 2014

嗯,回头研究研究

春天不暖--

春天不暖-- on 01 Jul 2014

这真是金山的题么,面向什么水平的,我感觉还是比较简单的

颜海镜

颜海镜 on 05 Jul 2014

good

颜海镜

颜海镜 on 05 Jul 2014

真的,面向的前端开发

颜海镜

颜海镜 on 03 Sep 2014

怒赞啊 good

十指

十指 on 09 Oct 2014

喜欢你的两款游戏,

颜海镜

颜海镜 on 09 Oct 2014

感谢支持

颜海镜

颜海镜 on 25 Oct 2014

Jekyll 是一个简单的网站静态页面生成工具。由于是用Ruby语音编写的,所以在Windows系统上配置起来还是稍微有点繁琐的。

淡忘~浅思

淡忘~浅思 on 01 Nov 2014

谢谢博主分享

颜海镜

颜海镜 on 01 Nov 2014

呵呵,感谢支持

淡忘~浅思

淡忘~浅思 on 01 Nov 2014

博主 换个友链可以不

颜海镜

颜海镜 on 01 Nov 2014

好啊,你的多少?

淡忘~浅思

淡忘~浅思 on 01 Nov 2014

http //www.ido321.com 淡忘~浅思

淡忘~浅思

淡忘~浅思 on 01 Nov 2014

已经添加了学长的友链

颜海镜

颜海镜 on 02 Nov 2014

淡忘~浅思

淡忘~浅思 on 02 Nov 2014

嘻嘻 早啊

颜海镜

颜海镜 on 02 Nov 2014

淡忘~浅思

淡忘~浅思 on 02 Nov 2014

嘻嘻

雷鹏

雷鹏 on 23 Nov 2014

赞一个

颜海镜

颜海镜 on 24 Nov 2014

thx

To-Think

To-Think on 01 Dec 2014

0.1的二进制是0.00011……文中小数点后少了一个0

丿姑娘请留步

丿姑娘请留步 on 25 Dec 2014

typeof a instanceof Dialog;这个语句有问题,本质上变成了 object instanceof Dialog返回false,当然这个不是重点,去掉typeof依然返回false,关于这个问题一切的起因就是写这个函数的人重新定义了Dialog.prototype,如果把始作俑者写的Dialog.prototype={}改为Dialog.prototype.init=/Dialog.prototype.add就都云淡风轻了;其实就算在new运算符之后再重写prototype对象,通过new新建的对象也不会再继承重写的原型对象的属性和方法,而是以new运算符继承的为准;一般不建议覆盖原型对象,可见不规范的写法可能会产生各种意想不到的问题

颜海镜

颜海镜 on 26 Dec 2014

嗯确实是这样,很久以前写的文章了

yp

yp on 31 Jan 2015

不是吧 怎么没考面向对象 ,我至今还没理解的面向对象

颜海镜

颜海镜 on 01 Feb 2015

哈哈 偏css方面,所以,,,拟懂得

陈泽江IT

陈泽江IT on 10 Feb 2015

这是我看过的前端面试。。最简单的了。。

hillgreen

hillgreen on 11 Feb 2015

机翻的软文

颜海镜

颜海镜 on 12 Feb 2015

。。。怎么会呢

颜海镜

颜海镜 on 12 Feb 2015

。。。真是这样的吗,那你好厉害,不过这确实都是些基础题

一池湛蓝

一池湛蓝 on 03 Mar 2015

你好,有个疑问想请教下您
var a={
b 1 ,
//c function(){return this.b+1},
c this.b,
d function(){
alert(this.b+ 1 );
}
}


alert( c= +a.c);
//a.c() 执行成功被赋值为字符串 “1”
//a.c 为什么就无法被赋值呢?

颜海镜

颜海镜 on 04 Mar 2015

hi 你好,这属于语法问题了,直接c this.b 此时的this指向的是window(ES3,ES5严格模式下会报错),所以window下可能并没有b,此时c的值就是undefined

坏坏de大猫猫

坏坏de大猫猫 on 07 Mar 2015

我很受益,作为一个前端刚入门的人来说
能把这些题目全部做出来应该对我水平有所提高吧

颜海镜

颜海镜 on 09 Mar 2015

加油

一池湛蓝

一池湛蓝 on 17 Mar 2015

多谢多谢

颜海镜

颜海镜 on 18 Apr 2015

你记住了吗? 威武

夜猫子丶程序猿

夜猫子丶程序猿 on 18 Apr 2015

请问 after 和 before什么作用,和 after, before区别是什么?

颜海镜

颜海镜 on 18 Apr 2015

伪类是冒号,伪元素是两个冒号,后来将after和before归为伪元素,就变成了两个,浏览器都是可以支持的,而一个冒号似乎兼容性更好点

内午满面

内午满面 on 20 Apr 2015

很棒

颜海镜

颜海镜 on 20 Apr 2015

感谢支持

颜海镜

颜海镜 on 20 Apr 2015

这个写的不错,涨姿势啊 威武

颜海镜

颜海镜 on 21 Apr 2015

另辟蹊径,柳暗花明又一村哈 威武

Sivan

Sivan on 22 Apr 2015

说 * 选择器「给浏览器带来大量不必要的负担」是不负责任的,目前并没有看到李菊福的性能测试。当下比较一致的观点是 `.foo > *` 这样把通配跟子选择器放到一起用时会导致比较大的性能问题。

颜海镜

颜海镜 on 23 Apr 2015

这样说目的就是告诉大家不要用*

Sivan

Sivan on 23 Apr 2015

不能完全否定 *,现在用 `*, * before, * after {}` 初始化 box-sizing 是很常见的写法。

颜海镜

颜海镜 on 23 Apr 2015

现在浏览器越来越快了,以后这些性能问题都会不存在

颜海镜

颜海镜 on 24 Apr 2015

威武

2012创业家

2012创业家 on 24 Apr 2015

http //mathjs.org/可以去了解下哈

颜海镜

颜海镜 on 25 Apr 2015

嗯嗯,感谢分享

马六甲

马六甲 on 27 Apr 2015

对于标题的翻译,不敢苟同~

颜海镜

颜海镜 on 28 Apr 2015

哦 有何见教啊

马六甲

马六甲 on 29 Apr 2015

Functional 一词包含很多意义,在不同场景下其意义不同。
原文中 Functional 取 实用(Practical) 意义更合适。FCSS本质是CSS的一种最佳实践(Best Practice)。

愚以为翻译为“实用CSS”更加接近原意并且容易理解。

Functional 取 函数式 意义的时候,则形容被修辞的对象具备“函数”(Function)的特征, 例如 Functional Programming。

颜海镜

颜海镜 on 04 May 2015

嗯,很有道理

summer

summer on 04 May 2015

您好!我现在想在V8 引擎中获取每个JS函数的scope chain(也就是拿到函数间的调用关系和数据传递关系),请问您知道我应该在源码的哪里加断点才能够拿到我想要的信息呢?

颜海镜

颜海镜 on 04 May 2015

你好,我是搞前端的,对于v8引擎不慎了解,不好意思哈

颜海镜

颜海镜 on 04 May 2015

威武

颜海镜

颜海镜 on 04 May 2015

威武

futurePlayer

futurePlayer on 04 May 2015

1.楼主说的第一个问题,其实可以专门写一个单独的css来规定默认的样式,可以多方调用,至少是在同一个项目中;
2.第8个问题说不要放没有class的html代码,请问这是为什么?求解;
3.position,float,overflow比较特殊,但也正是因为这种特殊确实可能引发一些bug,之前写移动端的时候出现了页面卡住的问题(ios上,具体几我忘了),最后只能硬着头皮用重绘来解决了这个问题,最后的最后查出来是这几个属性引起的;
4.css选择器的读取是从右向左的,所以深层次路径的时候我觉得应该充分考虑下右边选择器的特殊性;
5.对于合理布局的话应该着重考虑重绘重排的问题;
6.有个问题请教楼主,css开发中其实有个瓶颈,随着项目越来越大和开发人员的变更,很多人都不敢去轻易改动其中的代码,怕牵一发而动全身,他们更愿意去覆盖原样式,最终导致代码越来越冗余,质量越来越差,空间占用和资源消耗越来越大,当然加载的话你们都可以想象。是否可以开发出这样的一款插件或是以什么方式来改善这种问题?

颜海镜

颜海镜 on 05 May 2015

占个坑,慢慢回答。
1. 这个可以在less中写一个mixin,但大家的做法,还是偏向resetcss。
2. 不要像下面这样
3.
4. 是这样的,如果右面足够特殊,左边的就不需要了
6. 可以考虑下css后处理器这种东西

颜海镜

颜海镜 on 06 May 2015

记不住数组方法的人收藏吧

GhostCode

GhostCode on 06 May 2015

mark

颜海镜

颜海镜 on 06 May 2015

感谢支持

颜海镜

颜海镜 on 07 May 2015

涨姿势 威武

颜海镜

颜海镜 on 07 May 2015

深不见底,不可捉摸 囧

Michael_翔_

Michael_翔_ on 16 Jun 2015

赞一个

颜海镜

颜海镜 on 18 Jun 2015

感谢支持

歪妖内涵网

歪妖内涵网 on 02 Sep 2015

不错的网站,很喜欢,期待互访

颜海镜

颜海镜 on 02 Sep 2015

这。。。

最励志官网

最励志官网 on 05 Sep 2015

最励志网http //www.zuilizhi.net/? 路过留个言!

颜海镜

颜海镜 on 05 Sep 2015

感谢支持

爱奇趣分享网

爱奇趣分享网 on 06 Sep 2015

好久没来了,过来转转

颜海镜

颜海镜 on 06 Sep 2015

感谢支持,都是你的号吗?

卡媚儿

卡媚儿 on 30 Sep 2015

SubWidget.prototype = Object.create(Widget.prototype);
SubWidget.prototype.constructor = SubWidget;

无节操图片

无节操图片 on 02 Oct 2015

好久没来了,过来转转

LW无一

LW无一 on 03 Oct 2015

颜哥好。

var c=1;
function c() {return 2};
console.log(typeof c);//输出 number 不是function

function d() {return 1};
var d=1;
console.log(typeof d);//输出 number 不是function
从上面的例子看,我想问的是函数名与变量同名的问题。被hoisting和名字解析顺序搞晕了。

颜海镜

颜海镜 on 16 Oct 2015

感谢支持

颜海镜

颜海镜 on 16 Oct 2015

ES6新增class,可以试试

颜海镜

颜海镜 on 16 Oct 2015

记住变量会覆盖函数就ok了

hid_left

hid_left on 03 Mar 2016

function concat (input) {
return input.reduce(function (partial, value) {
if (partial) {
partial += ', '
}
return partial + value
}, '')
}

concat(
{ name 'George' },
{ name 'Sam' },
{ name 'Pear' }
)
// <- 'George, Sam, Pear'

这个例子中,concat函数不能按照原意处理数组,实际输出为 object Object , object Object , object Object 。需要获取对象的name属性才能按照作者本意输出。

a1ex

a1ex on 15 Mar 2016

很有价值,谢谢

颜海镜

颜海镜 on 16 Mar 2016

感谢支持

林凯

林凯 on 24 Mar 2016

超赞

颜海镜

颜海镜 on 24 Mar 2016

感谢支持

冧_jxd

冧_jxd on 01 Aug 2016

太赞了。谢谢。

颜海镜

颜海镜 on 01 Aug 2016

感谢支持

冧_jxd

冧_jxd on 03 Aug 2016

啊哈,今天还在前端talk的读者群看到你了。

颜海镜

颜海镜 on 03 Aug 2016

哎呀,这么巧合

冧_jxd

冧_jxd on 03 Aug 2016

是啊,我有在群里艾特你说我刚刚看了你 blog,不过这篇文章好像很久前了,我是最近在准备校招复习各种知识点的时候查到的。很有帮助呀。

颜海镜

颜海镜 on 04 Aug 2016

感谢支持哈,要毕业了!!!加油啊

noambitions163

noambitions163 on 11 Oct 2016

解决css冲突问题,可以试下这个类库:https //github.com/cssobj/cssobj
喜欢就给个star!

颜海镜

颜海镜 on 12 Oct 2016

我看下,感谢反馈

刘灯一盏

刘灯一盏 on 21 Nov 2016

错别字作者可以改一下, 文章不错

颜海镜

颜海镜 on 21 Nov 2016

求提pr,求指出

LB_GODevil

LB_GODevil on 15 Feb 2017

Object.getPrototypeOf(Object.prototype)
// null

Object的原型是返回null的

颜海镜

颜海镜 on 16 Feb 2017

对的