JS 奇技淫巧(二)
正则表达式实例
var a = /123/
var b = /123/
console.log(a == b) // false
console.log(a === b) // false
js
每个字面的正则表达式都是一个单独的实例,即使它们的内容相同。
数组比较大小
var a = [1, 2, 3]
var b = [1, 2, 3]
var c = [1, 2, 4]
console.log(a == b) // false
console.log(a === b) // false
console.log(a > c) // false
console.log(a < c) // true
js
数组也是对象,ES5 规范指出如果两个对象进行相等比较,只有在它们指向同一个对象的情况下才会返回 true,其他情况都返回 false。对象比较大小,会调用 toString
方法转成字符串进行比较,所以结果就变成字符串 “1,2,3" 和 ”1,2,4" 按照字典排序进行比较。
原型把戏
var a = {}
var b = Object.prototype
console.log(a.prototype === b) // false
console.log(Object.getPrototypeOf(a) == b) // true
console.log(a.__proto__ === b) // true
js
对象是没有 prototype
属性的,所以 a.prototype
是 undefined
。但我们可以通过 Object.getPrototypeOf()
获取 __proto__
来获取一个对象的原型。
构造函数的函数
function f() { }
var a = f.prototype
var b = Object.getPrototypeOf(f)
console.log(a === b) // false
js
结果是 false。Object.getPrototypeOf
用来获取指定对象的原型(内部 [[Prototype]]
属性的值)。
function Person() { }
var p = new Person()
var a = p.__proto__
var b = Object.getPrototypeOf(p)
var c = Person.prototype
console.log(a === b, a === c, b === c) // true true true
var d = Person.__proto__
var e = Object.getPrototypeOf(Person)
var f = Function.prototype
console.log(d === e, d === f, e === f) // true true true
js
禁止修改函数名
function foo() { }
var oldName = foo.name
foo.name = 'bar'
console.log(oldName, foo.name) // foo foo
js
函数名是只读的,文档 写的很清楚,所以这里的修改无效。
替换陷阱
"1 2 3".replace(/\d/g, parseInt) // 1 NaN 3
js
如果 replace 方法第二个参数是一个函数,则会在匹配的时候多次调用,第一个参数是匹配的字符串,第二个参数是匹配字符串的下标。所以运行时代码实际是 parseInt(1, 0)
、parseInt(2, 2)
、parseInt(3, 4)
。
Function 的名字
function f() { }
var parent = Object.getPrototypeOf(f)
console.log(f.name) // f
console.log(parent.name) // ''
console.log(typeof eval(f.name)) // function
console.log(typeof eval(parent.name)) // error
js
parent 实际上是 Function.prototype
,即 ƒ () { [native code] }
。它的 name 是 ”。所以 eval(parent.name)
是 undefined
。
正则测试陷阱
var lowerCaseOnly = /^[a-z]+$/
console.log(lowerCaseOnly.test(null), lowerCaseOnly.test()) // true true
js
test 方法的参数如果不是字符串,会经过抽象 ToString
操作强制转成字符串,因此实际上测试的是字符串 ”null“ 和 ”undefined“。
逗号定义数组
console.log([,,,].join(',')) // ,,
js
JavaScript 允许使用逗号来定义数组,得到的数组是含有 3 个 undefined 值的数组。
所有的数组元素被转成字符串,再用一个分隔符将这些字符串连接起来。如果元素是 undefined 或者 null,则会转换为空字符串。
保留字 class
// chrome
var a = { class: 'Animal', name: "heora" }
console.log(a.class) // Animal
js
这个实际的答案取决于浏览器。class 是关键字,但是在 Chrome、Firefox 和 Opera 中可以作为属性名称,在 IE 中是禁止的。另一方面,其实所有浏览器基本接受大部分的关键字(如:int、private、throws 等)作为变量名,而 class 是禁止的。
无效日期
var a = new Date("epoch")
console.log(a) // Invalid Date
js
实际结果是 Invalid Date
,它实际上是一个 Date 对象,因为 a instance Date
的结果是 true,但是它是无效的 Date。Date 对象内部是用一个数字来存储时间的,在这个例子中,这个数字是 NaN
。
神鬼莫测的函数长度
var a = Function.length
var b = new Function().length
console.log(a) // 1
console.log(b) // 0
console.log(a === b) // false
js
实际上 a 的值是 1,b 的值是 0 。我们来看下 MDN 文档关于 Function.length
的描述。
Function 构造器本身是一个 Function,它的 length 属性是 1。
Function 原型对象的属性:
Function 原型对象的 length 属性值为 0
在本例中,a 代表的是 Function 构造器的 length 属性,b 代表的是 Function 原型的 length 属性。
Date 的面具
var a = Date(0)
var b = new Date(0)
var c = new Date()
console.log([a === b, b === c, a === c]) // [ false, false, false ]
js
直接看下 MDN 关于对象的注意点:
只能通过调用 Date 构造函数来实例化日期对象:以常规函数调用它(不加 new 操作符)将会返回一个字符串,而不是一个日期对象。另外,不像其他 JavaScript 类型,Date 对象没有字面量格式。
所以 a 是字符串,b 和 c 是 Date 对象,并 b 代表的是 1970 年那个初始化时间,而 c 代表当前时间。
min 和 max 共舞
var min = Math.min()
var max = Math.max()
console.log(min < max) // false
js
对于 Math.min
,如果没有参数,结果为 Infinity
。对于 Math.max
,如果没有参数,结果为 -Infinity
。
最熟悉的陌生人
var a = new Date('2022-06-08')
var b = new Date(2022, 06, 08)
console.log([a.getDay() === b.getDay(), a.getMonth() === b.getMonth()]) // false false
js
new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);
当 Date 作为构造函数调用并传入多个参数时,如果数值大于合理范围时(如月份为 13 或者分钟数为70),相邻的数值会被调整。比如
new Date(2013, 13, 1)
等于new Date(2014, 1, 1)
,它们都表示日期 2014-02-01(注意月份是从0开始的)。其他数值也是类似,new Date(2013, 2, 1, 0, 70)
等于new Date(2013, 2, 1, 1, 10)
,都表示时间2013-03-01T01:10:00
。
getDay
返回指定日期对象的星期中的第几天(0~6)。
匹配隐式转换
if ("http://giftwrapped.com/picture.jpg".match(".gif")) {
console.log("a gif file");
} else {
console.log("not a gif file");
}
js
如果传入一个非正则表达式对象,则会隐式地使用 new RegExp(obj)
将其转换为正则表达式对象。
所以我们的字符串 ".gif"
会被转换成正则对象 /.gif/
,会匹配到 "/gif"
。
重复声明变量
function foo(a) {
var a
return a
}
function bar(a) {
var a = 'bye'
return a
}
console.log([foo('hello'), bar('hello')]) // [ 'hello', 'bye' ]
js
一个变量在同一作用域已经声明过,会自动移除 var 声明,但是赋值操作依旧保留。
其实可以参考以下规则解题:
- 寻找函数的形参和变量声明(变量声明提升)
- 实参的参数值赋值给形参
- 寻找函数的函数声明和赋值函数体
- 执行函数
警惕全局匹配
function captureOne(re, str) {
var match = re.exec(str)
return match && match[1]
}
var numRe = /num=(\d+)/ig;
var wordRe = /word=(\w+)/i;
var a1 = captureOne(numRe, "num=1")
var a2 = captureOne(wordRe, "word=1")
var a3 = captureOne(numRe, "NUM=1")
var a4 = captureOne(wordRe, "WORD=1")
console.log([a1 === a2, a3 == a4]) // true false
js
当正则表达式使用 ”g" 标志时,可以多次执行 exec 方法来查找同一个字符串的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex
属性指定的位置开始。所以 a3
的值是 null。
console.log(a1) // 1
console.log(a2) // 1
console.log(a3) // null
console.log(a4) // 1
js