分类 技术笔记 下的文章

JavaScript 规范编程笔记:对象

对象

没有对象我可以试着 new 一个
在其中我可以重载甚至覆盖任何一种方法
但是我却无法重载对你的思念
也许命中注定
你在我的世界里永远的烙上了静态的属性
而我不慎调用了爱你这个方法
当我义无返顾的把自己作为参数传进这个方法时
我才发现爱上你是一个死循环

JavaScript的简单类型包括数字、字符串、布尔值(true和false)、null值和undefined值,其他所有的值都是对象。在JavaScript中,数组是对象,函数是对象,正则表达式是对象,当然,对象自然也是对象。对象是属性的容器,其中每个属性都拥有名字和值。属性的名字可以是包括空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。

JavaScript包括一个原型链特性,允许对象继承另一个对象的属性。

1. 对象字面量

对象字面量提供了一种非常方便的创建新对象值得表示法。一个对象字面量就是包裹在一对花括号中的零或多个“名/值”对。

var empty_object = {};

var stooge = {
    "first-name": "Jerome",
    "last-name": "Howard"   
};

对象是可嵌套的。

var flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATA: "SYD",
        time: "2004-09-22",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2004-09-23",
        city: "Los Angeles"
    }
};

2. 检索

要检索对象中包含的值,可以采用在[]后缀中括住一个字符串表达式的方式。如果字符串表达式是一个常数,也可以使用.表示法代替。

stooge["first-name"]   // "Joe"
flight.departure.IATA  // "SYD"

如果尝试检索一个并不存在的成员元素的值,将返回一个undefined值。

stooge["middle-name"]   // undefined
flight.status           // undefined

尝试检索一个undefined值将会导致TypeError异常。这可以通过&&运算符来避免错误。

flight.equipment    // undefined
flight.equipment.model // throw "TypeError"
flight.equipment && flight.equipment.model  // undefined

3. 引用

对象通过引用来传递,它们永远不会被拷贝:

var x = stooge;
x.nickname = 'Curly';
var nick = stoogee.nickname;   // 'Curly'

var a = {}, b = {}, c = {};  // a、b和c每个都引用一个不同的空对象
a = b = c = {};  // a、b和c都应用同一个空对象

4. 原型

每个对象都连接到一个原型对象,并且它可以从中继承属性。所有通过对象字面量创建的对象都连接到 Object.prototype 这个JavaScript中标准的对象。

当你创建一个新对象时,你可以选择某个对象作为它的原型。如下我们将给Object增加一个beget方法,这个方法将创建一个使用原对象作为其原型的新对象。

if(typeof Object.beget !== 'function'){
    Object.beget = function(o){
        var F = function(){};
        F.prototype = o;
        return new F();
    };
}

var another_stooge = Object.beget(stooge);

如果我们尝试去获取对象的某个属性值,且该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果哪个原型对象也没有该属性,那么再从它的原型中寻找,依次类推,直到该过程最后到达终点Object.prototype.如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程称为委托

原型关系是一种动态的关系,如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

stooge.profession = 'actor';
another_stooge.profession   // 'actor'

5. 反射

检查对象并确定对象有什么属性是很容易的事情,只要试着去检索该属性并验证取得的值。typeof操作符对确定属性的类型很有帮助:

typeof flight.number    // 'number'
typeof flight.status    // 'string'
typeof flight.arrival   // 'object'
typeof flight.manifest  // 'undefined'

但是注意原型链中德任何属性也会产生一个值:

typeof flight.toString  // 'function'
typeof flight.constructor // 'function'

可以使用 hasOwnProperty 方法剔除这些不需要的属性:

flight.hasOwnProperty('number')       // true
flight.hasOwnProperty('constructor')  // false

6. 枚举

for in 语句可用来遍历一个对象中德所有属性名。并且遍历过程将会列出所有的属性——包括函数和你可能不关心的原型中德属性——所以有必要过滤掉那些不想要的值。

var name;
for (name in another_stooge) {
    if(typeof another_stooge[name] !== 'function') {
        document.writeln(name + ': ' + another_stooge[name]);
    }
}

属性名出现的顺序是不确定的,如果想要确保属性以特定的顺序出现,最好的方法就是完全避免使用 for in 语句,而是创建一个数组,在其中以正确的顺序包含属性名,通过使用 for 而不是 for in,可以得到我们想要的属性。

7. 删除

