堪称JavaScript中的必读圣经,又叫红宝书,第一部分主要讲解了语言特性,尤其是关于原型/继承的部分实在精彩

JavaScript高级程序设计

作者 Nicholas C. Zakas

JavaScript简介

  • JavaScript 发展史
    • 1995年 JavaScript 1.0
    • 1997年06月 - ECMAScript 1.0
    • 1998年6月 - ECMAScript 2.0
    • 1999年12月 - ECMAScript 3.0
    • 2008年7月 - ECMAScript 3.1
    • 2009年12月 - ECMAScript 5.0
    • 2015年6月 - ECMAScript 6.0
  • JavaScript 组成
    • 核心 ECMAScript
    • 文档对象模型 DOM
    • 浏览器对象模型 BOM
  • 混杂模式(quirks mode), 标准模式(standards mode)和几乎标准模式(almost standards mode)

基本概念

  • 使用可读性更高的注释

    1
    2
    3
    4
    /*
    * 这是一个多行注释
    * (块级注释)
    */
  • 'use strict' 使用严格模式编程,有助于培养良好的代码风格

  • 显式声明变量(这样使用 typeof 返回 undefined 时可知该变量未声明而非未初始化)
  • 5种基本数据类型 undefinednullbooleannumberstring 和一种复杂数据类型 object
  • typeof 操作符返回 undefinedbooleannumberstringobjectfunction
  • 只有 0 除以 0 会返回 NaN, 整数除以 0 返回 Infinity, 负数除以 0 返回 -Infinity
  • 后置型递增(递减)与前置型递增(递减)的区别

    var nThree = nOne++ + nTwo 相当于 var nThree = nOne + nTwo; nOne++
    var nThree = ++nOne + nTwo 相当于 nOne++; var nThree = nOne + nTwo
    记忆技巧是,前增是赋值前就增加,后增是赋值后才增加

  • 将数字字符串转换成 number 的方法

    1. var nOne = sOne - 0
    2. var nTwo = +sTwo
    3. var nThree = Number(sThree)
  • 大写字母的字符编码全部小写字母的字符编码

    1
    var result = 'Brick' < 'alphabet' // true
  • 数字字符串之间的比较

    1
    2
    3
    var result1 = '23' < '3'; // true
    var result2 = '23' < 3; // false
    var result3 = 'a' < 3; // false, 'a' 被转换成了 NaN, 任何数与 NaN 比较均是 false
  • for in 出来的顺序不可预测(ECMAScript 中对象属性没有顺序)

  • 在双层嵌套 for 循环中使用 label,使得可以控制退出到外部的标记 for 循环
  • 禁止使用 with 语句
  • switch 语句比较时使用全等操作符,不发生类型转换

变量、作用域和内存问题

  • ECMAScript 变量是松散的,包括基本类型值(简单数据段)和引用类型值(对象)
  • ECMAScript 中所有参数传递的都是值,不可能通过引用传递参数(原书 71 页)

    1
    2
    3
    4
    5
    6
    7
    8
    function setName(obj) {
    obj.name = 'Nicholas'
    obj = new Object()
    obj.name = 'Greg'
    }
    var person = new Object()
    setName(person)
    alert(person.name) // 'Nicholas'
  • 使用 instanceof 操作符检测对象类型:result = variable instanceof constructor

  • JavaScript 的垃圾收集方式是 标记清除

    IE8 之前的 DOM 与 BOM 对象是以 C++ 实现的 COM 对象,使用引用计数策略回收垃圾(存在循环引用问题)

引用类型

  • 传递参数时对必须值使用命名参数,对可选参数使用对象
  • aOne.length = 'someValue' 等同于 aOne.push('someValue')
  • ECMAScript 5 中新增了 Array.isArray(value) 方法(IE9+)判断是否为数组

    instanceof 操作符假定只有一个全局环境,多框架传递数组时会与各框架中的原生数组具有不同的构造函数

  • Array.toString()Array.join(',') 得到的结果相同

  • ECMAScript 5(IE9+)为数组定义了5个迭代方法(这些方法均不改变原数组):
    • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回true
    • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
    • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
    • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
    • some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true
  • ECMAScript 5(IE9+)为数组定义了2个归并方法:reduce()reduceRight()
  • 在 ECMAScript 3 中,正则表达式字面量始终共享一个 RegExp 实例(但浏览器实现与 ES5 相同)
  • 严格模式下不能使用 arguments.callee
  • toExponential() 方法返回以指数表示法(也称e表示法)表示的数值的字符串形式。
  • IE8+ 支持方括号表示法访问个别字符

    1
    2
    3
    var stringValue = 'hello world'
    alert(stringValue.charAt(1)) // 'e'
    alert(stringValue[1]) // 'e',不兼容低版本
  • ECMAScript 5 新增了字符串方法 trim()

  • replace('{字符串或正则}', '{替换字符或函数}'),第一个参数参数是字符串时只替换第一个匹配项, 要全局替换必须用正则且指定全局(g), 第二个参数可以附加特殊字符序列进行高级替换

面向对象的程序设计

理解对象

  • 数据属性
    • Configurable 默认为 true
    • Enumerable 默认为 true
    • Writable 默认为 true
    • Value 默认为 undefined
  • 访问器属性
    • Configurable 默认 true
    • Enumerable 默认 true
    • Get 默认 undefined
    • Set 默认 undefined
  • 属性前面包含下划线一般表示只能通过对象方法访问的属性:_property

