手记

javascript中的一些注意点

多行注释的陷阱

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

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

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

数字类型

javascript中的数字是Number类型,在内部表示为64位的浮点数,也就意味着2===2.0,这种设计的一个优点是避免了整型的溢出.

遍历对象的属性

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'

所以为了区分数组和对象我们应该可以采用以下的函数(ES5提供了原生的Array.isArray):

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中所有数字都是浮点型,所以使用位运算会降低程序性能,因为需要将数字转化为整型后执行运算然后转化回去.

void

在js中void是一个运算符,它接收一个操作数并返回undefined,例如:void 'abc' === undefined.然并卵.

5人推荐
随时随地看视频
慕课网APP