delete 运算符可以用来删除对象的属性。它将会移除对象中确定包含的属性。它不会触及原型中德任何对象。删除对象的属性可能会让来自原型中的属性浮现出来:

another_stooge.nickname // 'Moe'
// 删除 another_stooge 的 nickname 属性,从而暴露出原型的 nickname 属性
delete another_stooge.nickname;
another_stooge.nickname  // 'Curly'

8. 减少全局变量污染

最小化使用全局变量的一个方法是在你的应用中只创建唯一一个全局变量:

var MYAPP = {};

然后将此变量作为你的应用的容器:

MYAPP.stooge = {
    "first-name": "Joe",
    "last-name": "Howard"   
};

MYAPP.flight = {
    ....
}

JavaScript 规范编程笔记:语法

JavaScript 语法

本节将简要介绍JavaScript的语法,并简要地概述其语言结构。

1. 注释

JavaScript提供了两种注释形式,一种是用/* */包围的块状注释,另一种是以//为开头的行注释。注释应该被充分用来提高程序的可读性。必须注意的是,注释一定要精确的描述代码。没有用的注释比没有注释更糟糕。

其中,块注释对于被注释的代码块来说是不安全的额。例如下面的代码将导致一个语法错误:

/*
    var rm_a = /a*/.match(s);
*/

因此应避免使用/* */注释,而用//注释代替它。

2. 标识符

JavaScript保留字列表:

breakdeletefunctionreturntypeof
casedoifswitchvar
catchelseinthisvoid
continuefalseinstanceofthrowwhile
debuggerfinallynewtruewith
defaultfornulltry 

JavaScript未来保留字(这些单词虽然现在没有用到JavaScript中,但将来有可能用到):

abstractdoublegotonativestatic
booleanenumimplementspackagesuper
byteexportimportprivatesynchronized
charextendsintprotectedthrows
classfinalinterfacepublictransient
constfloatlongshortvolatile

注意,其中不包括undefinedNaNInfinity等这些本应被保留的字。

3. 数字

JavaScript中只有一个单一的数字类型,它在内部被表示为64位的浮点数,和Java的double一样。因此11.0是相同的值。

指数表示法,如100可表示为1e2。负数可以用前缀运算符-来构成。值NaN是一个数值,它表示一个不能产生正常结果的运算结果。NaN不等于任何值,包括它自己。你可以使用函数isNaN(number)检测NaN。值Infinity表示所有大于1.79769313486231570e+308的值。JavaScript提供了一个对象Math,它包括一套作用于数字的方法。

4. 字符串

字符串字面量使用单引号或双引号包围,它可能包含0个或多个字符。\(反斜杠)是转义符。JavaScript在被创建的时候,Unicode是一个16位的字符集,因此JavaScript中德所有字符都是16位的。

字符串有一个length属性,例如"seven".length是5。字符串使用+运算符进行连接,此外JavaScript还为字符串提供了一些方法。

5. 语句

一个编译单元包含一组可执行的语句。在web浏览器中,每个<script>标签都提供一个被编译且立即执行的编译单元。因为缺少链接器,JavaScript把它们一起抛入一个公共的全局名字空间中。

代码块是包在一对花括号的一组语句。不像许多其他的语言,JavaScript中德代码块不会创建一个新的作用域,因此变量应该被定义在函数的顶端,而不是代码块中。

var语句被用在函数的内部时,它定义了这个函数的私有变量。

下面的值被当作假(false):

  • false
  • null
  • undefined
  • 空字符串 ''
  • 数字 0
  • 数字 NaN

其他所有的值都被当作真,包括true、字符串"false",以及所有的对象。

for in 语句会枚举一个对象的所有属性名(或键名),在每次循环中,对象的另一个属性名字符串被赋值给for和in之间的变量。通常你需要通过检测object.hasOwnProperty(variable)来确定这个属性名就是该对象的成员,还是从其原型链里找到的。

for(myvar in obj){
    if(obj.hasOwnProperty(myvar)){
        ...
    }
}

6. 表达式

最简单的表达式是字面量值(比如字符串或数字)、变量、内置的值(true、false、null、undefined、NaN和Infinity)、以new前导的调用表达式、以delete前导的属性存取表达式、包在圆括号中德表达式、以一个前缀运算符作为前导的表达式,或者表达式后面跟着:

  • 一个插入运算符与另一个表达式;
  • 三元运算符?后面跟着另一个表达式,然后接一个:,再然后接第三个表达式;
  • 一个函数调用;
  • 一个属性存取表达式。

