《JavaScript权威指南》读书笔记

刚拿到权威指南这本书的时候被他的厚度吓了一跳,近千页密密麻麻的内容,心想不知道什么时候能看完。加之是刚接触JS,所以只能硬着头皮从头开始看。下面就是部分读书笔记,好记性不如烂笔头嘛。

<!--more-->

这本书将整个JavaScript分成了:前面部分是JavaScript语言特性和客户端JavaScript,后面部分是关于语言核心和客户端的参考。因此决定先通读前面的语言特性和浏览器API,而后面相关的参考作为字典查阅。 JavaScript语言核心有一些很重要的知识点,比如闭包,原型链,正则表达式等;客户端JavaSCript也有诸如BOM,DOM和HTML5接口等分类。单是某些知识点就不能仅用一篇博文记载,因此这篇博文主要记载在阅读本书时了解到的一些容易被忽略的地方。

1. 语言核心

语言核心主要介绍了JavaScript的语法结构,数据类型和变量,表达式和语句,函数,数组和对象等特性。

1.1. 语言环境

由于历史的原因,JavaScript发展历经波折,也出现了许多不太好的语言特性,出现了使用"use strict"的严格模式和普通的非严格模式。严格模式下对一些不好的语言特性进行了限制。 比如,严格版不支持八进制数字,也禁止出现不使用var声明的变量。 但是现在JavaScript发展非常迅速哦。

1.2. 数据类型

在JavaScript中,数据只分为原始类型(数字,字符串,布尔值,null和undefined)与对象类型;

虽然数字字符串与布尔值这些原始类型不是对象,其行为与不可变对象非常相似,他们都可以可以使用类似于对象的的方法,主要是临时生成了一个包装对象,上述类型在读取其原有的属性和方法时像对象一样,但是如果对其属性进行赋值则会忽略这个操作(包装对象在调用属性和方法完毕之后就销毁了)。

除了null或undefined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法的返回结果一致。换句不恰当的话说:在JavaScript中,一切皆对象~~

但是,原始类型的比较是通过值得比较来判断是否相等,但是对象只有同时为同一个对象的引用时才相等,否则即使对应的值完全相同也不相等。也就是说原始类型的赋值时值的传递,而对象的赋值是同一个对象的引用。

另外书中还谈论了一个小问题:nullundefinded的区别是什么?

前者表示非对象的空值;后者表示数字,字符串或者对象是无值的,通常用于已声明但没有进行赋值的变量。

1.3. 变量和作用域

每个JavaScript解释器启动时,都会创建一个全局对象(在浏览器环境下就是window对象)。

1.3.1. 变量声明

JS的变量是无类型的,通过使用var来声明变量,在全局作用域下的变量被称作全局变量,全局变量看作是全局对象的一个属性。在非严格模式下,在任何地方出现的不使用var进行声明而直接赋值的变量也会作为全局变量。全局变量可以在代码的任何地方使用,我们应当尽量避免使用全局变量。

1.3.2. 变量作用域

JS的变量作用域为函数作用域,声明的所有变量(var function)都会被提前至作用域区的顶部,且在变量被赋值之前其值为undefined,变量在声明它的函数体内及该函数体内嵌套的所有函数体内都有有定义的。当既没有声明又没有赋值就直接调用变量,就会发生错误。

前面提到全局变量(不在任何函数体内声明)可以看作是全局对象的属性,那么局部变量(即在函数体内声明的变量)可以看作是调用该函数的那个对象的属性;需要注意的是当使用var进行声明的时候,创建的这个属性数不可配置的,即无法用delete删除。

每一段代码(全局代码或者函数块)都有一个与之相关的作用域链,这个链是一个对象列表(函数也是对象)或者链表,这组对象定义了作用域中的变量,当需要查找某个变量时,将从第一个对象(最近的那个函数作用域)一直寻找至全局对象,也就是说,函数的嵌套形成了作用域链。

所谓闭包,也就是作为函数的参数或这是函数的返回结果的一个嵌套函数。由于作用域的存在,内部的嵌套函数可以访问外部函数作用域内的变量,并且可以将这些变量的生命周期延长到其父函数的生命周期之外,

每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的

1.4. 表达式和语句

1.4.1. 表达式

表达式就是一条代码短语,用于提供给解释器并最终计算出一个结果。表达式分为了简单表达式和复杂表达式,其中:

  • 简单表达式指不包含其他表达式的原始表达式(比如一个变量名)
  • 复杂表达式是简单表达式通过运算符组合起来的。

属性访问表达式指的:是通过 “.”访问符和"[]"访问符进行对象属性或者数组值进行访问。 当“[]"时将首先计算方括号内的表达式的值 并将其转换为字符串,这里需要注意的是如果属性名是字符串,则[]中也必须使用加上引号的字符串, 否则解释器会将它当成一个变量(变量名也是一个表达式)并计算其值,命名的属性不存在则表达式的值为undefined(而不是报错,对象不存在或null或nudefined则访问其属性就会报错)。如果属性名是一个保留字或空格标点符号及数字时只能使用方括号。

在计算表达式的时候,可能会进行类型转换。所有对象转换为布尔值均为真,==运算符不进行类型的判断,恒等运算符===才进行类型检测。 switch(case)选择语句进行匹配时,不会发生类型转换,也就是说,switch语句进行的是恒等计算。

1.4.2. 语句

表达式用来计算一个值,而语句是来执行以使某件事情发生(以分号 结尾)。有副作用的表达式称为表达式语句,此外声明语句用来声明新变量或者定义新函数。

函数既可以通过表达式声明(将函数定义表达式赋值给一个变量var f = function(){},也可以通过语句声明function f(){},其中,前者无法在函数赋值之前通过该变量名调用,而后者在变量提升的影响下在作用域内均可以调用而无论函数具体的声明位置。

也就是说,变量的声明是会提前的。但是采用var声明的变量,执行初始化的操作仍然在代码原位置,如果在初始化之前访问该变量值为undefined,此外多次声明同一个变量也是可以的,因为声明都会提前且值均置为undefined;而采用function进行函数声明语句不仅声明会提前,函数体也会提前(以表达式定义的函数在定义前无法被使用)。

1.4.3. 运算符的优先级

在表达式中,容易被忽略但是十分重要的一点就是运算符的优先级,书中也没有讲得很详细,具体可以查看这里,基本上是:括号 > 算术运算符 > 逻辑运算符 > 赋值运算符

1.5. 对象

1.5.1. 属性

JavaScript中的万物皆为对象(和包装对象),JS的对象都是关联数组。属性名一般都用字符串,尽管可以使用其他的值;当使用“.”时,后面跟的属性名是标识符,在程序运行的时候无法动态的修改;当使用[]时,属性名是通过字符串来表示的,可以在运行时更改。因此当不知道属性名的具体名字时一般采用方括号的形式。这样也可以为对象设置动态的属性名称。

可以使用for in语句遍历对象属性,for variable in object在运行前会先计算object表达式,如果是null和undefined会跳过循环,如果是原始值则转换为包装对象,否则object本身就为对象了,将依次枚举对象的属性来执行循环,在每次循环之前,都先计算variable表达式的值,并将属性名(一个字符串)赋给他。(注意并不会枚举内置型方法和其他不可枚举的属性)。

可以使用delete删除对象属性,但是delete只能删除自有属性(返回布尔值,只要删除成功或者没有任何副作用就为true),无法删除继承属性,要删除继承属性必须到定义这个属性的原型对象上删除。但是delete只是断开属性与宿主对象的联系,而不会去操作属性中的属性(意思是属性也是一个对象),由于已经删除的属性的引用依然存在,则会发生内存泄漏,因此在销毁对象时,需要遍历属性中的属性,依次删除。

对象的属性包含了四种特性,除了值之外还包括可写性,可枚举性和可配置性,需要注意的是存取器属性(setter getter)不具有值和可写性(由setter是否存在决定),因此存取器的特性是读取,写入,可枚举性和可配置性。

1.5.2. 方法

对象不仅包含了特定的值,还可以拥有特定的方法,可以把方法看作是特殊的函数,他们对于调用的参数及返回值的处理完全相同,但是最主要的区别在于调用上下文(context,这个翻译很诡异的感觉)。在非严格模式下,函数调用的上下文是全局对象,而在严格模式下是undefined;方法调用的上下文是拥有该方法的这个对象,在方法调用中可以使用this调用该对象。

当方法的返回值是一个对象的时候,该对象还可以继续调用它的方法,就像jQuery那样,因此当方法不需要返回值的时候,可以直接返回this,这样使用API就可以进行链式调用的编程风格。此外需要注意,嵌套的函数并不会从调用它的函数中继承this,如果需要应该将this保存在变量中,一般用self表示。

1.5.3. 原型链

JavaScript是通过原型实现继承的,每一个对象都与“原型”相关联,并且从原型继承相关属性,因此,对象的属性分为继承属性和自定义属性。且当对象继承的原型也具有原型的时候,这一系列链接的原型对象就是原型链。(待补充)

1.5.4. 数组

JavaScript中的数组是一种特殊的对象:属性名是递增的数字。简单总结了数组常用的方法:

  • 为新索引值赋值就可以添加元素,或者使用push和unshift(返回值为新数组长度);
  • 如果将数组的length属性值减小,当前数组中那些索引值大于等于length的元素将被删除(需要注意的是将length增大,并不会数组中添加元素只是在数组尾部创建一个新的空白区域)。
  • 采用delete删除数组元素时,只是将对应索引位置的值变为undefined而不会改变数组长度;此外可以采用pop和shift从尾部或者头部删除元素(返回的是删除的元素值)。
  • pop/push shift/unshift在尾部/头部进行删除/添加操作;
  • join("-")通过参数的字符将数组连接为字符串,默认为逗号,是字符串split()的逆向操作;
  • reverse()将数组中元素顺序颠倒(替换的是原数组本身);
  • sort()排序,可以传入一个比较函数f(a,b),若要第一个参数在前则返回负数,第二个参数在前则返回整数,相等返回0;
  • contact()连接两个数组的元素,并组合成一个数组,如果某个数组的元素包含数组,并不会递归扁平化数组的数组。 slice(n1,n2)返回数组的一部分,前闭后开区间。如果参数为负数表示相对于数组最后一个元素的位置,-1表示最后一个元素。该方法并不会修改原数组而是返回一个新的子数组;
  • splice(start,num,v1,v2...)该方法是在数组中插入或删除元素的通用方法,第一个参数指定操作(插入或删除)的起始位置(元素索引值),第二个参数指定需要删除的个数,后面的参数表示插入的元素,除了第一个参数后面的参数都是可省略的。但是如果只是想插入元素,第二个参数也必须设置为0而非空。

此外,新版本还新增的几种数组方法,这几种方法的第一个参数一般都是一个函数,函数的参数依次为数组元素,元素索引和数组本身(参数可以部分省略);而这些方法的第二个参数一般都是this,即拥有第一个参数方法的那个对象。被调用函数的返回值十分重要。

  • forEach(f)从头到尾遍历数组并对每个元素调用所指定的方法;
  • map(f)同上,区别为对每个元素调用方法,这个方法的返回值将组成一个新的数组,然后由map返回该数组,该数组与原始数组具有相同的长度;
  • filter(f)该方法也返回一个数组,为原始数组经过过滤之后的子数组,f方法为过滤操作,如果返回为真则将数组元素添加进子元素中,注意该方法返回的是稠密数组;
  • every(f)和some(f)前者对所有数组元素进行判断,f全部返回true,every才返回true;后者只要有一个f返回true,则some返回true;这两个函数会执行短路操作。
  • indexOf()和lastIndexOf()返回给定目标元素的索引值。

2. 客户端的JavaScript

待补充...