@@ -332,3 +332,300 @@ function Fubar (foo, bar) {
332332
333333上面代码使用` instanceof ` 运算符,在函数体内部判断` this ` 关键字是否为构造函数` Fubar ` 的实例。如果不是,就表明忘了加` new ` 命令。
334334
335+ ## 构造函数的继承
336+
337+ 让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。第一步是在子类的构造函数中,调用父类的构造函数。
338+
339+ ``` javascript
340+ function Sub (value ) {
341+ Super .call (this );
342+ this .prop = value;
343+ }
344+ ```
345+
346+ 上面代码中,` Sub ` 是子类的构造函数,` this ` 是子类的实例。在实例上调用父类的构造函数` Super ` ,就会让子类实例具有父类实例的属性。
347+
348+ 第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。
349+
350+ ``` javascript
351+ Sub .prototype = Object .create (Super .prototype );
352+ Sub .prototype .constructor = Sub;
353+ Sub .prototype .method = ' ...' ;
354+ ```
355+
356+ 上面代码中,` Sub.prototype ` 是子类的原型,要将它赋值为` Object.create(Super.prototype) ` ,而不是直接等于` Super.prototype ` 。否则后面两行对` Sub.prototype ` 的操作,会连父类的原型` Super.prototype ` 一起修改掉。
357+
358+ 另外一种写法是` Sub.prototype ` 等于一个父类实例。
359+
360+ ``` javascript
361+ Sub .prototype = new Super ();
362+ ```
363+
364+ 上面这种写法也有继承的效果,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法。
365+
366+ 举例来说,下面是一个` Shape ` 构造函数。
367+
368+ ``` javascript
369+ function Shape () {
370+ this .x = 0 ;
371+ this .y = 0 ;
372+ }
373+
374+ Shape .prototype .move = function (x , y ) {
375+ this .x += x;
376+ this .y += y;
377+ console .info (' Shape moved.' );
378+ };
379+ ```
380+
381+ 我们需要让` Rectangle ` 构造函数继承` Shape ` 。
382+
383+ ``` javascript
384+ // 第一步,子类继承父类的实例
385+ function Rectangle () {
386+ Shape .call (this ); // 调用父类构造函数
387+ }
388+ // 另一种写法
389+ function Rectangle () {
390+ this .base = Shape;
391+ this .base ();
392+ }
393+
394+ // 第二步,子类继承父类的原型
395+ Rectangle .prototype = Object .create (Shape .prototype );
396+ Rectangle .prototype .constructor = Rectangle;
397+ ```
398+
399+ 采用这样的写法以后,` instanceof ` 运算符会对子类和父类的构造函数,都返回` true ` 。
400+
401+ ``` javascript
402+ var rect = new Rectangle ();
403+
404+ rect instanceof Rectangle // true
405+ rect instanceof Shape // true
406+ ```
407+
408+ 上面代码中,子类是整体继承父类。有时只需要单个方法的继承,这时可以采用下面的写法。
409+
410+ ``` javascript
411+ ClassB .prototype .print = function () {
412+ ClassA .prototype .print .call (this );
413+ // some code
414+ }
415+ ```
416+
417+ 上面代码中,子类` B ` 的` print ` 方法先调用父类` A ` 的` print ` 方法,再部署自己的代码。这就等于继承了父类` A ` 的` print ` 方法。
418+
419+ ## 多重继承
420+
421+ JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。
422+
423+ ``` javascript
424+ function M1 () {
425+ this .hello = ' hello' ;
426+ }
427+
428+ function M2 () {
429+ this .world = ' world' ;
430+ }
431+
432+ function S () {
433+ M1 .call (this );
434+ M2 .call (this );
435+ }
436+
437+ // 继承 M1
438+ S .prototype = Object .create (M1 .prototype );
439+ // 继承链上加入 M2
440+ Object .assign (S .prototype , M2 .prototype );
441+
442+ // 指定构造函数
443+ S .prototype .constructor = S ;
444+
445+ var s = new S ();
446+ s .hello // 'hello:'
447+ s .world // 'world'
448+ ```
449+
450+ 上面代码中,子类` S ` 同时继承了父类` M1 ` 和` M2 ` 。这种模式又称为 Mixin(混入)。
451+
452+ ## 模块
453+
454+ 随着网站逐渐变成"互联网应用程序",嵌入网页的 JavaScript 代码越来越庞大,越来越复杂。网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等……开发者必须使用软件工程的方法,管理网页的业务逻辑。
455+
456+ JavaScript 模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
457+
458+
459+ :"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
460+
461+ 为了解决上面的缺点,可以
462+ 但是,JavaScript 不是一种模块化编程语言,ES6 才开始支持“类”和“模块”。下面介绍传统的做法,如何利用对象实现模块的效果。
463+
464+ ### 基本的实现方法
465+
466+ 模块是实现特定功能的一组属性和方法的封装。
467+
468+ 简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面。
469+
470+ ``` javascript
471+ var module1 = new Object ({
472+ _count : 0 ,
473+ m1 : function (){
474+ // ...
475+ },
476+ m2 : function (){
477+ // ...
478+ }
479+ });
480+ ```
481+
482+ 上面的函数` m1 ` 和` m2 ` ,都封装在` module1 ` 对象里。使用的时候,就是调用这个对象的属性。
483+
484+ ``` javascript
485+ module1 .m1 ();
486+ ```
487+
488+ 但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
489+
490+ ``` javascript
491+ module1 ._count = 5 ;
492+ ```
493+
494+ ### 封装私有变量:构造函数的写法
495+
496+ 我们可以利用构造函数,封装私有变量。
497+
498+ ``` javascript
499+ function StringBuilder () {
500+ var buffer = [];
501+
502+ this .add = function (str ) {
503+ buffer .push (str);
504+ };
505+
506+ this .toString = function () {
507+ return buffer .join (' ' );
508+ };
509+
510+ }
511+ ```
512+
513+ 上面代码中,` buffer ` 是模块的私有变量。一旦生成实例对象,外部是无法直接访问` buffer ` 的。但是,这种方法将私有变量封装在构造函数中,违反了构造函数与实例对象相分离的原则。并且,非常耗费内存。
514+
515+ ``` javascript
516+ function StringBuilder () {
517+ this ._buffer = [];
518+ }
519+
520+ StringBuilder .prototype = {
521+ constructor : StringBuilder,
522+ add: function (str ) {
523+ this ._buffer .push (str);
524+ },
525+ toString : function () {
526+ return this ._buffer .join (' ' );
527+ }
528+ };
529+ ```
530+
531+ 这种方法将私有变量放入实例对象中,好处是看上去更自然,但是它的私有变量可以从外部读写,不是很安全。
532+
533+ ### 封装私有变量:立即执行函数的写法
534+
535+ 另一种做法是使用“立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的。
536+
537+ ``` javascript
538+ var module1 = (function () {
539+ var _count = 0 ;
540+ var m1 = function () {
541+ // ...
542+ };
543+ var m2 = function () {
544+ // ...
545+ };
546+ return {
547+ m1 : m1,
548+ m2 : m2
549+ };
550+ })();
551+ ```
552+
553+ 使用上面的写法,外部代码无法读取内部的` _count ` 变量。
554+
555+ ``` javascript
556+ console .info (module1 ._count ); // undefined
557+ ```
558+
559+ 上面的` module1 ` 就是 JavaScript 模块的基本写法。下面,再对这种写法进行加工。
560+
561+ ### 模块的放大模式
562+
563+ 如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。
564+
565+ ``` javascript
566+ var module1 = (function (mod ){
567+ mod .m3 = function () {
568+ // ...
569+ };
570+ return mod;
571+ })(module1);
572+ ```
573+
574+ 上面的代码为` module1 ` 模块添加了一个新方法` m3() ` ,然后返回新的` module1 ` 模块。
575+
576+ 在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"(Loose augmentation)。
577+
578+ ``` javascript
579+ var module1 = (function (mod ) {
580+ // ...
581+ return mod;
582+ })(window .module1 || {});
583+ ```
584+
585+ 与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象。
586+
587+ ### 输入全局变量
588+
589+ 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
590+
591+ 为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
592+
593+ ``` javascript
594+ var module1 = (function ($ , YAHOO ) {
595+ // ...
596+ })(jQuery, YAHOO );
597+ ```
598+
599+ 上面的` module1 ` 模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入` module1 ` 。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
600+
601+ 立即执行函数还可以起到命名空间的作用。
602+
603+ ``` javascript
604+ (function ($ , window , document ) {
605+
606+ function go (num ) {
607+ }
608+
609+ function handleEvents () {
610+ }
611+
612+ function initialize () {
613+ }
614+
615+ function dieCarouselDie () {
616+ }
617+
618+ // attach to the global scope
619+ window .finalCarousel = {
620+ init : initialize,
621+ destroy : dieCouraselDie
622+ }
623+
624+ })( jQuery, window , document );
625+ ```
626+
627+ 上面代码中,` finalCarousel ` 对象输出到全局,对外暴露` init ` 和` destroy ` 接口,内部方法` go ` 、` handleEvents ` 、` initialize ` 、` dieCarouselDie ` 都是外部无法调用的。
628+
629+ ## 参考链接
630+
631+ - [ JavaScript Modules: A Beginner’s Guide] ( https://medium.freecodecamp.com/javascript-modules-a-beginner-s-guide-783f7d7a5fcc ) , by Preethi Kasireddy
0 commit comments