JavaScript 秘密花园由两位 Stack Overflow 用户伊沃·韦特泽尔(写作)和张易江(设计)完成,由三生石上翻译完成,内容短小精炼。这次温故知新,做一番总结。
JavaScript 中所有变量都是对象,除了两个例外 null
和 undefined
。
JavaScript 解析器错误,试图将点操作符解析为浮点数字值的一部分。
2.toString() // SyntaxError
2..toString() // 第二个点号可以正常解析
2 .toString() // 添加空格
;(2).toString() // 2 先被计算
删除属性的唯一方法是使用 delete
操作符,设置属性为 undefined
或者 null
并不能真正的删除属性,而仅仅是移除属性和值的关联。
实现传统的类继承模型很简单,但是实现 JavaScript 中的原型继承则困难的多。
It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.
function Foo() {
this.value = 42
}
Foo.prototype = {
method: function() {}
}
function Bar() {}
// 设置 Bar.prototype 属性为 Foo 的实例对象
Bar.prototype = new Foo()
Bar.prototype.foo = 'Hello World'
// 修正 Bar.prototype.constructor 为 Bar 本身
Bar.prototype.constructor = Bar
/**
* test [Bar的实例]
* Bar.prototype [Foo的实例]
* { foo: 'Hello World' }
* Foo.prototype
* { method: ... }
* Object.prototype
* { toString:... }
**/
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto)指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
someObject.[[Prototype]]
符号是用于指向 someObject 的原型。从 ECMAScript6 开始,[[Prototype]]
可以通过 Object.getPrototypeOf()
和 Object.setPrototypeOf()
访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__
。
但它不应该与构造函数 func 的 prototype
属性相混淆。被构造函数创建的实例对象的 [[prototype]]
指向 func 的 prototype
属性。 Object.prototype
属性表示 object 的原型对象。
hasOwnProperty
是 JavaScript 中处理属性并且不会遍历原型链的方法之一(另一种方法: Object.keys()
)。当检查对象某个属性是否存在时,hasOwnProperty
是唯一可用的方法。同时在使用 for in loop 遍历对象时,推荐总使用 hasOwnProperty
方法。
函数声明会被提前解析(hoisted),但函数表达式不会。
// 函数声明
foo()
function foo() {}
// 函数表达式
bar // undefined
bar() // TypeError
const bar = function() {}
函数名在函数内总是可见的。
const foo = function bar() {
bar() // 正常运行
}
bar() // 出错:ReferenceError
上面代码中,bar 函数声明外是不可见的,这是因为已经把函数赋值给了 foo;然而在 bar 内部依然可见。这是由于 JavaScript 的命名处理所致,函数名在函数内总是可见的。
JavaScript 有一套完全不同于其它语言的对 this 的处理机制。在 5 种不同的情况下,this 指向的各不相同。
foo() // this 指向全局对象
test.foo() // this 指向 test 对象
new foo() // this 指向返回的新对象
Foo.method = function() {
function test() {
// this 将会被设置为全局对象
}
test()
}
为了在 test 中获取对 Foo 对象的引用,需要在 method 函数内部创建一个局部变量指向 Foo 对象。因为 JavaScript 中不可以对作用域进行引用或赋值,所以不可以在外部访问私有变量。
Foo.method = function() {
const that = this
function test() {
// 使用 that 来指向 Foo 对象
}
test()
}
arguments 对象为其内部属性以及函数形式参数创建 getter 和 setter 方法。因此,改变形参的值会影响到 arguments 对象的值,反之亦然。
function foo(a, b, c) {
arguments[0] = 2
console.log(a) // 2
b = 4
console.log(arguments[1]) // 4
var d = c
d = 9
console.log(c) // 3
}
foo(1, 2, 3)
只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript 中,这可以通过匿名包装器轻松解决。
;(function() {
// 小括号内的函数首先被执行, 并且返回函数对象
// 函数创建一个命名空间
window.foo = function() {
// 对外公开的函数,创建了闭包
}
})() // 立即执行此匿名函数, 也就是函数对象
// 另外两种方式
;+(function() {})()
;(function() {})()
由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty
函数, 因此会比普通的 for 循环慢上好多倍。为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。
length 属性的 getter 方式会简单的返回数组的长度,而 setter 方式会截断数组。
var foo = [1, 2, 3, 4, 5, 6]
foo.length = 3
foo // [1, 2, 3]
JavaScript 是弱类型语言,这就意味着等于操作符会为了比较两个值而进行强制类型转换。
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n' == 0 // true
typeof 操作符(和 instanceof 一起)或许是 JavaScript 中最大的设计缺陷,因为几乎不可能从它们那里得到想要的结果。
尽管 instanceof 还有一些极少数的应用场景,typeof 只有一个实际的应用,那便是用来检测一个对象是否已经定义或者是否已经赋值,而这个应用却不是用来检查对象的类型。
typeof foo !== 'undefined'
上面代码会检测 foo 是否已经定义,如果没有定义而直接使用会导致 ReferenceError 的异常。 这是 typeof 唯一有用的地方。除非为了检测一个变量是否已经定义,应尽量避免使用 typeof 操作符。
Value | Class | Type |
---|---|---|
‘foo’ | String | string |
new String(‘foo’) | String | object |
1.2 | Number | number |
new Number(1.2) | Number | object |
true | Boolean | boolean |
new Boolean(true) | Boolean | object |
new Date() | Date | object |
new Error() | Error | object |
[1,2,3] | Array | object |
new Array(1, 2, 3) | Array | object |
new Function(”) | Function | function |
/abc/g | RegExp | object |
new RegExp(‘meow’) | RegExp | object |
{} | Object | object |
new Object() | Object | object |
上面表格中,Type 一列表示 typeof 操作符的运算结果。可以看到,这个值在大多数情况下都返回 object。
Class 一列表示对象的内部属性 [[Class]] 的值。
JavaScript 标准文档只给出了一种获取 [[Class]] 值的方法,那就是使用 Object.prototype.toString
。
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1)
return obj !== undefined && obj !== null && clas === type
}
is('String', 'test') // true
is('String', new String('test')) // true
为了检测一个对象类型,推荐使 Object.prototype.toString
方法,因为这是唯一一个可依赖的方式。
instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义,如果用来比较内置类型,将会和 typeof 操作符一样用处不大。
function Foo() {}
function Bar() {}
Bar.prototype = new Foo()
new Bar() instanceof Bar // true
new Bar() instanceof Foo // true
// 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例
Bar.prototype = Foo
new Bar() instanceof Foo // false
// instanceof 比较内置类型
new String('foo') instanceof String // true
new String('foo') instanceof Object // true
'foo' instanceof String // false
'foo' instanceof Object // false
需要注意:instanceof 用来比较属于不同 JavaScript 上下文的对象(比如浏览器中不同的文档结构)时将会出错,因为它们的构造函数不会是同一个对象。
instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。正如 typeof 操作符一样,任何其它的用法都应该是避免的。