深入理解JavaScript系列(46):代码复用模式(推

介绍

本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用。

模式1:原型继承

原型继承是让父对象作为子对象的原型,从而达到继承的目的:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

function object(o) {

function F() {

    }

 

    F.prototype = o;

returnnew F();

}

 

// 要继承的父对象

var parent = {

    name: "Papa"

};

 

// 新对象

var child = object(parent);

 

// 测试

console.log(child.name); // "Papa"

 

 

// 父构造函数

function Person() {

// an "own" property

this.name = "Adam";

}

// 给原型添加新属性

Person.prototype.getName = function () {

returnthis.name;

};

// 创建新person

var papa = new Person();

// 继承

var kid = object(papa);

console.log(kid.getName()); // "Adam"

 

 

// 父构造函数

function Person() {

// an "own" property

this.name = "Adam";

}

// 给原型添加新属性

Person.prototype.getName = function () {

returnthis.name;

};

// 继承

var kid = object(Person.prototype);

console.log(typeof kid.getName); // "function",因为是在原型里定义的

console.log(typeof kid.name); // "undefined", 因为只继承了原型

 

同时,ECMAScript5也提供了类似的一个方法叫做Object.create用于继承对象,用法如下:

双击代码全选

1

2

3

4

5

6

7

/* 使用新版的ECMAScript 5提供的功能 */

var child = Object.create(parent);

 

var child = Object.create(parent, {

    age: { value: 2} // ECMA5 descriptor

});

console.log(child.hasOwnProperty("age")); // true

 

而且,也可以更细粒度地在第二个参数上定义属性:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 首先,定义一个新对象man

var man = Object.create(null);

 

// 接着,创建包含属性的配置设置

// 属性设置为可写,可枚举,可配置

var config = {

    writable: true,

    enumerable: true,

    configurable: true

};

 

// 通常使用Object.defineProperty()来添加新属性(ECMAScript5支持)

// 现在,为了方便,我们自定义一个封装函数

var defineProp = function (obj, key, value) {

    config.value = value;

    Object.defineProperty(obj, key, config);

}

 

defineProp(man, 'car', 'Delorean');

defineProp(man, 'dob', '1981');

defineProp(man, 'beard', false);

 

所以,继承就这么可以做了:

双击代码全选

1

2

3

var driver = Object.create( man );

defineProp (driver, 'topSpeed', '100mph');

driver.topSpeed // 100mph

 

但是有个地方需要注意,就是Object.create(null)创建的对象的原型为undefined,也就是没有toString和valueOf方法,所以alert(man);的时候会出错,但alert(man.car);是没问题的。

模式2:复制所有属性进行继承

这种方式的继承就是将父对象里所有的属性都复制到子对象上,一般子对象可以使用父对象的数据。
先来看一个浅拷贝的例子:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/* 浅拷贝 */

function extend(parent, child) {

var i;

    child = child || {};

for (i in parent) {

if (parent.hasOwnProperty(i)) {

            child[i] = parent[i];

        }

    }

return child;

}

 

var dad = { name: "Adam" };

var kid = extend(dad);

console.log(kid.name); // "Adam"

 

var dad = {

    counts: [1, 2, 3],

    reads: { paper: true }

};

var kid = extend(dad);

kid.counts.push(4);

console.log(dad.counts.toString()); // "1,2,3,4"

console.log(dad.reads === kid.reads); // true

 

代码的最后一行,你可以发现dad和kid的reads是一样的,也就是他们使用的是同一个引用,这也就是浅拷贝带来的问题。
我们再来看一下深拷贝:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

/* 深拷贝 */

function extendDeep(parent, child) {

var i,

        toStr = Object.prototype.toString,

        astr = "[object Array]";

 

    child = child || {};

 

for (i in parent) {

if (parent.hasOwnProperty(i)) {

if (typeof parent[i] === 'object') {

                child[i] = (toStr.call(parent[i]) === astr) ? [] : {};

                extendDeep(parent[i], child[i]);

            } else {

                child[i] = parent[i];

            }

        }

    }

return child;

}

 

var dad = {

    counts: [1, 2, 3],

    reads: { paper: true }

};

var kid = extendDeep(dad);

 

kid.counts.push(4);

console.log(kid.counts.toString()); // "1,2,3,4"

console.log(dad.counts.toString()); // "1,2,3"

 

console.log(dad.reads === kid.reads); // false

kid.reads.paper = false;

 

深拷贝以后,两个值就不相等了,bingo!

模式3:混合(mix-in)

混入就是将一个对象的一个或多个(或全部)属性(或方法)复制到另外一个对象,我们举一个例子:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

function mix() {

var arg, prop, child = {};

for (arg = 0; arg < arguments.length; arg += 1) {

for (prop in arguments[arg]) {

if (arguments[arg].hasOwnProperty(prop)) {

                child[prop] = arguments[arg][prop];

            }

        }

    }

return child;

}

 

var cake = mix(

                { eggs: 2, large: true },

                { butter: 1, salted: true },

                { flour: '3 cups' },

                { sugar: 'sure!' }

                );

 

console.dir(cake);