函数调用引发函数的执行,函数调用运算符是跟随在函数名后面的一对圆括号。圆括号中可能包含将会传递给这个函数的参数。一个属性存取表达式用于指定一个对象或数组的属性或元素。

7. 字面量

对象字面量是一种方便指定新对象的表示法。属性名可以是标示符或字符串。这些名字被当作字面量名而不是变量名来对待,所以对象的属性名在编译时才能知道。属性的值就是表达式。

8. 函数

函数字面量定义了函数值,它可以有一个可选的名字,用于递归的调用自己。它可以指定一个参数列表,这些参数将作为变量由调用时传递的实际参数(arguments)初始化。函数的主体包括变量定义的语句。

JavaScript 规范编程:前言

JavaScript 规范编程

我一直努力去写高可维护性、高扩展性的JavaScript程序,但结果往往是令人沮丧的。从自己修改程序以及其他开发人员维护那些代码时的反馈来看,离高可维护性和高可扩展性还很远。JavaScript是一门令人惊讶的强大语言,它不按常规出牌,往往在其它编程语言那里获得的经验在它这里往往是走不通甚至是相反的。但是它又是一门轻量级的语言,在对这门语言没有太多了解,甚至对编程都没有太多了解的情况下,你也能用它来完成工作。它是一门拥有极强表达能力的语言。当你知道要做什么时,它甚至能表现得更好。然而,编程毕竟是很困难的事情,绝不应该在对此一无所知时便开始你的工作。

“JavaScript 规范编程” 的大部分内容来自Douglas Crockford所著的《JavaScript 语言精粹》,此书剥开了JavaScript玷污的外衣,抽离出一个具有更好可靠性、可读性和可维护性的JavaScript子集,让你看到一门优雅的、轻量级的和非常富有表现力的语言。这样精心抽离的子集不拿来作为之后开发的规范,岂不是暴殄天物。故此,我以“JavaScript 规范编程”将写一系列文章,也可以说是这本书的读书笔记吧。

Ubuntu 13.04 安装后要做的几件事

系统安装已经变得很简单,但是安装完后离在其上开始进行开发却还很远;因为有很多事情还没有做,比如驱动、开发工具设置、系统设置等等,然而正是这些事情让进度卡壳。这次安装 Ubuntu 13.04其实是继续之前,之前安装失败了,重装也没有搞好。现在有点时间了,终于在周末搞的差不多了,先记录先来。

一、ATI驱动安装

在 Ubuntu 12.04 之前,一直使用这篇文章的方法安装即可,但是从 Ubuntu 12.10 开始,由于xorg的版本更新,而 ATI 驱动跟进太慢,AMD 最终放弃对 ATI Mobility Radeon HD 2xxx-4xxx系列显卡的支持,于是我就悲催鸟!

我的显卡是 ATI Mobility Radeon HD 4650,按照以前的方法安装失败,显卡不能驱动,风扇呼呼的扇,但温度还是很高。可通过一下命令查看显卡类型:

lspci -vvnn | grep VGA

运行命令后我的显示内容:

ping- SERR- FastB2B- DisINTx-
02:00.0 VGA compatible controller [0300]: Advanced Micro Devices [AMD] nee ATI RV730/M96 [Mobility Radeon HD 4650/5165] [1002:9480] (prog-if 00 [VGA controller])

幸亏有老外开发者专门做了 PPA 源,可以装打好补丁的 fglrx ,运行以下命令:

sudo add-apt-repository ppa:makson96/fglrx
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install fglrx-legacy

二、安装 Eclipse 及插件

安装 Eclipse 其实不能说叫安装,应该叫下载解压,从 www.eclipse.org 下载后解压到相应目录就可以运行了,当然需要JDK的支持,可以直接在软件中心安装 OpenJDK 。但是如果使用 Unity 环境就需要修复全局菜单bug,以及配置下 Unity 上的快捷方式。

1. 全局菜单配置

1.1 备份要修改的文件

sudo cp /usr/lib/gtk-2.0/2.10.0/menuproxies/libappmenu.so /usr/lib/gtk-2.0/2.10.0/menuproxies/libappmenu.so.bak

Ubuntu 13.04 中目录变更为以下:

# 64位操作系统
/usr/lib/x86_64-linux-gnu/gtk-2.0/2.10.0/menuproxies/libappmenu.so
# 32位操作系统
/usr/lib/i386-linux-gnu/gtk-2.0/2.10.0/menuproxies/libappmenu.so

