《JavaScript高级程序设计》第三版第二次阅读的读书笔记
- 随意的杂七杂八的一些记录,方便自己以后回顾
第一章——JavaScript简介:
- 一个完整JavaScript实现应该包含下面三个部分组成:
- 核心(ECMAScript)
- 文档对象模型(DOM)
- 浏览器对象模型(BOM)
- ECMA-262标准
- 定义了这门语言的基础
- 规定了这些内容:语法、类型、语句、关键字、保留字、操作符、对象
- ECMAScript
- ECMAScript就是对实现ECMA-262标准规定的各个方面内容的语言的描述。JavaScript实现了ECMAScript,Adobe ActionScript同样也实现了ECMAScript。
第二章——在HTML中使用JavaScript:
- HTML5规范要求脚本按照它们出现的顺序执行,但在现实中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded事件触发前执行。
- 异步脚本不保证按照指定它们的先后顺序执行,因此,要确保两者之间互不依赖。
- 建议异步脚本不要在加载期间修改DOM
- 异步脚本一定会在页面的
load
事件前执行,但可能会在DOMContentLoaded事件触发之前或之后执行。 - 文档模式
- 通过使用文档类型(doctype)切换实现的
- 混杂模式、标准模式、准标模式
- 没有文档类型声明,浏览器默认开启混杂模式,浏览器在这种模式下的行为差异非常大。
6.元素的使用
第三章——基本概念:
- 标识符,指的是变量、函数、属性的名字,或者函数的参数。
- 严格模式
- ECMAScript 5 引入的概念
- 是为JavaScript定义了一种不同的解析与执行模型
- 为这门语言中容易出错的地方施加了限制
- 语句结尾的分号如果不写,容易导致压缩错误
- 变量
- ECMAScript的变量是松散类型,就是可以用来保存任何类型的数据
- 使用var操作符定义的变量将成为定义该变量的作用域中的局部变量
- 数据类型
- 5种简单数据类型(Undefined、Null、Boolean、Number、String),1种复杂数据类型(Object)
alert(typeof null) // "object"
alert(null == undefined); // true
- Number类型使用IEEE754格式来表示整数和浮点数值(双精度数值)
- 浮点数值需要的内存空间是保存整数值的两倍
- 0.1加0.2的结果不是0.3,是0.30000000000000004,因此,永远不要测试某个特定的浮点数值。
- NaN与任何值都不想等,包括NaN本身:
alert(NaN == NaN); // false
- 数值转换,把非数值转换为数值
(Number()、parseInt()、parseFloat())
,第一个可以用于任何数据类型,后面两个专门用于把字符串转换成数值 - ECMAScript的字符串是不可变的
- 数值、布尔值、对象和字符串都有
toString()
方法,但null
和undefined
值没有,这时候可以使用String()
函数,这个函数能将任何类型但值转换为字符串 - ECMAScript中的对象其实就是一组数据和功能集合
- Object的每个实例都具有这些属性和方法:
constructor、hasOwnProperty(propertyName)、isPrototypeOf(object)、propertyIsEnumerable(propertyName)、toLocaleString()、toString()、valueOf()
- 操作符
- 递增和递减操作符(任何值都使用,包括字符串、布尔值、浮点数值和对象)
// 前置递减,变量的值是在语句被求值以前改变的
var num1 = 2;
var num2 = 10;
var num3 = --num1 + num2;
var num4 = num1 + num2;
// num3的值是 11
// num4的值是11
// 后置递减,递减的操作是在包含它们的语句被求值之后才执行的
var num1 = 2;
var num2 = 10;
var num3 = num1-- + num2;
var num4 = num1 + num2;
// num3的值是 12
// num4的值是11
- 位操作符:按位非(~)、按位与(&)、按位或(|)、按位异或(^)、左移(<<)、有符号右移(>>)、无符号右移(>>>)
- 乘法和除法(看能不能整理一个统一的,好记住的)
// 乘法
NaN * 1 = NaN;
Infinity * 0 = NaN;
Infinity * -10 = -Infinity;
Infinity * Infinity = Infinity;
// 除法
Infinity / Infinity = NaN;
Infinity / -1 = -Infinity;
0 / 0 = NaN;
100 / 0 = Infinity;
- 任何操作数与
NaN
进行比较,结果都是false
- 相等和不想等操作符:
这两个操作符都会先转换操作数(称为强制转型)
// 要比较相等性之前,不能将null和undefined转换成其他任何值
null == 0; // false;
undefined == 0; // false
null === undefined // false
- 语句
- Safari 3 以前版本的for-in语句中存在一个bug,会导致某些属性被返回两次
-
label
语句一般跟for
语句等循环语句配合使用 -
with
语句的作用是将代码的作用域设置到一个特定的对象中 -
switch
语句在比较值时使用的是全等操作符
- 函数
- 命名的参数只提供便利,但不是必需的,即使定义了两个参数,不一定要传两个,可以传一个或者三个
-
arguments
的值永远跟对应命名参数的值保持同步,但它们但内存空间是独立的。 - ECMAScript函数不能像传统意义上那样实现重载
- ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数
第四章——变量、作用域和内存问题:
- 将一个值赋值给变量时,解析器必需确定这个值是基本类型值还是引用类型值
- 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;引用类型的值是对象,保存在堆内存中
- 所以引用类型的值都是
object
的实例 - 确定一个值是哪种基本类型可以使用
typeof
操作符,而确定一个值是哪种引用类型可以使用instanceof
操作符 - 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存到这个对象中
- 作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问
- 函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同
- 延长作用域链(有些语句可以在作用域链前端添加一个变量对象)
-
try-catch
语句的catch
块,会创建一个新的变量对象 -
with
语句,将指定的对象添加到作用域链中
- 没有块级作用域;使用var声明的变量会自动被添加到最接近的环境中,在with语句中,最接近的环境是函数环境
- 垃圾收集(标记清除)
- 给当前不使用的值加上标记,垃圾收集器定期执行清除工作
- 垃圾收集(引用计数)
- 计算变量的引用次数,如果此时是0,则被回收
- 会有循环引用的问题,这样就无法回收了,容易导致内存泄漏的现象
- IE9前的BOM和DOM中的对象,尽量避免循环引用,如果使用了,就手工断开,因为它们使用COM(组件对象模型)对象的形式实现的,使用的是引用计数。
- 分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少
- 确保占用最少的内存可以让页面获得更好的性能,因此一旦数据不再有用,最好通过将其设置为
null
来释放其引用
第五章——引用类型:
- 引用类型是一种数据结构,用于将数据和功能组织在一起(它也常被称为类,但这种称呼并不妥当),引用类型有时候也被称为对象定义,因为它们描述但是一类对象所具有的属性和方法。
- 构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。
- 创建object实例的方法
- new操作符后跟object构造函数
- 对象字面量表示
- 对象字面量的数值属性名会自动转换为字符串
- Array类型
- ECMAScript数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据
- 数组的length属性不是只读的,可以手动设置对应的值
- 检测数组:
instanceof
操作符在有两个全局执行环境的时候,就有可能会判断错误,因此新增Array.isArray()
方法 -
alert()
要接收字符串参数,所以它会在后台调用toString()
- 栈是一种LIFO(后进先出)的数据结构,项的插入和移除,只发生在栈的顶部
-
push()
在数组末尾添加项,pop()
在数组末尾移除项,这两个方法实现类似栈的行为 - 队列数据结构是FIFO(先进先出),在列表的末端添加项,前端移除项
-
shift()
在数组前端移除项,和push()一起实现类似队列的行为 -
unshift()
方法在数组前端添加任意个项 - 总结上面几个方法的返回值:添加项的都会返回数组的长度,移除项的都会返回移除的对应的项
-
reverse()
方法会反转数组项的顺序,会改变原数组 -
sort()
方法比较的是字符串,要想更灵活,可以传一个比较函数作为参数,会改变原数组 -
concat()
方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组 slice()、splice()
-
indexOf()、lastIndexOf()
,这两个方法比较第一个参数的时候,会使用全等操作符 - 迭代方法:
every()、filter()、forEach()、map()、some()
- 归并方法:
reduce()
和reduceRight()
,这两个方法都会迭代数组的所有项,然后构建一个最终返回的值
- Date类型
-
Date.parse()
和Date.UTC()
返回相应的日期的毫秒数 - 如果传入
Date.parse()
方法的字符串不能表示日期,则会返回NaN -
Date.now()
,返回表示调用这个方法时的日期和时间的毫秒数,使用+操作符获取Date对象的时间戳,也可以达到同样的目的 - 各种日期格式化方法和组件方法
- RegExp类型
- 3个标志:g(全局)、i(不区分大小写)、m(多行)
- 模式中使用的所有元字符都必须转义:
( [ { \ ^ $ | ) ? * + . ] }
- 由于RegExp构造函数的模式参数是字符串,所以在某些情况下到对字符进行双重转义
- 实例属性:
global、ignoreCase、lastIndex、multiline、source
(按照字面量形式而非传入构造函数的字符串返回) - 实例方法:
exec()
,该方法专门为捕获组而设计的 - test()接受一饿字符串参数,在模式与该参数匹配的情况下返回true
-
toLocaleString()
和toString()
返回正则表达式字面量,valueOf()返回正则表达式本身 - 构造函数属性
(input、lastMatch、lastParen、leftContext、multiline、rightcontext)
,这些属性适用于作用域中的所有表达式,并且基于所执行的最近一次正则表达式操作而变化 - 模式的局限性,有9个
- Function类型
- 函数实际上是对象
- 函数定义:1.函数声明语法定义;2.函数表达式定义;
- 3.Function构造函数,不推荐,这种方法会导致解析两次代码,影响性能
// 函数声明
function sum (num1, num2) {
return num1 + num2;
}
// 函数表达式
var sum = function (num1, num2) {
return num1 + num2;
};
// Function构造函数
var sum = new Function("num1", "num2", "return num1 + num2");
- 函数名仅仅是指向函数的指针,一个函数可以有多个名字
- 不带圆括号的函数名是访问函数的指针,而非调用函数
- 将函数名想象为指针,有助于理解为什么函数没有重载的概念
- 解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用,而函数表达式,则必须等到解析器执行到它所在的代码行,才会真的被解释执行
- 函数名本身就是变量,所以函数也可以作为值来使用
- 可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回
- 在函数内部,有两个特殊的对象:
arguments
和this
-
arguments
还有一个属性名叫callee
,该属性是一个指针,指向拥有这个arguments
对象的函数 - 定义阶层函数
-
this
引用的是函数执行的环境对象 - 函数对象有一个名叫
caller
的属性,保存这调用当前函数的函数引用 - 每个函数都包含两个属性:
length
和prototype
,length
属性表示函数希望接受的命名函数的个数 -
prototype
是保存函数所有的实例方法的真正所在 -
prototype
属性是不可枚举的,因此用for-in
无法发现 - 每个函数都包含这两个方法:
apply()
和call()
,用途一致,区别是接受的参数形式不一致,apply()
的第二个参数是数组,而call()
要将所有的参数都依次枚举出来 -
apply()
和call()
能够扩充函数赖以运行的作用域,且有对象不需要与方法有任何耦合关系的好处 -
bind()
这个方法会创建一个函数实例,其this
值会被绑定到传给bind()
函数的值 -
toLocaleString()、toString()
和valueOf()
方法返回的是函数的代码,返回的代码因浏览器而异
- 基本包装类型
- ECMAScript提供来3个特殊的引用类型:
Number、Boolean、String
- 每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象
- 后台自动完成一系列的操作,让下面这种直观的操作可行
// (1)创建String类型的一个实例
// (2)在实例上调用指定的方法
// (3)销毁这个实例
// 可以想象成这样的代码
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
// 从而让我们实现下面这种直观的操作
var s1 = "some text";
var s2 = s2.substring(2);
- 引用类型与基本包装类型的主要区别就是对象的生存期,基本包装类型的对象,只存在于一行代码的执行瞬间,然后立即被销毁
- 将数值格式化为字符串的方法:
toFixed()
会按照指定的小数位返回数值的字符串表示 - 几种包装类型的
valueOf()
方法和toString()
方法的返回值
类型名称 | valueOf()返回 | toString()返回 |
---|---|---|
Boolean类型 | 基本类型值true或false | 字符串"true"或"false" |
Number类型 | 对象表示的基本类型的数值 | 字符串形式的数值 |
String类型 | 对象所表示的基本字符串值 | 对象所表示的基本字符串值 |
- 布尔表达式中的所有对象都会被转换为
true
-
toExponential()
,返回以指数表示法(也称e表示法)表示的数值的字符串形式 -
toPrecision()
,能帮你选择最合适的表示某个数值的格式 - String类型的每个实例都有一个
length
属性,表示字符串中包含多少个字符 - 字符方法:
charAt()和charCodeAt()
- 字符串操作方法:
concat()、slice()、substr()、substring()
- 字符串位置方法:
indexOf()和lastIndexOf()
-
trim()
方法 - 字符串大小写转换方法:
toLowerCase()、toLocaleLowerCase()、toUpperCase()、toLocaleUpperCase()
- 字符串的模式匹配方法:
match()、search()、replace()、split()
htmlEscape()、localeCompare()、fromCharCode()
- 单体内置对象
- 内置对象的定义是:由ECMAScript实现提供的、不依赖宿主环境的对象,这些对像在ECMAScript程序执行之前就已经存在了
-
Global
对象,不属于任何其他对象的属性和方法,最终都是它的属性和方法 -
encodeURI()
和encodeURIComponent()
-
decodeURI()
和decodeURIComponent()
-
decodeURI()
只能对使用encodeURI()
替换的字符串进行编码 -
eval()
方法:它会将传入的参数当作实际的ECMAScript语句来解析,然后把执行结果插入到原位置,这意味着通过eval()
执行的代码可以引用在包含环境中定义的变量 -
Math
对象:ECMAScript还为保存数学公式和信息提供了一个公共为止,即Math对象 -
min()
和max()
方法,这两个方法经常用于避免多余的循环和在if
语句中确定一组数的最大值 -
Math.ceil()
,向上舍入 -
Math.floor()
,向下舍入 -
Math.round()
,标准舍入,四舍五入 -
Math.random()
方法,返回大于等于0小于1的一个随机数 - 因为有了基本包装类型,所以JavaScript中的基本类型值可以被当作对象来访问
- 在所有代码执行之前,作用域中就已经存在两个内置对象,
Globa
l对象和Math
对象
第六章——面向对象的程序设计:
- 面向对象(Object-Oriented, OO)
- 属性类型:
- 描述了属性的各种特征
- 为了表示特性是内部值,规范把它们放在了两对儿方括号中
- 分为数据属性和访问器属性两种
- 数据属性:
[ [Configurable] ]、[ [Enumerable] ]、[ [Writable] ]、[ [Value] ]
- 要修改这些特性,需要用这个方法:
Object.defineProperty()
- 访问器属性:
[ [Configurable] ]、[ [Enumerable] ]、[ [Get] ]、[ [Set] ]
- 访问器属性不能直接定义,必须使用
Object.defineProperty()
来定义 - 在这个方法之前,要创建访问器属性,会用到这两个非标准方法:
__defineGetter__()
和__defineSetter__()
- 这个方法可以一次性定义多个属性:
Object.defineProperties()
- 读取属性的特性的方法:
Object.getOwnPropertyDescriptor()
- 创建对象(工厂模式、构造函数模式、原型模式、组合使用构造函数模式和原型模式、寄生构造函数模式、稳妥构造函数模式)
- 工厂模式(有点像只是把构建对象的代码抽成一个方法来用而已)
- 按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以小写字母开头
- 使用构造函数的主要问题,是每个方法都要在每个实例中重新创建一遍
- 原型模式不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
-
原型模式的关系图如下:
-
Object.getPrototypeOf()
可以返回[ [Prototype] ]
的值 - 虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值
-
hasOwnProperty()
可以检测一个属性是否存在实例中,还是存在原型中,存在与对象实例中时,才会返回true
- 在单独使用时,
in
操作符会在通过对象能够访问给定属性时返回true
,无论该属性存在于实例中还是原型中 - 使用
for-in
循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中包括存在于实例中的属性,也包括存在于原型中的属性,即使[ [Enumerable] ]
的值是false
,也会被循环返回 -
Object.keys()
接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组 - 如果你想要所有实例属性,无论它是否可枚举,都可以使用
Object.getOwnPropertyNames()
- 更简单的原型语法:用一个包含所有属性和方法的对象字面量来重写整个原型对象,这样
constructor
属性就不再指向原来的那个构造函数了,自然也无法确定对象的类型了,如果这个值很重要,可以手动设置回去。 - 用上面那个方式重设
constructor
属性会导致它的[ [Enumerable] ]
特性被设置为true
,默认情况下,原生的constructor
属性是不可枚举的,因此可以用Object.defineProperty()
来设置为false
- 重写原型对象切断了现有原型于任何之前已经存在的对象实例之间的联系,它们引用的仍然是最初的原型
- 原型对象的问题:1.所有的实例在默认情况下都将取得相同的属性值;2.共享的本性会导致其中一个实例对象修改了,会影响到另外一个实例,包含引用类型值的属性特别突出
- 组合使用构造函数模式和原型模式:构造函数模式用于定义实例属性,而原型模式用来定义方法和共享的属性,这种方式使用最广
- 动态原型模式:可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型
- 寄生构造函数:在不返回值的情况下,默认会返回新对象实例,若在构造函数末尾添加一个
return
语句,可以重写调用构造函数时返回的值 - 寄生构造函数的问题:返回的对象与构造函数或者与构造函数的原型属性之间没有关系,因此不能依赖
instanceof
操作符来确定对象类型 - 稳妥构造函数模式跟寄生构造函数虽然类似,可是有不同点:1.新创建对象的实例方法不引用
this
;2.不使用new
操作符调用构造函数
- 继承(原型链、借用构造函数、组合继承、原型式继承、寄生式继承、寄生组合式继承)
- 接口继承只继承方法签名,而实现继承则继承实际的方法
- 许多OO语言支持上面两种继承方式,由于函数没有签名,所以ECMAScript只支持实现继承
- 原型链的基本概念:让原型对象等于另一个类型的实例,而另一个原型又是另一个类型的实例,就这样层层递进就构成了实例与原型的链条
- 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向
Object.prototype
- 确定原型和实例的关系:用instanceof操作符,只要用这个操作符来测试实例与原型链中出现锅的构造函数,结果就会返回true
- 确定原型和实例的关系:只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此
isPrototypeOf()
方法也会返回true - 在通过原型链实现继承时,不能使用对象字面量创建原型方法,这样做会重写原型链
- 原型链的问题:1.原型的引用类型共享问题,原型实际上会变成另一个类型的实例,于是,原先的实例属性也就变成了现在的原型的属性了;2.在创建子类型时,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数
- 借用构造函数有时候也叫伪造对象或经典继承
- 借用构造函数的基本思想是:在子类型构造函数的内部调用超类型构造函数
- 借用构造函数的优势:可以在子类型构造函数中向超类型构造函数传递参数
- 借用构造函数的问题:方法都在构造函数中定义,因此函数复用就无从谈起了
- 在超类型中原型定义的方法,对子类型而言是不可见的
- 组合继承指的是将原型链和借用构造函数的技术组合到一块,即使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承
- 组合继承的问题:无论在什么情况下,都会调用两次超类型构造函数
- 原型式继承的想法是借助原型可以基于已有的对象创建新对象
function object(o){
function F(){}
F.prototype = o;
return new F();
}
-
Object.create()
方法规范化了原型式继承,接受两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象 - 原型式问题:包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样
- 寄生式继承跟寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象
- 在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式
- 寄生组合式:通过借用构造函数类继承属性,通过原型链的混成形式来继承方法,也就是使用寄生式继承来继承超类型的原型
- 寄生组合式继承,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方法
第七章——函数表达式:
- 函数声明提升是指在执行代码之前会先读取函数声明
- 匿名函数也叫拉姆达函数
- 递归函数是在一个函数通过名字调用自身的情况下构成的
- arguments.callee
- 是一个指向正在执行的函数的指针,可以用来实现对函数的递归调用
- 这样就可以解除递归函数跟名字的耦合情况
- 可是在严格模式下会导致错误
- 可是可以使用命名函数表达式来达成上面相同的结果,而又不会出现错误
var factorial = (function f(num){
if(num <= 1){
return 1;
} else {
return num * f(num - 1);
}
});
- 闭包
- 闭包是指有权访问另一个函数作用域中的变量的函数
- 常见的创建方式,就是在一个函数内部创建另一个函数
- 后台的每个执行环境都又一个表示变量的对象——变量对象
- 闭包之所以能访问到包含函数的变量:是因为闭包函数在创建的时候,就会创建包含(全局变量对象、包含函数的变量对象)的这么一个作用域链,这个作用域链被保存在内部的[ [Scope] ]属性中,当闭包函数运行时,当前的活动对象(在此作为变量对象)被创建并被推入执行环境的作用域链前端。因此闭包的作用域链中就包含这包含函数的变量对象,因此可以访问到它的变量
- 包含函数执行完毕后,其执行环境的作用域链会被销毁,但它但活动对象仍然会留在内存中,直到匿名函数(闭包)被销毁后,它但活动对象才会被销毁,因此匿名函数但作用域链仍然可以引用这个活动对象
- 作用域链的这种配置机制引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值
- 可以通过创建一个匿名函数并且让它立即执行,强制让闭包的行为符合预期,解决掉上面提到的副作用
-
this
对象是在运行时基于函数的执行环境绑定的 - 每个函数在被调用时都会自动取得两个特殊变量,
this
和arguments
,内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量 - 把外部作用域中的
this
对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象(arguments
也要这样操作,才能访问到外部的) -
this
的值可能会意外地改变,即使是语法的细微变化,都有可能意外改变this的值 - 内存泄漏:如果闭包的作用域链中保存着一个HTML元素,那么就意外着该元素将无法被销毁
- 匿名函数可以用来模仿块级作用域并避免这个问题,块级作用域(通常称为私有作用域)
- 将函数声明包含在一对圆括号中,表示它实际是一个函数表达式
- 无论在什么地方,只要临时需要一些变量,就可以使用私有作用域
- 用匿名函数立即执行,实现私有作用域这种做法,经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数
- 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法
- 私有变量
- 任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量
- 私有变量包括函数的参数、局部变量和在函数内部定义的其他函数
- 如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量,这样就可以创建用于访问私有变量的公有方法
- 把有权访问私有变量和私有函数的公有方法称为特权方法
- 能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数
- 利用私有和特权成员,可以隐藏那些不应该被直接修改的数据
- 在构造函数中定义特权方法的缺点:必须使用构造函数模式来达到这个目的,这样针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题
- 静态私有变量
- 在私有作用域中定义私有变量和函数,这样外部就不能访问这些变量和函数,因此是私有的
- 而在私有作用域中新建一个全局的构造函数,这样新建的构造函数就可以在私有作用域之外被访问到,而构造函数在原型上定义的方法,作为特权方法,这个特权方法作为一个闭包,总是保存这对包含作用域的引用
- 使用这种方式创建的静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己但私有变量,因为这样但变量变成来一个静态的、由所有实例共享的属性
- 模块模式
- 所谓单例,就是只有一个实例的对象
- 模块模式是为单例创建私有变量和特权方法
- 模块模式通过为单例添加私有变量和特权方法能够使其得到增强
- 模块模式的基本思想:将一个匿名函数赋值给一个变量,这个匿名函数里面定义了私有变量和函数,将一个对象字面量作为函数的值返回
- 如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以用这个模式
- 增强的模块模式:适合那些单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况
第十一章——DOM扩展:
- 选择符API
-
querySelector()
方法,返回匹配的第一个元素 -
querySelectorAll()
方法,返回所有匹配的元素 -
matchesSelector()
方法,浏览器支持程度不同,如果调用元素与该选择符匹配,返回true,否则返回false - 以上三个方法都接受一个参数,就是css选择符
- 元素遍历,因为不同有的浏览器会返回文本节点,有的不会,为了确保遍历的是元素,且行为一致,因此新定义了下面的一些属性
childElementCount
firstElementChild
lastElementChild
previousElementSibling
nextElementSibling
- 利用这些属性找到的元素就不必担心空白文本节点
- HTML5
-
getElementsByClassName()
方法,接受一个参数,一个包含一或多个类名的字符串,传入多个类名时,类名的先后顺序不重要 -
className
中是一个字符串,即使修改字符串一部分,也必须每次都设置整个字符串值,即某个元素有三个类名,要删除其中一个,需要拆开,去掉相应的,然后在把去掉后的拼成字符串,重新设置回去 - 利用
classList
属性,就不用像上面那样麻烦了,这个属性有一个表示自己包含多少元素的length
属性,取得元素可以用item()
或者使用方括号语法 -
classList
属性还定义了这些方法:add(value)、contains(value)、remove(value)、toggle(value)
- 元素获得焦点的方式有页面加载、用户输入(通常是按Tab键)和在代码中调用
focus()
方法 -
document.activeElement
属性,始终会引用DOM中当前获得了焦点的元素,文档加载期间,这个属性的值是null
-
document.hasFocus()
方法,用于确定文档是否获得了焦点 -
document.readyState
属性,有两个值:loading
(正在加载文档)和complete
(已经加载完文档) -
document.compatMode
属性,这个属性是为了告诉开发人员浏览器采用了哪种渲染模式,标准模式下返回的值是CSS1Compat
,混在模式返回的值是BackCompat
- 作为对
document.body
引用文档的元素的补充,HTML5新增了
document.head
属性 - 字符集属性,
charset
属性表示文档中实际使用的字符集,也可以用来指定新字符集 - 属性
defaultCharset
表示根据默认浏览器及操作系统的设置,当前文档默认的字符集应该是什么 - 可以为元素添加非标准的属性(自定义数据属性),需要添加前缀
data-
- 可以通过元素的
dataset
属性来访问和设置自定义属性的值 -
innerHTML
属性:读模式下,会返回与调用元素所有的子节点的HTML标记,写模式下,会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点 - 不要指望浏览器返回的
innerHTML
值完全相同 -
innerHTML
属性的一些限制,大多数浏览器,通过innerHTML
插入元素并不会执行其中的脚本,通常会在前面加
input
标签解决这个问题,还有一些元素不支持这个属性等等 - 使用
innerHTML
从外部插入HTML,都应该首先以可靠的方式处理HTML,IE8提供了window.toStaticHTML()
方法,接受一个参数,即一个HTML字符串,其他浏览器尽可能手工检查下其中的文本内容outerHTML
属性:读模式下,返回调用它的元素及所有子节点的HTML标签,写模式下,会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素insertAdjacentHTML()
方法,接受两个参数,插入位置和要插入的HTML文本,第一个参数必须是下列值之一(beforebegin、afterbegin、beforeend、afterend)
- 在使用
innerHTML
、outerHTML
和insertAdjacentHTML()
时,最好先手工删除要被替换的元素的所有事件处理程序和javaScript对象属性innerHTML
属性比通过多次DOM操作先创建节点再指定它们之间的关系,效率高很多,因为在设置innerHTML
或outerHTML
时,就会创建一个HTML解析器,这是在浏览器级别的代码基础上运行的,比执行javaScript快得多- 创建和销毁解析器也会带来性能损失,所以最好能够设置
innerHTML
或outerHTML
的次数控制在合理的范围内,例如:可以单独构建字符串,然后再一次性地将结果字符串赋值给innerHTML
scrollIntoView()
方法,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中,可以传入true
或者false
作为参数,也可以不传- 专有扩展
- 文档模式,决定了可以使用什么功能。值分别可以是:
(Edge、EmulateIE9、EmulateIE8、EmulateIE7、9、8、7、5)
-
document.documentMode
属性可以知道给定页面使用的是什么文档模式 -
children
属性:跟childNodes
的区别是,只包含元素中同样还是元素的子节点 -
contains()
方法:用来检测某个节点是不是另一个节点的后代,调用这个方法的应该是祖先节点,也就是搜索开始的节点,这个方法接受一个参数,即要检测的后代节点 -
compareDocumentPosition()
方法也能够确定节点间的关系,返回一个表示关系的位掩码 -
innerText
属性:读模式会按照由浅入深的顺序,将子文档树中的所有文本拼接起来,写模式会删除元素的所有子节点,插入包含相应文本值的文本节点 - 设置
innerText
永远只会生成当前节点的一个子文本节点,为了确保只生成一个子文本节点,必须对文本进行HTML编码,可以通过innerText
属性过滤掉HTML标签,方法如下:
div. innerText = div. innerText;
- Firefox虽然不支持
innerText
,但支持类似的textContent
属性 -
outerText
属性:读模式跟innerText
的结果一样,写模式,不只是替换调用它的元素的子节点,会替换整个元素(包含子节点) - 滚动的方法:
scrollIntoViewIfNeeded(alignCenter)、scrollByLines(lineCount)、scrollByPages(pageCount)
- 使用