创建对象

  • 工厂模式——传入参数并返回参数作为值的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function createPerson(name, age, job) {
    var o = new Object()
    o.name = name
    o.sayName = function () {
    alert(this.name)
    };
    return o
    }
    var person1 = createPerson('Nicholas', 29, 'Software Engineer')
  • 工厂模式不能解决对象识别的问题

构造函数模式

1
2
3
4
5
6
7
8
9
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
alert(this.name)
}
}
var person1 = new Person('Nicholas', 29, 'Software Engineer')
  • 构造函数的问题在于每个实例都要重建一遍,不能完成共享

原型模式

1
2
3
4
5
6
7
8
function Person(){} //空构造函数
Person.prototype.name = 'Nicholas'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
alert(this.name)
}
var person1 = new Person()
  • 理解原型对象
    • ES5 中使用 Object.getPrototypeOf() 可以方便地取得一个对象的原型
    • 使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中,常用语 for-in 中区分原型(IE8存在 Bug,for-in 循环中不会出现[不可枚举]的属性:hasOwnProperty()propertyIsEnumerable()toLocaleString()toString()valueOf())。
    • 原型具有动态性,所做修改立刻在所有实例上体现,跟是否已创建实例无关(指针)

其他模式

  • 最常用的默认方式:构造函数与原型模式同时使用
  • 动态原型模式可以灵活地初始化原型
  • 寄生构造函数模式,使用工厂模式的构造函数(尽量不用)
  • 稳妥构造函数模式(安全,没有公共属性,不适用 thisnew

继承

原型链

  • 所有函数都是 Object 的实例,最终原型指向 Object.prototype
  • 通过原型链实现继承时,使用对象字面量创建原型方法会重写原型链
  • 组合使用原型链和借用构造函数是最常用的继承模式
  • Object.create() —— 原型式继承

函数表达式

  • 函数声明不要放在语句块中(可使用函数表达式)

    递归

  • S5 严格模式中不能使用 arguments.callee
    1
    2
    3
    4
    5
    6
    7
    8
    //命名函数表达式实现递归
    var factorial = (function f (num) {
    if (num <= 1) {
    return 1
    } else {
    return num * f(num-1)
    }
    })

闭包

  • 过度使用闭包会导致内存占用过多
  • 闭包只能取得包含函数中变量的最后一个值
  • 闭包中的 this 指向 window 对象(与匿名函数相同)
  • IE8 以下浏览器在闭包中保存了 HTML 元素,导致这个元素将无法销毁

    1
    2
    3
    4
    5
    6
    7
    8
    function assignHandler(){ //IE8-中关于HTML元素闭包无法销毁的解决方法
    var element = document.getElementById('someElement')
    var id = element.id
    element.onclick = function(){
    alert(id)
    }
    element = null
    }
  • 重新声明变量但不赋值将被忽略

BOM

  • 全局变量不能通过 delete 操作符删除,而直接在 window 对象上的定义的属性可以,尝试访问 window 上定义的属性不会抛出错误

    1
    2
    3
    4
    5
    // 这里会抛出错误,因为 oldValue 未定义
    var newValue = oldValue
    // 这里不会抛出错误,因为这是一次属性查询
    // newValue 的值是 undefined
    var newValue = window.oldValue
  • 跨浏览器取得窗口左边和上边的位置

    1
    2
    3
    4
    // IE、Safari、Opera 和 Chrome 支持 window.screenLeft
    // Firefox、Safari 和 Chrome 支持 window.screenX
    var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX
    var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY
  • 跨浏览器获得页面 ViewPort 大小

    1
    2
    var pageWidth = document.documentElement.clientWidth //IE7+
    var pageHeight = document.documentElement.clientHeight //IE7+
  • 使用超时调用来模拟间歇调用是一种最佳模式(不用手动停止调用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var num = 0
    var max = 10
    function incrementNumber() {
    num++
    //如果执行次数未达到 max 设定的值,则设置另一次超时调用
    if (num < max) {
    setTimeout(incrementNumber, 500)
    } else {
    alert('Done')
    }
    }
    setTimeout(incrementNumber, 500)
  • location 对象

    document.locationwindow.location 引用同一个对象

    • 赋值给 location.href 实际上调用了 location.assign() 方法,此方式会生成浏览记录
    • location.replace('http://www.wrox.com/') 则不会在历史纪录中留下痕迹
    • location.reload(true),不传 true 则可能从缓存中加载

DOM

  • DOM 有12种节点类型 nodeType
  • var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0) 这个写法在 IE8 及之前版本中无效

    1
    2
    3
    var html = document.documentElement // 取得对 <html> 的引用
    var body = document.body // 取得对 <body> 的引用
    var doctype = document.doctype // 取得对 <!DOCTYPE> 的引用
  • document.URLwindows.location.href 输出一样,但前者只读

  • HTML 中,标签名始终都以全部大写表示,但保险起见,比较前应转化大小写
    1
    2
    3
    4
    5
    6
    if (element.tagName == "div"){ // 不能这样比较,很容易出错!
    //在此执行某些操作
    }
    if (element.tagName.toLowerCase() == "div"){ // 这样最好(适用于任何文档)
    //在此执行某些操作
    }

DOM扩展

  • querySelector()querySelectorAll()(IE8+)
  • readyState 属性

    1
    2
    3
    4
    5
    // loading,正在加载文档
    // complete,已经加载完文档
    if (document.readyState == "complete"){
    //执行操作
    }
  • scrollIntoView()

  • querySelector()querySelectorAll() (IE8+)