1.2 用vim打开文件并修改

sudo vim /usr/lib/gtk-2.0/2.10.0/menuproxies/libappmenu.so
# 搜索Eclipse并修改为Xclipse
/Eclipse
rX
# 保存并退出
ZZ

- 阅读剩余部分 -

JavaScript的模块化:封装(闭包),继承(原型)

随着用户体验被越来越重视,前端的 JavaScript 越来越流行。看起来随随便便的 JavaScript 其实也可以像 Java 一样进行封装继承,只是看起来没那么明显而已。今天看了这篇文章,讲得非常好,有种顿悟的感觉,下面简单记录一下并加入一些自己的观点,更详细的看原文吧。

我们试图在页面上维护一个计数器对象 ticker ,这个对象维护一个数值 n 。随着用户的操作,我们可以增加一次计数(将数值 n 加上 1 ),但不能减少 n 或直接改变 n 。而且,我们需要时不时查看这个数值。

门户大开的 JSON 风格模块化

var ticker = {
    n:0,
    tick:function(){
        this.n++;
    },
};

这种方式书写自然,而且确实有效,我们需要增加一次计数时,就调用 ticker.tick() 方法,需要查询次数时,就访问 ticker.n 变量。但是,模块的使用者被允许自由地改变 n ,比如调用 ticker.n– 或者 ticker.n=-1。。我们并没有对 ticker 进行封装, ntick() 看上去是 ticker 的“成员”,但是它们的可访问性和 ticker 一样,都是全局性的(如果 ticker 是全局变量的话)。在封装性上,这种模块化的方式比下面这种更加可笑的方式,只好那么一点点(虽然对有些简单的应用来说,这一点点也足够了)。

var ticker = {};
var tickerN = 0;
var tickerTick = function(){
    tickerN++;
}

tickerTick();

值得注意的是,在 tick() 中,我访问的是 this.n ——这并不是因为 nticker 的成员,而是因为调用 tick() 的是 ticker 。事实上这里写成 ticker.n 会更好,因为如果调用 tick() 的不是 ticker ,而是其他什么东西,比如:

var func = ticker.tick;
func();

这时,调用 tick() 的其实是 window ,而函数执行时会试图访问 window.n 而出错。

JSON风格的模块化其实只是定义了一个模块的组织方式,并没有进行封装,它的成员其实都是全局变量。

作用域链和闭包

var config = {
    nStart:100,
    step:2
}

function ticker(config){
    var n = config.nStart;
    function tick(){
        n += config.step;
    }
}
console.log(ticker.n); // ->undefined

JavaScript 中只有函数具有作用域,即在函数体外无法访问函数内部的变量。上面的例子,从 tick()ticker() 再到全局,这就是 JavaScript 中的“作用域链”。

可是还有问题,那就是——怎么调用 tick()ticker() 的作用域将 tick() 也掩盖了起来。解决方法有两种:

  • 1)将需要调用方法作为返回值,正如我们将递增 n 的方法作为 ticker() 的返回值;
  • 2)设定外层作用域的变量,正如我们在 ticker() 中设置 getN

代码如下

var getN;
function ticker(config){
    var n = config.nStart;
    getN = function(){
        return n;
    };
    return function(){
        n += config.step;
    };
}

var tick = ticker({nStart:100,step:2});
tick();
console.log(getN()); // ->102

这时,变量 n 就处在“闭包”之中,在 ticker() 外部无法直接访问它,但是却可以通过两个方法来观察或操纵它。

在本节第一段代码中, ticker() 方法执行之后, ntick() 就被销毁了,直到下一次调用该函数时再创建;但是在第二段代码中, ticker() 执行之后, n 不会被销毁,因为 tick()getN() 可能访问它或改变它,浏览器会负责维持n。我对“闭包”的理解就是:用以保证 n 这种处在函数作用域内,函数执行结束后仍需维持,可能被通过其他方式访问的变量 不被销毁的机制。

可是,如果我需要维持两个具有相同功能的对象 ticker1ticker2 ,那该怎么办? ticker() 只有一个,总不能再写一遍吧?

new 运算符与构造函数

如果通过 new 运算符调用一个函数,就会创建一个新的对象,并使用该对象调用这个函数。在我的理解中,下面的代码中 t1t2 的构造过程是一样的。

function myClass(){}
var t1 = new myClass();
var t2 = {};
t2.func = myClass;
t2.func();
t2.func = undefined;

