当前位置:威尼斯 > Web前端 > 其实也不是完全没有用过javascript,(子对象的原

其实也不是完全没有用过javascript,(子对象的原

文章作者:Web前端 上传时间:2019-09-19

理解JavaScript的原型属性

2016/06/21 · JavaScript · 2 评论 · 原型

本文由 伯乐在线 - alvendarthy 翻译,sunshinebuel 校稿。未经许可,禁止转载!
英文出处:bytearcher。欢迎加入翻译组。

理解 JavaScript 的prototype属性不太容易。你也许知道它同面向对象编程(OOP)和对象继承有关,但未必对其技术原理非常清楚。

1.原型继承

  面向对象编程可以通过很多途径实现。其他的语言,比如 Java,使用基于类的模型实现: 类及对象实例区别对待。但在 JavaScript 中没有类的概念,取而代之的是一切皆对象。JavaScript 中的继承通过原型继承实现:一个对象直接从另一对象继承。对象中包含其继承体系中祖先的引用——对象的 prototype 属性。

最近语言学习有些疯狂, 从Ruby到Lisp, 然后是C#, 既然已经疯狂了, 就顺面学习一下javascript吧. 对javascript的印象一直不佳, 从骂脏话最多的使用者, 到使用者平反的世界上最被误解的语言, 从所谓的让人抓狂的特性, 到世界上任何可以用javascript实现的东西, 最终都会被javascript实现, 并且, 这是最后一个实现. 出处太多, 不一一列举, 知者已知, 不知者也没有必要为了这些无聊的言论特意找出处了.

原型继承

面向对象编程可以通过很多途径实现。其他的语言,比如 Java,使用基于类的模型实现: 类及对象实例区别对待。但在 JavaScript 中没有类的概念,取而代之的是一切皆对象。JavaScript 中的继承通过原型继承实现:一个对象直接从另一对象继承。对象中包含其继承体系中祖先的引用——对象的 prototype 属性。

class 关键字是在 ES6 中首次引入 JavaScript 的。其实,它并没有为面向对象继承引入新模型, class 关键字通过语法糖,实现了本文介绍的原型特性和构造函数。

2. JavaScript 实现继承的语言特性

  • 当尝试访问 JavaScript 对象中不存在的属性时,解析器会查找匹配的对象原型。例如调用 car.toString(),如果 car 没有 toString 方法,就会调用 car 对象的原型。 这个查找过程会一直递归, 直到查找到匹配的原型或者继承链尽头。

  • 调用  new Car() 会创建一个新的对象,并初始化为 Car.prototype。 这样就允许为新对象设置原型链。需要注意的是,new Car() 只有当  Car 是函数时才有意义。 此类函数即所谓构造函数

  • 调用对象的一个成员函数时, this 的值被绑定为当前对象。例如调用 "abc".toString(),this 的值被设置为 "abc",然后调用 toString 函数。该技术支持代码重用:同样的代码,可在 this 为各种不同的值时调用。对象的成员函数,也被称为对象的方法。

   图片 1

  首先,我们定义构造函数 Rectangle。 按照规范,我们大写构造函数名首字母,表明它可以用 new 调用,以示与其他常规函数的区别。构造函数自动将 this 赋值为一空对象,然后代码中用 x 和 y 属性填充它,以备后用。然后, Rectangle.prototype 新增一个通过 x 和 y 属性计算周长成员函数。 注意 this 的使用,在不同的对象中,this 会有不同的值,这些代码都可以正常工作。最后, 一个名为 rect 的对象创建出来了。 它继承了 Rectangle.prototype, 我们可以调用 rect.perimeter(), 然后将结果打印到控制台。

其实也不是完全没有用过javascript, 以前在开发一个Unity项目的时候用过一下Unity里面的javascript, 只不过那个javascript我甚至都只能称之为UnityScript. 太多太多自己实现的特性, 而又有些不够完整. 现在, 认识一下真正的javascript吧.

JavaScript 实现继承的语言特性

以下语言特性共同实现了 JavaScript 继承。

  • 当尝试访问 JavaScript 对象中不存在的属性时,解析器会查找匹配的对象原型。例如调用 car.toString(),如果 car 没有 toString 方法,就会调用 car 对象的原型。 这个查找过程会一直递归, 直到查找到匹配的原型或者继承链尽头。
  • 调用  new Car() 会创建一个新的对象,并初始化为 Car.prototype。 这样就允许为新对象设置原型链。需要注意的是,new Car() 只有当  Car 是函数时才有意义。 此类函数即所谓构造函数。
  • 调用对象的一个成员函数时, this 的值被绑定为当前对象。例如调用 "abc".toString()this 的值被设置为 "abc",然后调用 toString 函数。该技术支持代码重用:同样的代码,可在 this 为各种不同的值时调用。对象的成员函数,也被称为对象的方法。

prototype 属性名称带来的误解

  有一些关于 JavaScript 的原型的误解。 一个对象的原型与对象的 prototype 属性并非一回事。 前者用于在原型链中匹配不存在的属性。后者用于通过 new 关键字创建对象,它将作为新创建对象的原型。 理解二者的差异,将帮助你彻底理解 JavaScript 中的原型特性。

  Rectangle.prototype 是用 new Rectangle() 创建出来对象的原型, 而 Rectangle 的原型实际上是 JavaScript 的 Function.prototype。(子对象的原型是父对象的 prototype 属性 对象中保存原型的变量,也被称之为内部原型引用(the internal prototype link),历史上也曾称之为 __proto__ ,对这个称谓始终存在一些争议。 更精确的,它可以被称为 Object.getPrototypeOf(...) 的返回值。

 

举个栗子

我们用面向对象编程,实现一个计算矩形周长的例子。

JavaScript

function Rectangle(x, y) { this.x = x; this.y = y; } Rectangle.prototype.perimeter = function() { return 2 * (this.x + this.y); } var rect = new Rectangle(1, 2); console.log(rect.perimeter()); // outputs '6'

1
2
3
4
5
6
7
8
9
10
11
function Rectangle(x, y) {
    this.x = x;
    this.y = y;
}
 
Rectangle.prototype.perimeter = function() {
    return 2 * (this.x + this.y);
}
 
var rect = new Rectangle(1, 2);
console.log(rect.perimeter()); // outputs '6'

首先,我们定义构造函数 Rectangle。 按照规范,我们大写构造函数名首字母,表明它可以用 new 调用,以示与其他常规函数的区别。构造函数自动将 this 赋值为一空对象,然后代码中用 xy 属性填充它,以备后用。

然后, Rectangle.prototype 新增一个通过 xy 属性计算周长成员函数。 注意 this 的使用,在不同的对象中,this 会有不同的值,这些代码都可以正常工作。

最后, 一个名为 rect 的对象创建出来了。 它继承了 Rectangle.prototype, 我们可以调用 rect.perimeter(), 然后将结果打印到控制台。

 

prototype 属性名称带来的误解

有一些关于 JavaScript 的原型的误解。 一个对象的原型与对象的 prototype 属性并非一回事。 前者用于在原型链中匹配不存在的属性。后者用于通过 new 关键字创建对象,它将作为新创建对象的原型。 理解二者的差异,将帮助你彻底理解 JavaScript 中的原型特性。

在我们的例子中, Rectangle.prototype 是用 new Rectangle() 创建出来对象的原型, 而 Rectangle 的原型实际上是 JavaScript 的 Function.prototype。(子对象的原型是父对象的 prototype 属性)

对象中保存原型的变量,也被称之为内部原型引用(the internal prototype link),历史上也曾称之为 __proto__ ,对这个称谓始终存在一些争议。 更精确的,它可以被称为 Object.getPrototypeOf(...) 的返回值。

2 赞 5 收藏 2 评论

 

关于作者:alvendarthy

图片 2

一个热爱生活的家伙! 个人主页 · 我的文章 · 16

图片 3

Mac OS X 10.8.2, node v0.8.16

需要解释一下, node跟浏览器里嵌入的javascript不一样, 不具有类似confirm和prompt等接口, 我用console.log来输出.

 

概要

 

JavaScript本身就是设计为一个前端语言, 据说设计只用了10天, 有些缺陷, 但是的确足够简单. 虽然JavaScript The Definitive Guide和大多数的语言书籍一样厚如砖头, 但是其实语言本身的介绍只有前面近200页, 这个厚度其实也就和R&D中描述的C语言差不多.

也就是因为设计比较简单, JavaScript也被一些人认为不算是现代语言, 不具有现代语言的一些特性.

 

语法细节

 

可选的语句结束符;, 这个很少见. 不过一般的规范都推荐不要真的省.

支持自增++,自减符号--, 相对Ruby, Python来说, 这个要更习惯.

switch和传统的C语言语法类似, 但是可以支持字符串的case.

支持NaN, null, undefined这三种表示类似无意义的量的方式, 有的时候这是混乱的根源. 也许还要再加上Infinity.

与大部分语言一样, javascript也分为原生类型和引用类型, 其中原生类型在拷贝, 参数传递和比较时时通过值的方式, 而引用类型都是通过引用的方式.

字符串为不可变类型, 任何的改变处理都是生成新字符串. 比较时为值比较. 字符串的值比较我个人认为时更加自然的做法, 比Java那种变态的方式要自然的多. 你几乎要反复的告诉每一个新来的程序员, 字符串的值比较在java中要使用equals函数.

动态类型语言, 变量通过var定义.

支持用label方式的break和continue, 用于在多层循环中直接对外层循环进行break和continue.

完整并且传统的try, catch, finally异常机制. 除了C++没有finally不够完整以外, 几乎所有现在语言的异常都是这么设计的了.

字符串

 

javascript虽然说语法是类C的, 但是起点是Java, 所以尽管设计的面向对象系统虽然不是传统的模版式的, 但是javascript中的字符串都是对象.

 

"hello, world".length

// out: 12

上述的代码在现在已经不稀奇了, 但是相对C++来说还是更先进的.(可见C++多落后了)

 

var str = "hello" + "," + "world!";

console.log(str);

// out: hello,world!

字符串支持+操作符作为字符串连接.

 

javascript有个奇怪的地方是字符串和数字同时使用时:

 

console.log("3" + 4 + 5);

// out: 345

console.log(4 + 5 + "3");

// out: 93

也就是说, 相对一些语言(比如php)会自动的将字符串转为数字来说, javascript是倾向于将数字转为字符串的. 其实因为这种用法过于灵活, 即使是Ruby和Python这样以灵活著称的语言都是不允许这样的自动类型转换的.

更诡异的还不只这些, 对于加法来说是如此, 对于乘法来说又是另外一回事:

 

console.log("3" * 4);

// out: 12

 

console.log("3" * "4");

// out: 12

在乘法运算中, 因为javascript的字符串并没有像Ruby, Python一样对乘法的运算做出特殊解释(字符串的乘法表示重复), 所以默认会将字符串转为整数进行运算, 更诡异的是, 就算是两个字符串, 同样也会不报错的进行整数转换并且运算.

 

函数

 

函数在javascript中是第一类值, 同时还支持闭包. 这是javascript构成对象的基础.

 

function add(x, y) {

  return x + y;

}

 

var sub = function(x, y) {

  return x - y;

}

 

add(2, 3);

sub(5, 3);

// out: 5

// out: 2

有上述两种函数构造形式, 在调用时没有区别. 其中第一种方法和传统的函数定义方式一样, 而第二种实际上就是匿名函数的定义方式了. 只不过因为javascript中函数是第一类值, 所以可以很方便的赋值.

 

匿名函数

 

匿名函数也被称为lambda, 是个很方便和有用的特性, 加上对闭包的支持, 以此衍生了很多特性. 也因此成就了javascript类函数语言的特性.

 

var caller = function(fun, leftParam, rightParam) {

  fun(leftParam, rightParam);

}

 

caller(function(a, b) { console.log(a+b); }, 10, 20);

// out: 30

如上例所示, 匿名函数很重要的一个应用就是用于很方便的构建高阶函数. 也许上例有些太生造, 最常用的一个特性可能就是排序了, 因为排序的规则可能很多, 一般排序函数都允许再传入一个函数作为参数, 来指定排序的规则. 比如再javascript中, 普通的排序函数有些奇怪, 默认是按照字符串排序的. 见下例:

 

a = [1, 3, 2, 10, 20];

console.log(a.sort());

// out: [ 1, 10, 2, 20, 3 ]

这在大部分时候估计都不是我们要的做法, 默认这样子我是第一次看见, 这就像字符串和整数想加最后变成字符串一样诡异, 也许javascript本身设计的时候是作为前端检验表单啥为主的语言, 所以对字符串这么偏爱吧. 幸运的是, sort函数还是可以传入一个函数作为排序规则的. 见下例:

 

a = [1, 3, 2, 10, 20];

console.log( a.sort( function(a, b) { return a - b; } ) );

// out: [ 1, 2, 3, 10, 20 ]

因为匿名函数和递归在javascript中使用的都比一般语言要多, 所以提供了arguments.callee用于表示当前调用的函数, 以方便匿名函数的递归调用, 事实上, 相对一般用函数名的递归调用方式, 这种方式要更加符合DRY(Dont Repeat Yourself)原则, 因为当函数名更改的时候, 不用再更改递归调用的函数名了.

 

var factorial = function(n) {

  if (n <= 1) {

    return 1;

  }

  else {

    return n * arguments.callee(n - 1);

  }

}

 

factorial(4);

// out: 24

更有意思的是, arguments.callee在javascript的严格模式中是禁止的, 简单的说就是这种调用方法是官方不推荐使用的错误用法, 在将来甚至有可能废除, mozilla的解释是这种更DRY的用例本身很”weak”, 但是却阻止了inline优化的进行, 因为这种方式是通过引用un-inlined函数实现的, 也只有函数un-inlined时, arguments.callee才可以引用到.

事实上, 我觉得这简直是因噎废食的做法, 因为现在虽然是这样实现的, 但是完全可以通过更好的语法分析, 然后进行编译器的优化, 而不是因此废弃这样有用的语法. 这种用法绝对不像是官方说的那么”weak”, 要知道, DRY几乎是软件设计领域头等重要的原则.

 

闭包

 

一个闭包就是一个函数和被创建的函数中的范围对象的组合. 因为闭包的强大特性和带来的方便, 很多传统的语言都逐渐了加入了对其的支持, 很多时候, 甚至被视为一个语言是否还算是跟上时代的标志.

 

function makeIncrementor(base) {

  var count = base;

  return function(num) {

    count += num;

    return count;

  }

}

 

obj1 = makeIncrementor(10);

obj2 = makeIncrementor(20);

 

obj1(1);

// out: 11

obj1(1);

// out: 12

 

obj2(2);

// out: 22

obj2(2);

// out: 24

上面的例子较好的展示了闭包的特性, 可以获得上层函数的参数和变量, 并且各自互相独立, 因为闭包对局部状态的保存, 很多时候能当作一个对象来使用.

 

灵活的参数调用

 

function add(x, y) {

  return x + y;

}

 

add(2, 3, 4);

add();

add(2);

 

// out: 5

// out: NaN

// out: NaN

上述代码在调用时不会发生错误, 而是直接把后面的参数抛弃掉.

甚至于, 后面的两个参数不够的函数调用, 会返回NaN, 也不会发生错误.

本质上是因为一旦函数调用参数不够时, 后面的参数都会被置为undefined. 所以虽然javascript不支持默认参数, 但是可以模拟出来.

 

function mul(x, y) {

  if (y === undefined) {

    return x * 10;

  }

 

  return x * y;

}

 

mul(10);

// out:  100

更灵活的语法是可以通过arguments变量来获取参数, 这样可以支持任意数量的函数参数.

 

function add() {

    var sum = 0;

    for (var i = 0, j = arguments.length; i < j; i++) {

        sum += arguments[i];

    }

    return sum;

}

 

add(2, 3, 4, 5);

// out: 14

函数级作用域

 

javascript只有函数级别的作用域, 函数外都是全局作用域, 没有块级作用域. 意味着类似for, while, if等块中定义的实际是全局变量. 这个设定在现代语言中是逆天的. 于是, 借助匿名函数, 人们想出了更加诡异的解决方案来模拟块级作用域.

 

for (var i = 0; i < 10; ++i) {

  console.log(i);

}

 

console.log(i);   // 此时i仍然可用.

 

(function() {

  for (var j = 0; j < 10; ++j) {

    console.log(j);

  }

 

 })();

 

console.log(j); // ReferenceError: j is not defined.

数组

 

javascript的数组比想象的要灵活, 支持用超出索引的引用来添加元素, 这个我只在ruby和php中见过, 连python都不支持. 当然, 这种设计虽然灵活, 但是容易出现很隐晦的错误, 最终是好是坏也难以评价.

 

a = [0, 1, 2];

a[a.length] = 3;

a.push(4);

 

console.log(a);

// [ 0, 1, 2, 3, 4 ]

上述两种在数组后面添加元素的方法是等效的, 假如添加的元素不是数组的下一个元素(即跳跃式添加的话), 中间会用undefined填充.

 

对象

 

javascript的对象本质上就是一个hash表的集合.

 

var obj = new Object();

var obj2 = {};

有上述两种语法用于创建空对象, 其中第二种被称为’literal’语法, 也是常用的数据格式Json的基础.

 

因为是hash表, 所以动态添加内容不在话下.

 

var obj = new Object();

obj.name = "Simon";

obj.hello = function() {

  console.log("hello," + this.name);

}

 

obj.hello();

// out: hello,Simon.

有些不一样的是, 因为javascript中函数是第一类值, 所以可以很自然的在这个对象中添加函数, 完成完整的数据封装. 用{}来初始化上述对象的话, 会更加简单:

 

var obj = {

  name : "Simon",

  hello : function() {

    console.log("hello," + this.name);

  }

}

正因为其实对象就是一个关联数组, 所以同样可以用for in来遍历, 作用就像是python中的dir一样. 类似这种自审视的功能, 在传统静态语言是较为稀缺的, 在那种语言里这种功能叫做反射. 相配套的还有typeof操作符, hasOwnProperty, propertyIsEnumerable, isPrototypeof函数.

 

for (var name in obj) {

  console.log(name);

}

 

// out: name

// out: hello

更进一步, 你甚至可以通过obj["hello"]()这种调用关联数组的方式来调用对象中的函数.

 

面向对象

 

javascript算是第一个让大家知道这个世界上除了从C++一派来的class-based(模版式)的类定义方式, 还有类似self语言的prototype(原型)方式的流行语言. 虽然lua也是prototype方式, 但是毕竟只在游戏圈子里面流行.

 

自定义对象:

 

function Rectangle(w, h) {

  this.width = w;

  this.height = h;

  this.area = function() { return this.width * this.height; }

}

 

var rect1 = new Rectangle(2, 4);

var rect2 = new Rectangle(8.5, 11);

 

console.log(rect1.are());

// out: 8

以上代码用类似构造函数的方式处创建了两个类型为Rectangle的对象. 注意和以前创建对象的区别, 以前我们都是从Object直接开始构建, 那样在构建多个对象时远不如这种构造函数方式方便. 用这种方法, 我们就能得到简单的类似class-based对象创建的方法. 只是创建的是构造函数, 不是一个class而已.

 

不过, 上面代码并不完美, 最大的问题在于每个创建的对象都有一个自己的area函数, 而实际上, 所有的对象只需要指向一个共同的area函数即可, 这也是C++等class-based语言的做法. 为每个对象都创建一个函数, 无论是运行效率还是内存占用效率都不恰当. javascript提供的解决方案就是prototype, 在函数对象中默认都会初始化一个prototype的变量, 这个变量中有的所有函数最终都会被这个函数新创建的对象拥有, 并且拥有的还都是引用, 见代码:

 

function Rectangle(w, h) {

  this.width = w;

  this.height = h;

}

 

Rectangle.prototype.area = function() {  return this.width * this.height; };

 

var rect1 = new Rectangle(2, 4);

console.log(rect1.are());

// out: 8

类属性(Class Properties)

 

在class-based语言中, 有的属性可以直接通过类名使用, 并且一个类的所有对象共享同一个对象. 在javascript因为所有的函数本身就是对象, 构造函数也不例外, 所以可以通过在构造函数上直接添加属性来实现这样的特性.

 

Rectangle.UNIT = new Rectangle(1, 1);

其实, 类似的用法javascript本身就有, 比如Number.MAX_VALUE就是这样的类属性.

 

类方法(Class Methods)

 

和类属性一样, 在构造函数上创建对象, 就能模拟出class-based语言中类方法. 这里不累述了.

 

私有成员(Private Members)

 

在class-based语言中(Python例外), 一般都有对不同的成员设置不同访问权限的方法. 比如C++的prvate, public, protected, 在javascript, 通过上述方式创建的对象, 你可以看成都是默认为public的, 但是也的确有办法让外部访问不了内部的变量.

 

简单的方法

 

此方法来自JavaScript The Definitive Guide, 代码如下:

 

function Rectangle(w, h) {

  this.getWidth = function() { return w; }

  this.getHeight = function() { return h; }

}

 

Rectangle.prototype.area = function() {

  return this.getWidth() * this.getHeight();

}

 

var rect = new Rectangle(2, 3);

console.log( rect.area() );

// out: 6

此时, 无论是在对象外还是在对象内部, 都只能通过访问函数(getWidth和getHeight)获得成员变量.

 

Crockford的办法

 

事实上, 上面的简单办法没有在根本上解决问题, 只是限定需要通过访问函数了而已, 外部还是能访问对象的内部变量. Crckford据说是第一个发现了javascript创建真正私有变量的技术. 该技术主要在Private Members in JavaScript有描述.

 

function Rectangle(w, h) {

  var width = w;

  var height = h;

 

  this.area = function() {

    return width * height;

  }

}

 

var rect = new Rectangle(2, 3);

console.log(rect.area());

该方法利用了javascript的闭包特性, 此时width和height在外部彻底无法访问. 只有函数内部才能访问. 同样的, 私有的函数也可以通过一样的方法实现, 但是, 这个方法我感觉还是不够完美, 因为很显然的原因, 此时需要访问私有变量的函数都只能在构造函数中直接定义, 不能再使用prototype变量了, 也就是会有前面提到的每个对象都会有个新函数的问题.

 

模拟class-based的继承

 

相关内容见Douglas Crockford的Classical Inheritance in JavaScript. 我个人因为对这种奇怪的方式比较反感, 所以不太想去使用, 这里就不进行描述了. 需要提及的是, 假如真的需要class-based的继承的话, 在最新版的javascript 2.0规范(ECMAScript 5)中你能找到你想要的真正的类. 虽然相关标准还在进行当中, 可能还需要几年才能实际使用.

语言的发展道路基本上是趋同的, 程序社区有较为公认的标准, 所以PHP也在新的版本中加入了完整的面向对象支持, 而C++在11标准里面加入了闭包. 而Java和C#在新版本中不仅加入了闭包, 还增加了模版.

当javascript 2.0加入了class以后, 可能将来使用javascript就和C++等语言区别不大了. 可能会更像UnityScript.

 

基于原型的继承

 

这种继承方式和class-based的继承不一样, 直接使用了javascript的prototype特性, 在真正的class没有出来之前, 我个人对这样的方式好感更多. 主要可以参考的还是Douglas Crockford的文章, 见Prototypal Inheritance in JavaScript

简单的说就是子类讲自己的prototype变量设为想要继承的父类对象, 根据javascript的特性, 当一个函数找不到时, 会在prototype中寻找, 也就相当于子类没有重载时直接使用父类的函数. 当一个函数可以找到时, 会直接使用子类的函数, 这相当于子类重载了父类的相关函数.

因为我还是不准备使用这个方法, 所以这里还是不加描述了.

 

极简主义法

 

该方法我第一次是在阮一峰的网络日志上看到的, 见Javascript定义类(class)的三种方法.

就阮一峰描述, 该方法最先由荷兰人Gabor de Mooij在Object oriented programming in Javascript中提出.

该方法不使用Object.create和prototype特性, 本质上是新增一个自己约定的构造函数, 自己模拟了一个类似prototype的原型机制, 代码相对来说比较简单容易理解. 但是其实已经颠覆了本节前面提到的所有内容, 比如没有使用prototype和new.

 

类的创建

 

上面的Rectange类, 可以改为下面的方式实现.

 

var Rectangle = {

   createNew: function(w, h) {

                var rect = {};

                rect.width = w;

                rect.height = h;

                rect.area = function() { return this.width * this.height; };

                return rect;

              }

 }

 

 var rect = Rectangle.createNew(2, 3);

 console.log(rect.area());

继承

 

需要注意的是, 此时的Rectangle是一个单纯的对象, 而不再如传统方式一样是一个函数(当然, 其实也是对象). 这也是一个更容易理解的优点. 然后, 我们可以简单的在子类的createNew函数中先创建出要继承的对象, 然后继续修改该对象直到达到我们的要求. 如下:

 

var SubRectangle = {

  createNew: function(w, h) {

               var rect = Rectangle.createNew(w, h);

               rect.perimeter = function() { return this.width * 2 + this.height * 2; };

               return rect;

             }

}

 

var rect = SubRectangle.createNew(2, 3);

console.log(rect.area());

console.log(rect.perimeter());

私有成员及类的属性

 

我在新的SubRectangle子类中新增了perimeter函数, 用于计算周长, 可以看到使用的方法和传统的继承非常的像.

根据这个思路和前面提到的传统方法, 私有变量和类的属性, 方法都是很简单的事情.

 

var Rectangle = {

   createNew: function(w, h) {

                var rect = {};

                var width = w;    // private

                var height = h;   // private

                rect.area = function() { return width * height; };

                return rect;

              }

 }

 Rectangle.UNIT = Rectangle.createNew(1, 1); // class propertie

 

 var rect = Rectangle.createNew(2, 3);

 console.log(rect.area());

 console.log(Rectangle.UNIT.area());

共用函数

 

这个方法总的来说非常简单直观和直接, 没有必要像Douglas Crockford的方法一样需要创建较多的辅助函数来实现. 但是, 其实还是像前面没有用到prototype的解决方法一样, 每个对象的成员函数都是独立的, 假如对效率和内存比较在意的话, 可以使用引用外部函数的方法来优化. 如下:

 

var Rectangle = { 

  _area: function() { 

    return this.width * this.height; 

 }, 

  createNew: function(w, h) { 

               var rect = {}; 

               rect.width = w; 

               rect.height = h; 

               rect.area = Rectangle._area; 

               return rect; 

             } 

}

这个方法能解决函数有多份的问题, 但是同时带来的问题就是无法访问私有成员, 同时会给外部的Rectangle增加一些接口, 虽然可以通过命名来告诉调用者, 这些接口是私有的. 具体用哪种方法, 就看是注重效率还是注重代码本身的设计了.

 

 

, 从Ruby到Lisp, 然后是C#, 既然已经疯狂了, 就顺面学习一下javascript吧. 对javascript的印象一直不佳, 从骂脏话最多的使用...

本文由威尼斯发布于Web前端,转载请注明出处:其实也不是完全没有用过javascript,(子对象的原

关键词: