Skip to content

Commit fafbb18

Browse files
committed
docs(oop): edit prototype
1 parent f196dbb commit fafbb18

2 files changed

Lines changed: 297 additions & 38 deletions

File tree

docs/oop/prototype.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

docs/types/function.md

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -200,44 +200,6 @@ function f() {
200200
f() // 1
201201
```
202202

203-
### 不能在条件语句中声明函数
204-
205-
根据 ES5 的规范,不得在非函数的代码块中声明函数,最常见的情况就是`if``try`语句。
206-
207-
```javascript
208-
if (foo) {
209-
function x() {}
210-
}
211-
212-
try {
213-
function x() {}
214-
} catch(e) {
215-
console.log(e);
216-
}
217-
```
218-
219-
上面代码分别在`if`代码块和`try`代码块中声明了两个函数,按照语言规范,这是不合法的。但是,实际情况是各家浏览器往往并不报错,能够运行。
220-
221-
但是由于存在函数名的提升,所以在条件语句中声明函数,可能是无效的,这是非常容易出错的地方。
222-
223-
```javascript
224-
if (false) {
225-
function f() {}
226-
}
227-
228-
f() // 不报错
229-
```
230-
231-
上面代码的原始意图是不声明函数`f`,但是由于`f`的提升,导致`if`语句无效,所以上面的代码不会报错。要达到在条件语句中定义函数的目的,只有使用函数表达式。
232-
233-
```javascript
234-
if (false) {
235-
var f = function () {};
236-
}
237-
238-
f() // undefined
239-
```
240-
241203
## 函数的属性和方法
242204

243205
### name 属性

0 commit comments

Comments
 (0)