t1t2 都是新构造的对象, myClass() 就是构造函数了。类似的, ticker() 可以重新写成。

function TICKER(config){
    var n = config.nStart;
    this.getN = function(){
        return n;
    };
    this.tick = function(){
        n += config.step;
    }
}

var ticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
var ticker2 = new TICKER({nStart:20,step:3});
ticker2.tick();
ticker2.tick();
console.log(ticker2.getN()); // ->26

习惯上,构造函数采用大写。注意, TICKER() 仍然是个函数,而不是个纯粹的对象(之所以说“纯粹”,是因为函数实际上也是对象, TICKER() 是函数对象),闭包依旧有效,我们无法访问 ticker1.n

原型 prototype 与继承

上面这个 TICKER() 还是有缺陷,那就是, ticker1.tick()ticker2.tick() 是互相独立的!请看,每使用 new 运算符调用 TICKER() ,就会生成一个新的对象并生成一个新的函数绑定在这个新的对象上,每构造一个新的对象,浏览器就要开辟一块空间,存储 tick() 本身和 tick() 中的变量,这不是我们所期望的。我们期望 ticker1.tickticker2.tick 指向同一个函数对象。

JavaScript 中,除了 Object 对象,其他对象都有一个 prototype 属性,这个属性指向另一个对象。这“另一个对象”依旧有其原型对象,并形成原型链,最终指向 Object 对象。在某个对象上调用某方法时,如果发现这个对象没有指定的方法,那就在原型链上一次查找这个方法,直到 Object 对象。

函数也是对象,因此函数也有原型对象。当一个函数被声明出来时(也就是当函数对象被定义出来时),就会生成一个新的对象,这个对象的 prototype 属性指向 Object 对象,而且这个对象的 constructor 属性指向函数对象。

通过构造函数构造出的新对象,其原型指向构造函数的原型对象。所以我们可以在构造函数的原型对象上添加函数,这些函数就不是依赖于 ticker1ticker2 ,而是依赖于 TICKER 了。

为了访问闭包中的内容,对象必须有一些简洁的依赖于实例的方法,来访问闭包中的内容,然后在其 prototype 上定义复杂的公有方法来实现逻辑。实际上,例子中的 tick() 方法就已经足够简洁了,我们还是把它放回到 TICKER 中吧。下面实现一个复杂些的方法 tickTimes() ,它将允许调用者指定调用 tick() 的次数。

function TICKER(config){
    var n = config.nStart;
    this.getN = function(){
        return n;
    };
    this.tick = function(){
        n += config.step;
    };
}
TICKER.prototype.tickTimes = function(n){
    while(n>0){
        this.tick();
        n--;
    }
};
var ticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
var ticker2 = new TICKER({nStart:20,step:3});
ticker2.tickTimes(2);
console.log(ticker2.getN()); // ->26

这个 TICKER 就很好了。它封装了 n ,从对象外部无法直接改变它,而复杂的函数 tickTimes() 被定义在原型上,这个函数通过调用实例的小函数来操作对象中的数据。

所以,为了维持对象的封装性,我的建议是,将对数据的操作解耦为尽可能小的单元函数,在构造函数中定义为依赖于实例的(很多地方也称之为“私有”的),而将复杂的逻辑实现在原型上(即“公有”的)。

最后再说一些关于继承的话。实际上,当我们在原型上定义函数时,我们就已经用到了继承! JavaScript 中的继承比 C++ 中的更……呃……简单,或者说简陋。在 C++ 中,我们可能会定义一个 animal 类表示动物,然后再定义 bird 类继承 animal 类表示鸟类,但我想讨论的不是这样的继承(虽然这样的继承在 JavaScript 中也可以实现);我想讨论的继承在 C++ 中将是,定义一个 animal 类,然后实例化了一个 myAnimal 对象。对,这在 C++ 里就是实例化,但在 JavaScript 中是作为继承来对待的。

JavaScript 并不支持类,浏览器只管当前有哪些对象,而不会额外费心思地去管,这些对象是什么 class 的,应该具有怎样的结构。在我们的例子中, TICKER() 是个函数对象,我们可以对其赋值(TICKER=1),将其删掉(TICKER=undefined),但是正因为当前有 ticker1 和 ticker2 两个对象是通过 new 运算符调用它而来的, TICKER() 就充当了构造函数的作用,而 TICKER.prototype 对象,也就充当了类的作用。