JavaScript中的那些坑
由于正则表达式字面量的存在,多行注释可能会产生陷阱,例如以下程序将抛出错误:
/*
var a = /h*/.test('hello');
*/
正则结束前的那个星号将被解析为注释结束符,从而.
被认为是不合法的.所以尽量避免使用多行注释
js采用IEEE754标准表示数字,整型也是按照64位浮点数进行处理的,也就意味着2===2.0
,这种设计的一个优点是避免了整型的溢出.JavaScript中精确的整型的范围是(-2^53,2^53),ES6中的2个常量:Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
。
Math.pow(2,53) +1 === Math.pow(2,53) // true
遍历对象的属性
for-in
循环可以枚举对象以及它原型上的属性,我们可以通过hasOwnProperty()
进行过滤,例如:
for(var key in obj){
// 以下的过滤还可以使用typeof obj[key] !== 'function'
if(obj.hasOwnProperty(key)){
console.log(key,'=>',obj[key]);
}
}
函数
带有缓存的函数在js中函数就是对象,对象是"名/值"对的集合并拥有一个连接到原型对象的隐藏连接.对象字面量产生的对象连接到
Object.prototype
.函数对象连接到Function.prototype
(该原型对象本身连接到Object.prototype
).每个函数在创建时附有2个附加的隐藏属性:函数的上下文和实现函数行为的代码.因为函数是对象,所以它们可以像其他任何值一样被使用.函数可以存放在变量,对象和数组中,函数可以被当做参数传递给其他函数,函数也可以再返回函数,函数也可以拥有方法.函数与普通对象的不同之处仅仅在于它可以被调用.
函数可以使用对象缓存之前的计算结果从而减少不必要的计算,这是程序优化的一种方式,例如,对于斐波那契数列我们可以这样优化:
var fibonacci = function(){
var memo = [0,1]
var fib = function(n){
var result = memo[n]
if(typeof result !== 'number'){
result = fib(n - 1) + fib(n - 2)
memo[n] = result
}
return result
}
return fib
}()
我们可以将这种形式推广到一般函数:
var memoizer = function(memo,fundamental){
var shell = function(n){
var result = memo[n]
if(typeof result !== 'number'){
result = fundamental(shell,n)
memo[n] = result
}
return result
}
return shell
}
var fib = memoizer([0,1],function(shell,n){
return shell(n-1) + shell(n-2)
})
var fac = memoizer([1,1],function(shell,n){
return n * shell(n-1)
})
继承
当一个函数对象被创建时,
Function
构造器产生的函数对象会运行类似这样的一些代码:
this.prototype = {constructor:this};
新函数对象被赋予一个prototype
属性,其值是包含在一个constructor
属性并且属性值为该函数的新对象.该prototype
对象是存放继承特征的地方.因为javascript语言没有提供一种方法去确定哪个函数是打算用来做构造器的,所以每个函数都会得到一个prototype
对象(非常重要).
本质上来说就是子类的原型等于父类的实例,实例如下:
var SuperType = function(){
this.superValue = 'supper value'
}
var SubType = function(){
this.subValue = 'sub value'
}
SubType.prototype = new SuperType()
var sub = new SubType()
console.log(sub.subValue) // subvalue
console.log(sub.superValue) // supervalue
数组
javascript中并没有真正的数组,数组本质上也是对象
请看下面的例子:
var arr = [1,2,3,4,5,6]
console.log(arr.length) // 6
arr.abc = false // 给数组增加属性
console.log(arr) // [1, 2, 3, 4, 5, 6, abc: false]
console.log(arr.length) // 6
由运行的结果可以看出给数组添加了一个abc属性,尽管字面上的长度有所增加,但是数组的实际长度并没有改变!
typeof [] // 'object'
所以为了区分数组和对象我们应该可以采用以下的函数:
let isArray = value => !!value && typeof value === 'object' && value.constructor === Array
正则表达式
分组
观察以下匹配url的正则表达式:
'use strict'
let parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/
let url = 'http://www.ora.com:80/goods?q=1#fragment'
let result = parse_url.exec(url)
let names = ['url','schema','slash','host','port','path','query','hash']
for(let i = 0;i < names.length;i++){
console.log(names[i] + ':',result[i])
}
(?:([A-Za-z]+):)?
这个因子匹配一个协议名,但仅当它之后跟随一个冒号(:)的时候才匹配(?:...)
表示一个非捕获型分组(noncapturing group),通常用非捕获分组来代替少量不优美的捕获型分组是很好的方法,因为捕获会有性能上的缺失.后缀?
表示这个分组是可选的.(...)
表示一个捕获型分组(capturing group).一个捕获型分组将复制它所匹配的文本,并将其放入到result数组中.每个捕获型分组都将被指定一个编号,第一个捕获分组的编号是1,所以该分组所匹配的文本拷贝将出现在result[1]中;下一个因子(\/{0,3})
是捕获分组2.
匹配数字
pattern_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i
正则表达式转义
\1
指向分组1所捕获到的文本的一个引用,所以它能够再次匹配,例如我们用下面的正则表达式来搜索文本中重复的单词:
var doubled_words = /[A-Za-z\u00c0-\u1fff\u2800-\ufffd\-]+\s+\1/gi
正则表达式分组
捕获型
一个被包围在圆括号中的正则表达式选择.任何匹配这个分组的字符将被捕获.每个捕获分组都被指定了一个数字,第一个捕获(的是分组1,第二个捕获(的是分组2
非捕获型
有一个(?:
前缀,仅做简单匹配,并不会捕获所匹配文本,会有微弱的性能优势,不会干扰捕获型分组的编号.
由于javascript中所有数字都是浮点型,所以使用位运算会降低程序性能,因为需要将数字转化为整型后执行运算然后转化回去.
巧妙使用位运算可以创造很多的奇淫技巧。例如以下的转化为整数的代码:
let parseInt = num => num 0
// 还可以这样
let parseInt = num => num >>> 0
// 甚至,我们可以这样
let parseInt = num => ~~num
以上的parseInt
实现可以传入任意的数据类型,如果数据类型不匹配将会被转化为0,避免了原生的parseInt
中的NaN
的问题,但是局限在于只能处理32位的整数。
JSON.stringify
和JSON.parse
的妙用
我们可能见到这样的函数:
let f = uid => {
let user = global.__user[uid]
let game = global.__game[uid]
let userInfo = JSON.parse(JSON.stringify(user))
let gameInfo = JSON.parse(JSON.stringify(game))
let ret = {
user: userInfo,
game: gameInfo
}
return ret
}
思考上述的过程是不是存在重复?为什么要将一个对象变成字符串,然后再将字符串解析为对象?我们明明可以直接使用那个对象?其实let objs = JSON.parse(JSON.stringify(obj))
执行的是对象的深度克隆操作。序列化为字符串之后它的各个属性已经被解除了引用,重新JSON.parse
相当于创建了一个新的对象。当然对象的克隆本身有很多方法,例如:lodash的_.clone
数组复制其实有很多方法:
var arr2 = arr1.concat();
var arr2 = arr1.filter(n => true);
var arr2 = arr1.map(n => n);
js实现node的REPL模式
一个简易的JavaScript解释器。类似于ruby的irb,Scala的命令行终端。
基本思路:eval函数的使用以及UDP协议。实现如下:
server.js
'use strict'
const dgram = require('dgram')
let udp = dgram.createSocket('udp4')
const PORT = 8888
const ADDRESS = '127.0.0.1'
udp
.on('message',(data,rinfo) => {
let cmd = data.toString().trim()
console.log(`get cmd [${cmd}] from ${rinfo.address} : ${rinfo.port}`)
let evalRes
try {
evalRes = new Buffer('=> ' + JSON.stringify(eval(cmd)) + '\njavascript>')
} catch (ex) {
console.trace('udp 2>', ex.stack)
evalRes = new Buffer(ex.stack)
}
udp.send(evalRes,0,evalRes.length,rinfo.port,rinfo.address)
})
.on('error',err => {
console.trace(err.stack)
udp.close()
})
.on('close',() => {
process.exit()
})
.on('listening',() => {
let addr = udp.address()
console.log(`command server listen on ${addr.address} : ${addr.port}`)
})
.bind(PORT,ADDRESS)
client.js
'use strict'
const dgram = require('dgram')
let udp = dgram.createSocket('udp4')
const stdin = process.stdin
const PORT = 8888
const ADDRESS = '127.0.0.1'
stdin.resume()
stdin.setEncoding('utf-8')
console.log('javascript>')
udp.on('message',(msg,rinfo) => {
try {
msg = JSON.parse(msg.toString())
} catch (e) {
msg = msg.toString()
}
console.log(msg)
})
stdin.on('data',chunk => {
let msg = new Buffer(chunk)
udp.send(msg,0,msg.length,PORT,ADDRESS)
})
生成随机字母数字字符串
function generateRandomAlphaNum(len) {
var rdmString = "";
for( ; rdmString.length < len; rdmString += Math.random().toString(36).substr(2));
return rdmString.substr(0, len);
}
随机背景色
toString(36)
表示10个阿拉伯数字和26个小写英文字母
'#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6)
在js中如何优雅实现sleep
promise
function sleep(delay){
return function(){
return new Promise(function(resolve, reject){
setTimeout(resolve, delay);
});
}
}
var promise = new Promise(function(resolve){
console.log('do something');
resolve();
}).then(sleep(2000)).then(function(){
console.log('after sleep 2000');
});
generator
function sleep(ms) {
return function (cb) {
setTimeout(cb, ms);
};
}
co(function *() {
var now = Date.now();
yield sleep(2000);
expect(Date.now() - now).to.not.be.below(2000);
})();
参见知乎
underscore.js中的_.after方法该方法可以根据传入的times参数的不同生成需要调用多次才执行的实际函数的函数,这是偏函数的典型应用:
var _ = require('underscore')
var count = 5;
var fiveCountCallback = _.after(count,function(){
console.log('after 5 count and execute this function')
})
var timer = 0
setInterval(function(){
console.log(timer++)
fiveCountCallback() // 每1s执行一次该函数,但是真正执行该函数却是在55后
}, 1000)
console.log('this will print first')
其内部实现如下:
_.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};
共同学习,写下你的评论
评论加载中...
作者其他优质文章