阅读笔记《JavaScript语言精粹》

对象

1.检索属性

使用[]和.

2.引用传递

JavaScript的简单数据类型包括数字、字符串、布尔值、null值和undefined值。其它所有的值都是对象。数组是对象,函数是对象,正则表达式是对象。对象通过引用传递,它们永远不会被复制。

3.原型

当我们对某个对象做出改变时,不会触及该对象的原型,只有在检索值的时候才会被用到。原型连接在更新时是不起作用的。delete删除对象中的属性,它也不会触及原型链中的任何对象,删除对象的属性可能会让来自原型链中的属性透现出来。

4.枚举

for-in语句枚举过程会列出所有的属性——包括函数和你可能不关心的原型中的属性——所以有必要过滤掉那些你不想要得值。最为常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数。

5.减少全局变量污染

JavaScript依赖于全局变量来进行链接,应当把全局性的资源都纳入一个名称空间之下,你的程序与其它应用程序、组件或类库之间发生冲突的可能性就会显著降低。


函数

1.函数对象

函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype),每个函数在对象创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码。函数的与众不同之处在于它们可以被调用。

2.闭包

函数可以定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。

3.调用

除了声明时定义得形式参数,每个函数还接收两个附加的参数:this和arguments。参数this的值取决于调用的模式,这些模式在如何初始化关键参数this上存在差异。

  • 方法调用模式:当函数为对象的一个属性时,称它为该对象的方法,this绑定到该对象,方法可以通过this访问该对象的属性。

  • 函数调用模式:当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误。(应当绑定到外部父函数的this变量。)

  • 构造器调用模式:如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个对象。new前缀也会改变return语句的行为。(参照5.返回)

  • apply调用模式:函数是对象,所以也拥有方法。apply方法让我们构建一个参数数组传递给调用函数。apply接收两个参数,第一个是要绑定给this的值,第二个就是一个参数数组。

4.参数

当函数被调用时,会得到一个arguments参数,可以通过它访问函数所有的参数列表,这使得编写一个无须指定参数个数的函数成为可能。arguments拥有一个length属性,但它不是数组,并没有任何数组的方法。

5.返回

一个函数总会有返回值,如何没有指定就返回undefined。如果函数调用时前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。

6.扩充类型的功能

通过给基本类型增加方法,我们可以极大的提高语言的变现力。因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的对象实例上,哪怕对象示例是在方法被增加之前就创建好了。object.prototype.METHOD_NAME = ...

7.递归

递归函数可以非常高效的操作树形结构,遗憾的是,javas当前并没有提供递归优化。深度递归的函数可能会因为堆栈溢出而运行失败。

8.作用域

JavaScript不支持块作用域,只有函数作用域,意味着定义在函数中的参数和变量在函数外部不可见,而在内部任何位置定义的变量,在该函数任何地方都可见。所以,最好在函数体的顶部声明函数中可能用到的所有变量。

9.模块

使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象,通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。

模块模式的一般形式:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有化变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。

10.级联

级联技术可以产生出极富表现力的接口。它也能给那波构造“全能”接口的热潮降温,一个接口没有必要一次做太多事情。

obj.setPositionX(10)
.setPositionY(10)
.color("red")
.border("10px")
.tip("级联");

11.记忆

函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称作记忆。JavaScript的对象和数组要实现这种优化非常的方便。比如用递归计算Fibonacci数列...


继承

JavaScript是弱类型的语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。

在基于类的语言中,对象是类的示例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。

JavaScript中可能的继承模式有很多。

1.伪类

使用new前缀。伪类模式本意是想向面向对象靠拢,但它看起来格格不入。

2.原型

原型模式中会摒弃类,转而专注于对象,一个新对象可以继承一个旧对象的属性。

3.函数化(模块)

函数化模式有很大的灵活性。它相比伪类模式不仅带来的工作更少,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

4.部件

我们可以从一套部件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件处理特征的函数。


数组

数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素。数组是一种性能出色的数据结构。不幸的是,JavaScript没有像此类数组一样的数据结构。

作为替代,JavaScript提供了一种拥有一些类数组特征的对象。它把数组的下标转换成字符串,用其作为属性。它明显地比一个真正的数组慢,但它使用起来很方便。它的属性检索和更新与对象一模一样,只不过多一个可以用整数作为属性名的特性。

1.长度

每个数组都有一个length属性。和大多数其它语言不同,JavaScript数组的length是没有上界的。如果用大于等于当前length的数字作为下标来存储一个元素,那么length值会被增大以容纳新元素,不会发生越界错误。length属性的值是这个数组的最大整数属性名加上1:

var arr = [];
arr[100] = true;
//arr.length === 101

设置更大的length不会给数组分配更多的空间。而把length设小将导致所有下标大于等于新length的属性被删除。

