本文共 6760 字,大约阅读时间需要 22 分钟。
使用[]和.
JavaScript的简单数据类型包括数字、字符串、布尔值、null值和undefined值。其它所有的值都是对象。数组是对象,函数是对象,正则表达式是对象。对象通过引用传递,它们永远不会被复制。
当我们对某个对象做出改变时,不会触及该对象的原型,只有在检索值的时候才会被用到。原型连接在更新时是不起作用的。delete删除对象中的属性,它也不会触及原型链中的任何对象,删除对象的属性可能会让来自原型链中的属性透现出来。
for-in语句枚举过程会列出所有的属性——包括函数和你可能不关心的原型中的属性——所以有必要过滤掉那些你不想要得值。最为常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数。
JavaScript依赖于全局变量来进行链接,应当把全局性的资源都纳入一个名称空间之下,你的程序与其它应用程序、组件或类库之间发生冲突的可能性就会显著降低。
函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype),每个函数在对象创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码。函数的与众不同之处在于它们可以被调用。
函数可以定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。
除了声明时定义得形式参数,每个函数还接收两个附加的参数:this和arguments。参数this的值取决于调用的模式,这些模式在如何初始化关键参数this上存在差异。
方法调用模式:当函数为对象的一个属性时,称它为该对象的方法,this绑定到该对象,方法可以通过this访问该对象的属性。
函数调用模式:当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误。(应当绑定到外部父函数的this变量。)
构造器调用模式:如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个对象。new前缀也会改变return语句的行为。(参照5.返回)
apply调用模式:函数是对象,所以也拥有方法。apply方法让我们构建一个参数数组传递给调用函数。apply接收两个参数,第一个是要绑定给this的值,第二个就是一个参数数组。
当函数被调用时,会得到一个arguments参数,可以通过它访问函数所有的参数列表,这使得编写一个无须指定参数个数的函数成为可能。arguments拥有一个length属性,但它不是数组,并没有任何数组的方法。
一个函数总会有返回值,如何没有指定就返回undefined。如果函数调用时前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。
通过给基本类型增加方法,我们可以极大的提高语言的变现力。因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的对象实例上,哪怕对象示例是在方法被增加之前就创建好了。object.prototype.METHOD_NAME = ...
递归函数可以非常高效的操作树形结构,遗憾的是,javas当前并没有提供递归优化。深度递归的函数可能会因为堆栈溢出而运行失败。
JavaScript不支持块作用域,只有函数作用域,意味着定义在函数中的参数和变量在函数外部不可见,而在内部任何位置定义的变量,在该函数任何地方都可见。所以,最好在函数体的顶部声明函数中可能用到的所有变量。
使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象,通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。
模块模式的一般形式:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有化变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。级联技术可以产生出极富表现力的接口。它也能给那波构造“全能”接口的热潮降温,一个接口没有必要一次做太多事情。
obj.setPositionX(10) .setPositionY(10) .color("red") .border("10px") .tip("级联");
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称作记忆。JavaScript的对象和数组要实现这种优化非常的方便。比如用递归计算Fibonacci数列…
JavaScript是弱类型的语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。
在基于类的语言中,对象是类的示例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。 JavaScript中可能的继承模式有很多。使用new前缀。伪类模式本意是想向面向对象靠拢,但它看起来格格不入。
原型模式中会摒弃类,转而专注于对象,一个新对象可以继承一个旧对象的属性。
函数化模式有很大的灵活性。它相比伪类模式不仅带来的工作更少,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。
我们可以从一套部件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件处理特征的函数。
数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素。数组是一种性能出色的数据结构。不幸的是,JavaScript没有像此类数组一样的数据结构。
作为替代,JavaScript提供了一种拥有一些类数组特征的对象。它把数组的下标转换成字符串,用其作为属性。它明显地比一个真正的数组慢,但它使用起来很方便。它的属性检索和更新与对象一模一样,只不过多一个可以用整数作为属性名的特性。每个数组都有一个length属性。和大多数其它语言不同,JavaScript数组的length是没有上界的。如果用大于等于当前length的数字作为下标来存储一个元素,那么length值会被增大以容纳新元素,不会发生越界错误。length属性的值是这个数组的最大整数属性名加上1:
var arr = [];arr[100] = true;//arr.length === 101
设置更大的length不会给数组分配更多的空间。而把length设小将导致所有下标大于等于新length的属性被删除。
由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素。
var arr = [10,20,30];delete arr[1];// arr: [10, undefined, 30]
不幸的是,那样会在数组中留下一个空洞。因为前排的在被删除元素之后的元素保留着它们最初的属性。而你通常想要递减后面每个元素的属性。
JavaScript数组有一个splice方法用来进行数组元素的增加删除,对于大型数组来说可能会效率不高。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]';};
展示JavaScript一些难以避免的问题特性。你必须知道这些问题并准备好应对措施。
全局变量可以被程序的任何部分在任意时间修改,它们使得程序的行为变得极度复杂。在程序中使用全局变量降低了程序的可靠性。
全局变量使得在同一个程序中运行独立的子程序变得困难。如果某些全局变量的名称碰巧和子程序中的变量名称相同,那么他们将会相互冲突,可能导致程序无法运行,而且通常难以调试。 JavaScript的问题不仅在于它允许使用全局变量,而且在于它依赖全局变量。JavaScript没有连接器,所有的编译单元都会载入一个公共的全局对象中。 共有3种方式定义全局变量:var foo = value;
window.foo = value;
foo = value;
JavaScript的语法来源于C。它采用了块语法,却没有提供块级作用域:代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。所以应该在每个函数的开头部分声明所有的变量。
JavaScript有一个自动修复机制,它试图通过自动插入分号来修正有缺损的程序。但是,千万不要指望它,它可能会掩盖更为严重的错误。
return返回一个值时,这个值表达式的开始部分必须和return位于同一行,否则return后面被插入分号后,返回结果将是undefined。JavaScript设计之初,Unicode预期最多会有65536个字符。但从那以后它的容量慢慢增长到了拥有一百万个字符。
JavaScript的字符是16位的,足以覆盖原有的65536个字符。剩下的百万字符中的每一个都可以用一对字符来表示。Unicode把一对字符视为一个单一的字符。而JavaScript认为一对字符是两个不同的字符。typeof null返回的是’Object’,而不是null
对于正则表达式typeof /a/
各种JavaScript的实现版本不太一致 parseInt把一个字符串转换为整数,它在遇到非数字时会停止解析。
parseInt(“16”)与parseInt(“16 haha”)会产生相同的结果,它不会提醒我们出现了额外的文本。+运算符用于加法运算或字符串连接。如果其中一个运算数是一个(空)字符串,它会把另一个运算数转换成字符串并返回。如果你打算用 + 去做加法运算,请确保两个运算数都是Number。这个复杂的行为是bug的常见来源。
typeof NaN === ‘number’ //true
但是NaN表示的不是一个数字,该值可能会在试图把非数字形式的字符串转换为数字时产生。 typeof不能辨别数字和NaN,而且NaN也不等同于它自己:NaN === NaN //falseNaN !== NaN //trueisNaN(NaN) //trueisNaN(0) //falseisNaN("0") //falseisNaN("haha") //true
arguments不是一个数组,它只是一个有着length成员属性的对象。
检测数组:if(Object.prototype.toString.apply(VALUE) == "[object Array]") { //VALUE是一个数组}
//值 (类型)0 //NumberNaN //Number"" //Stringfalse //Booleannull //Objectundefined //Undefined
undefined和NaN并不是常量。它们居然是全局变量,而且你可以改变它们的值。千万不要这样做。
hasOwnProperty方法可以用做一个过滤器去避开for in语句中的一个隐患。遗憾的是,它是一个方法,而不是一个运算符,所以在任何对象中,它可能会被一个不同的函数甚至一个非函数的值所替换。
JavaScript的对象永远不会是真的空对象,因为它们可以从原型链中取得成员属性。有时候那会带来麻烦。
展示JavaScript一些有问题的特性,但我们很容易就能避免它们。
如果两个运算数类型不同,==和!=会强制转换运算数值的类型。永远不要使用它们!
使用===和!==,只有在两个运算数类型相同且拥有相同值时===才会返回true,!==返回false。with语句本意是用来快捷访问对象的属性,不幸的是,它的结果可能有时不可预料,所以应该避免使用它。且它本身就严重影响了JavaScript处理器的速度,因为它阻断了变量名的词法作用域。
eval使得代码更加难以阅读。还使得性能显著降低,因为它需要运行编译器,还会让JSLint无法检测其中的问题。
浏览器提供的setTimeout和setInterval函数,参数传人字符串时也会调用eval。continue语句跳到循环的顶部,通过移除continue语句之后的代码,性能都会得到改善!
不要使用switch穿越,这会让我们更加容易发现不小心造成的case条件穿越导致的bug。
if, while, do 或 for 语句可以接受一个括在花括号中的代码,也可以接受单行语句。但那会模糊了程序的结构,不要这么做。
JavaScript的执行环境一般接触不到硬件,所以非常慢。JavaScript很少被用来执行位操作。
不要在if中使用function语句,不同的浏览器在解析时的处理是不同的,这就会造成可移植性的问题。
一个语句不能以函数表达式开头,因为官方的语法假定单词function开头的语句是一个function语句。解决方法是把函数调用括在一对圆括号中:(function () { //}());
JavaScript有一套类型的包装对象,例如:new Boolean(false)
会返回一个对象,该对象有一个valueOf方法会返回被包装的值。这其实完全没有必要,并且又是还令人困惑。
new Boolean
, new Number
, new String
, 也请避免使用new Object
和new Array
,可使用 {} 和 [] 来代替。 使用new创建原型的新对象时,会涉及到this的绑定问题,如果在创建对象是忘记加上new,则会把this绑定到全局对象,造成全局变量污染。所以尽量避免去使用new。
在JavaScript中void是一个运算符,它接受一个运算数并返回undefined。这没什么用,且令人困惑。应避免去使用它。
转载地址:http://uhtpi.baihongyu.com/