资讯专栏INFORMATION COLUMN

JavaScript中的那些坑

ivyzhang / 1146人阅读

摘要:序列化为字符串之后它的各个属性已经被解除了引用,重新相当于创建了一个新的对象。类似于的,的命令行终端。基本思路函数的使用以及协议。

多行注释的陷阱

由于正则表达式字面量的存在,多行注释可能会产生陷阱,例如以下程序将抛出错误:

/*
var a = /h*/.test("hello");
*/

正则结束前的那个星号将被解析为注释结束符,从而.被认为是不合法的.所以尽量避免使用多行注释

整型

js采用IEEE754标准表示数字,整型也是按照64位浮点数进行处理的,也就意味着2===2.0,这种设计的一个优点是避免了整型的溢出.JavaScript中精确的整型的范围是(-2^53,2^53),ES6中的2个常量:Number.MAX_SAFE_INTEGERNumber.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-zu00c0-u1fffu2800-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.stringifyJSON.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)) + "
javascript>")
            
        } 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);
    }
  };
};
对象的键

对象的键如果是数字,则按照数字进行升序排序。

obj = {"100":"a","99":"b"} // { "99": "b", "100": "a" }
"100" > "99" // false

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80138.html

相关文章

  • JavaScript四舍五入的那些

    摘要:进制之谜众所周知,计算机在设计之初,出于各方面角度考虑,最终采用二进制的格式来存储数据。同样的情况,也会出现在十进制和二进制的转换中。当我们在计算机中,声明一个变量为,其实该数字作为二进制保存在计算机中,并不真的是。 前言 经常使用JavaScript用来处理数字的程序员都知道,JavaScript的Number.toFixed,这一函数,在格式化数字时,会自动进行四舍五入,例如: 1...

    zollero 评论0 收藏0
  • JavaScript中,关于变量和声明的一些

    摘要:主要讲述了中关于变量声明和代码编写时你可能没它留意的一些坑。但是换行符并不会被忽略,换行符起到了分号的功能。需要注意的是,大小写敏感,和是两个不同的变量。保留字中有一批称为保留字的家伙是不能用做变量的,用了在一些浏览器中很可能会报错。 今天翻译的这篇文章依旧比较基础,是这个系列文章的第三篇。主要讲述了JavaScript中关于变量声明和代码编写时你可能没它留意的一些坑。 那些熟悉PHP...

    lowett 评论0 收藏0
  • 前端入指南

    摘要:作为自学两年的初级前端,希望对那些想入门前端开发的人分享一些观点。尤其是这几年前端领域飞速的发展,新东西层出不穷。或者关注下我的微信公众号前端获取每天分享前端入门知识。为什么选择前端 做一件事之前最好问问自己为什么要做,然后再去思考该怎么做。如果只是看到别人做了,并且有很不错的收入,然后自己就决定做了,很可能中途放弃浪费掉很多时间。起码问自己一个问题:我是否真的热爱这个领域,并且很乐意在这个...

    junnplus 评论0 收藏0
  • JavaScript操作DOM的那些

    摘要:在操作中存在着许多跨浏览器方面的坑,本文花了我将近一周的时间整理,我将根据实例整理那些大大小小的坑。在火狐中,与等效的是。对象的属性则表示文档的根节点。不区分和在下使用和时会同时返回或与给定值相同的元素。 js在操作DOM中存在着许多跨浏览器方面的坑,本文花了我将近一周的时间整理,我将根据实例整理那些大大小小的坑。 DOM的工作模式是:先加载文档的静态内容、再以动态方式对它们进行刷新,...

    RiverLi 评论0 收藏0

发表评论

0条评论

ivyzhang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<