2.删除

由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素。

var arr = [10,20,30];
delete arr[1];
// arr: [10, undefined, 30]

不幸的是,那样会在数组中留下一个空洞。因为前排的在被删除元素之后的元素保留着它们最初的属性。而你通常想要递减后面每个元素的属性。

JavaScript数组有一个splice方法用来进行数组元素的增加删除,对于大型数组来说可能会效率不高。

3.判断是否数组

JavaScript本身对于数组和对象的区别是混乱的。typeof运算符报告数组的类型是'object',这没有任何意义。我们可以通过is_array函数来弥补这个缺陷。

var is_array = function (value) {
return value && typeof value === 'object' && value.constructor === Array;
};

遗憾的是,它在识别不同的窗口(window)或帧(frame)里构造的数组时会失败。有一个更好的方式去判断一个对象是否为数组:

var is_array = function (value) {
return Object.prototype.toString.apply(value) === '[Object Array]';
};

代码风格

  1. 优秀的程序拥有一个前瞻性的结构,它会预见到未来才可能需要的修改,但不会让其成为过度的负担。
  2. 使用K&R风格,把 { 放在一行的结尾而不是下一行的开头,因为它会避免JavaScript的return语句中的一个可怕的设计错误。(插入分号)
  3. JavaScript拥有C语言的语法,但它的代码块没有作用域。所以在每个函数的开始部分声明所有的变量。
  4. JavaScript可以方便的使用全局变量,但随着程序的日益复杂,全局变量逐渐变得问题重重。对一个脚本应用或工具库,尽量只使用唯一一个全局变量。每个对象都有它自己的命名空间。使用闭包能提供进一步的信息隐藏。

毒瘤

展示JavaScript一些难以避免的问题特性。你必须知道这些问题并准备好应对措施。

1.全局变量

全局变量可以被程序的任何部分在任意时间修改,它们使得程序的行为变得极度复杂。在程序中使用全局变量降低了程序的可靠性。

全局变量使得在同一个程序中运行独立的子程序变得困难。如果某些全局变量的名称碰巧和子程序中的变量名称相同,那么他们将会相互冲突,可能导致程序无法运行,而且通常难以调试。

JavaScript的问题不仅在于它允许使用全局变量,而且在于它依赖全局变量。JavaScript没有连接器,所有的编译单元都会载入一个公共的全局对象中。

共有3种方式定义全局变量:

  • 第1种是在任何函数之外放置一个var语句:
var foo = value;
  • 第2种是直接给全局对象添加一个属性。全局对象时所有全局变量的容器。在Web浏览器里,全局对象名为window:
window.foo = value;
  • 第3种是直接使用未经声明的变量,这被称为隐身的全局变量:
foo = value;

2.作用域

JavaScript的语法来源于C。它采用了块语法,却没有提供块级作用域:代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。所以应该在每个函数的开头部分声明所有的变量。

3.自动插入分号

JavaScript有一个自动修复机制,它试图通过自动插入分号来修正有缺损的程序。但是,千万不要指望它,它可能会掩盖更为严重的错误。

return返回一个值时,这个值表达式的开始部分必须和return位于同一行,否则return后面被插入分号后,返回结果将是undefined。

4.Unicode

JavaScript设计之初,Unicode预期最多会有65536个字符。但从那以后它的容量慢慢增长到了拥有一百万个字符。

JavaScript的字符是16位的,足以覆盖原有的65536个字符。剩下的百万字符中的每一个都可以用一对字符来表示。Unicode把一对字符视为一个单一的字符。而JavaScript认为一对字符是两个不同的字符。

5.typeof

typeof null返回的是'Object',而不是null

对于正则表达式typeof /a/各种JavaScript的实现版本不太一致

6.parseInt

parseInt把一个字符串转换为整数,它在遇到非数字时会停止解析。

parseInt("16")与parseInt("16 haha")会产生相同的结果,它不会提醒我们出现了额外的文本。

7.+运算符

+运算符用于加法运算或字符串连接。如果其中一个运算数是一个(空)字符串,它会把另一个运算数转换成字符串并返回。如果你打算用 + 去做加法运算,请确保两个运算数都是Number。这个复杂的行为是bug的常见来源。

8.NaN

typeof NaN === 'number' //true

但是NaN表示的不是一个数字,该值可能会在试图把非数字形式的字符串转换为数字时产生。

typeof不能辨别数字和NaN,而且NaN也不等同于它自己:

NaN === NaN //false
NaN !== NaN //true
isNaN(NaN) //true
isNaN(0) //false
isNaN("0") //false
isNaN("haha") //true

9.伪数组

arguments不是一个数组,它只是一个有着length成员属性的对象。

检测数组:

if(Object.prototype.toString.apply(VALUE) == "[object Array]") {
//VALUE是一个数组
}

10.假值

//值 (类型)
0 //Number
NaN //Number
"" //String
false //Boolean
null //Object
undefined //Undefined

undefined和NaN并不是常量。它们居然是全局变量,而且你可以改变它们的值。千万不要这样做。

11.hasOwnProperty

hasOwnProperty方法可以用做一个过滤器去避开for in语句中的一个隐患。遗憾的是,它是一个方法,而不是一个运算符,所以在任何对象中,它可能会被一个不同的函数甚至一个非函数的值所替换。

11.对象

JavaScript的对象永远不会是真的空对象,因为它们可以从原型链中取得成员属性。有时候那会带来麻烦。


糟粕

展示JavaScript一些有问题的特性,但我们很容易就能避免它们。

1.==

如果两个运算数类型不同,和!=会强制转换运算数值的类型。永远不要使用它们!

使用=和!,只有在两个运算数类型相同且拥有相同值时=才会返回true,!==返回false。

2.with语句

with语句本意是用来快捷访问对象的属性,不幸的是,它的结果可能有时不可预料,所以应该避免使用它。且它本身就严重影响了JavaScript处理器的速度,因为它阻断了变量名的词法作用域。

3.eval

eval使得代码更加难以阅读。还使得性能显著降低,因为它需要运行编译器,还会让JSLint无法检测其中的问题。

浏览器提供的setTimeout和setInterval函数,参数传人字符串时也会调用eval。

4.continus语句

continue语句跳到循环的顶部,通过移除continue语句之后的代码,性能都会得到改善!

5.switch穿越

不要使用switch穿越,这会让我们更加容易发现不小心造成的case条件穿越导致的bug。

6.缺少块的语句

if, while, do 或 for 语句可以接受一个括在花括号中的代码,也可以接受单行语句。但那会模糊了程序的结构,不要这么做。

7.位运算符

JavaScript的执行环境一般接触不到硬件,所以非常慢。JavaScript很少被用来执行位操作。

8.function语句

不要在if中使用function语句,不同的浏览器在解析时的处理是不同的,这就会造成可移植性的问题。

一个语句不能以函数表达式开头,因为官方的语法假定单词function开头的语句是一个function语句。解决方法是把函数调用括在一对圆括号中:

(function () {
//
}());

9.类型的包装对象

JavaScript有一套类型的包装对象,例如:new Boolean(false)会返回一个对象,该对象有一个valueOf方法会返回被包装的值。这其实完全没有必要,并且又是还令人困惑。

不要使用new Boolean, new Number, new String, 也请避免使用new Objectnew Array,可使用 {} 和 [] 来代替。

10.new

使用new创建原型的新对象时,会涉及到this的绑定问题,如果在创建对象是忘记加上new,则会把this绑定到全局对象,造成全局变量污染。所以尽量避免去使用new。

11.void

在JavaScript中void是一个运算符,它接受一个运算数并返回undefined。这没什么用,且令人困惑。应避免去使用它。

最新文章

  1. 当div有边框图片的时候,怎么实现内部的p标签的水平和垂直居中
  2. bzoj3157国王奇遇记(秦九韶算法+矩乘)&&bzoj233AC达成
  3. Lind.DDD.Events领域事件介绍
  4. shape的简单用法
  5. JS中数组去除重复的方法
  6. jQuery 请指出'$'和'$.fn'的区别?或者说出'$.fn'的用途。
  7. nyoj_31
  8. NOI模拟赛Day2
  9. iOS - Apache Tomcat WebServer 服务器配置
  10. Apache 下SVN项目管理使用说明
  11. cdoj 1256 昊昊爱运动 预处理/前缀和
  12. HDU更多的学校比赛9场 HDU 4965Fast Matrix Calculation【矩阵运算+数学技巧】
  13. Scala内部类
  14. An Introduction to Variational Methods (5.1)
  15. Mongodb 批量Upsert
  16. TXLSReadWriteII 公式计算
  17. eclipse自动添加注释
  18. MySql 5.7 新特性概览
  19. log4j2常见配置
  20. 案例导入和导出Scott用户

热门文章

  1. CF839D Winter is here
  2. springmvc小结(上)
  3. selenium基础知识(概述、安装、IDE等)
  4. ETL测试小结
  5. Mac 开发配置手册
  6. String的非空判断、Integer的非空判断、list的大小判断,对象的非空判断
  7. GitHub Desktop 拉取 GitHub上 Tag 版本代码
  8. Linux 运维工程师学习成长路线上要经历哪四个阶段?
  9. MAC系统 输入管理员账户密码 登录不上
  10. Canvas状态的保存与恢复