JavaScript权威指南第六版(阅读笔记)
前言:
对于软件行业学习是无止境的,因为知识更替非常快,能够快速稳固掌握一门新技术是一个程序员应该具备的基本素质。
了解一门语言,了解它的概念非常重要,但是一些优秀的设计思想需要细心和大量实践才能慢慢参悟,在这之前需要做的是能够运用它来开发,那么了解一些基础特性非常有必要,通常这些特性是需要经验积累,从各种坑中积累出来,但是还有一种看似很笨却很有效的学习方法.那就是将别人的经验记录下来,有事没事都拿出来看看,集合开发中的经验,这会非常有效。
记录的目的在于以后翻阅,同时能将厚厚的书籍里面核心的概念以及技术点去其糟粕,取其精华同时做一个梳理,也会方便了解自己掌握程度以及针对性的学习未掌握的部分。
编程语言的词法结构是一套基础性规则,用来描述如何使用这门语言的编写程序。作为语法的基础,它规定了诸如变量是什么样的、怎么写注释,以及程序语句之间如何分隔等规则。
字符集:
区分大小写
JavaScript是区分大小写的语言。也就是说,关键字、变量、函数名和所有的标识符都必须采用一致的大小写形式
HTML不区分大小写但是XHTML区分大小写
JavaScript程序是用Unicode字符集编写的。Unicode是ASCII和Latin-1的超集,并支持地球上几乎所有在用的语言
空格、换行符和格式控制符
JavaScript会忽略程序中标识之间的空格,多数情况下统一回忽略换行符
注释
在行尾 "//" 之后的文本都会被JavaScript当做注释忽略掉,此外 "/*" 和 "*/" 之间的文本也会当做注释,这种注释可以跨行书写,但不能有嵌套的注释
直接量
程序中直接使用的数据值 12(数字) 1.2(小数) "hello world"(字符串) true(布尔值) null 正则表达式 对象 数组
标识符和保留字
标识符就是一个名字。在JS中,标识符用来对变量和函数名进行命名,或用作JS代码中某些循环语句中的跳转位置的标记。
- 标识符必须以字母、下划线、美元符开始
- 保留字就是JS把一些标识符拿出来用作自己的关键字,因此,就不能再程序中把这些关键字用作标识符了
- 可选的分号
-
- 缺少分隔符,一条语句结束就成了下一条语句的开始
- 在JS中,如果语句各自独占一行,通常可以省略语句之间的分号(“}”也可以省略),JS会在换行处自动填补分号,但并不是所有换行出都会自动填补,只有在可以解析的情况下才会填补
- 在编程语言中,能够表示并操作的值的类型称做数据类型,编程语言最基本的特性就是能够支持多种数据类型。
- 当程序需要将值保存起来以备将来使用时,便将其赋值给一个变量
- 变量是一个值的符号名称,可以通过名称来获得对值的引用。变量的工作机制是编程语言的另一个基本特性
- JS的数据类型分为两类
-
- 原始数据(primitive type)类型,包括数字、字符串和布尔值
- 对象(object type)类型,JS中除了数字、字符串、布尔值、Null、Undefined之外的就是对象了
-
- 对象(object)是属性(property)的集合,每个属性都由“名/值对”
- JS定义了一种特殊的对象——数组(array)
- JS定义了一种特殊的对象——函数
- 数字
-
- JS不区分整数值和浮点数值,JS中所有数字均用浮点数值表示
- 当一个数字直接出现在程序中,我们称之为数字直接量
- JS支持多种格式的数字直接量
-
- 十进制
- 十六进制
- 数组下标的32位整数
- 浮点型直接量
- JS中的算术运算
-
- JS中的算术运算在溢出、下溢或被零整除时不会报错。当数字运算结果超过了JS所能表示的数字上限,结果为一个特殊的无穷大值,JS中以Infinity表示
- 下溢是当运算结果无限接近于零并比JS能表达的最小值还小的时候,JS将返回0
- 从技术上讲,只有JS对象才能拥有方法。然而数字、字符串和布尔值也可以拥有自己的方法(3.6节进行讲解)
- 原始类型/对象类型
- 可变类型/不可变类型
-
- 可变类型:值是可以修改的,数组与对象就是可变类型
- 不可变类型:数字、字符串、布尔值、NULL和Undefined——比如,修改一个数值的内容本身就说不通
- JS可以自由地进行数据类型转换。比如,程序期望使用字符串的地方使用了数字,JS会自动将数字转换为字符串,使用布尔值的地方使用非布尔值,也会进行自动转换
- JS中灵活的类型转换规则对“判断相等”的定义有影响。等号运算符“==”
- JS变量是无类型的,变量可被赋值任何类型,同样一个变量也可重新赋予不同类型的值
- 词法作用域
- 日期和时间
- 字符串
-
- 字符串是一组由16位值组成的不可变的有序序列,每个字符串通常来自于Unicode字符集
- JS定义的各式字符串操作方法均作用于16位值
- JS中字符串是不可变类型,JS中的字符串是固定不变的,类似replace()的方法都返回新的字符串,原始字符串本身并没发生改变
- 字符串直接量
- 转义字符
-
- JS字符串中反斜线(\)有着特殊的用途,反斜线符号后加一个字符,就不再表示它们字面含义了,比如,\n就是一个转义字符,它表示换行符
- \'转义符,表示单引号
- 字符串的使用
-
- JS的内置功能之一就是字符串连接。字符串使用加(+)号连接,表示两数相连
- 匹配模式(正则表达式)
- 布尔值
-
- 布尔值指代真或假、开或关、是或否。这个类型只有两个值,保留字true和false
- 程序中的比较语句的结果通常都是布尔值
- 布尔值通常用于JS中的控制结构中
- 任意JS的值都可以转换为布尔值。(undefined、null、0、-0、NaN、“”//空字符串)这些值都会被转换成false。所有其他的值,包括所有对象(数组)都会转换成true。可以转换为false的值有时称做“假值”,反之称为“真值”
- 布尔值包含toString()方法,这个不重要的API可以将布尔值转换为字符串
- “&&”运算符执行了逻辑与(AND)操作。当且仅当两个操作数都是真值时才返回true
- “||”运算符是布尔或(OR)操作
- “!”一元操作符执行了布尔非(NOT)操作
- null和undefined
-
- null是JS语言的关键字,表示一个特殊值,用来描述“空值”。对null执行typeof预算,结果返回字符串“object”,也就是说,可以将null认为是一个特殊的对象值,含义是“非对象”。通常认为null是它自有类型的唯一一个成员,它可以表示数字、字符串和对象是“无值”的。
- undefined不是JS语言的关键字,它与null一样表示空缺。用未定义的值表示更深层次的“空值”。它是变量的一种取值,表明变量没有初始化。typeof运算符得到undefined的类型,则返回“undefined”,表明这个值是这个类型的唯一成员
- null与undefined是不同的,但是它们都是表示“值的空缺”,两者往往可以互换。判断相等运算符“==”认为两者是相等的(哟啊使用严格相等运算符“===”来区分它们)。它们都不包括任何属性和方法。使用”.“和”[]“来存取这两个值的成员或方法都会产生一个类型错误
- 或许可以认为undefined是系统级的,出乎意料的或类似错误的值的空缺,而null是表示程序集的、正常的或在意料之中的值的空缺。如果对他们进行操作,赋值给变量或者属性,或作为参数传入函数,最佳选择是使用null
- 全局对象
-
- 当JS解释器启动时或者在任何Web浏览器加载新页面的时候,它将创建一个新的全局对象,并给它一组定义的初始属性
-
- 全局属性,比如undefined、Infinity和NaN
- 全局函数,比如isNaN()、parseInt()
- 构造函数,比如Date()、RegExp()、String()、Object()和Array()
- 全局对象,比如Math和JSON
- 全局对象的初始属性并不是保留字,但它们应该当作保留字来对待
- 客户端JS来讲,Windows对象定义了一些额外的全局属性
- 在代码的最顶级——不再任何函数内的JS代码——可以使用JS关键字this来引用全局对象
- 在客户端JS中,在其表示的浏览器窗口中的所有JS代码中,Window对象充当了全局对象。
- 包装对象
-
- JS对象是一种复合值,它是属性或已命名值的集合。通过”.”符号来引用属性值。当属性值是一个函数的时候,称其为方法。通过o.m()来调用对象o中的方法
- 字符串也同样具有属性和方法,字符串不是对象,但是有属性。只要引用了字符串的属性,JS就会将字符串s值通过调用new String(s)的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁。
- 同字符串一样,数字和布尔值也具有各自的方法:通过Number()和Boolean()构造函数创建一个临时对象。null和undefined没有包装对象,访问它们的属性会造成一个类型错误
- “==”等于运算符将原始值和其包装对象视为相等,当“===”全等运算符将它们视为不相等,使用typeof运算符可以看到原始值和其包装对象的不同
- 不可变的原始值和可变的对象引用
-
- JS中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着本质的区别。
- 原始值是不可更改的,任何方法都无法更改一个原始值
- 对数字和布尔值来说改变数字的值本身就说不通,对字符串来说,不那么明显,看起来字符串组成像是对各字符的数组,我们期望通过制定索引下标来修改字符串中的字符。实际上JS是禁止这样做的,字符串所有方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值
- 原始值的比较是值的比较,只有在它们的值相等时它们才会相等
- 如果比较两个单独的字符串,当且仅当它们的长度相等且每个索引的字符都相等时,JS才认为他们相等
- 对象和原始值不同,它们是可变的——它们的值也是可修改的
- 对象的比较并非值的比较,即使两个对象包含同样的属性及相同的值,它们也是不相等的,各个索引元素完全相等的两个数组也不相等
- 通常称对象为引用类型,以此来和JS基本类型区分开
- 对象值都是引用,对象的比较是引用的比较
- 当且仅当它们引用同一个基对象时,它们才相等
- 将数组或对象赋值给一个变量,仅仅是赋值的引用,对象本身并没有复制一次
- 类型转换
-
- JS取值非常灵活,从布尔值可以看出,当JS期望使用一个布尔值的时候,可以提供任意类型值,JS将根据需要自行转换类型
- 显示类型转换
-
- JS可以自动做许多类型转换,但为了代码变得清晰易读而做的显示转换
- 显示类转换最简单的方法使用Boolean()、Number()、String()或Object()函数
- 对象转换为原始值
-
- 所有对象继承了两个转换方法。
- toString()
-
- 第一个是toString(),它的作用是返回一个反映这个对象的字符串
- 很多类定义了更多特定版本的toString()
- 数组类的toString()方法,将每个数组元素转换为一个字符串,并在元素之间添加都好后合并成结果字符串
- 函数类的toString()方法,返回这个函数的实现定义的表示方式,通常是将用户定义的函数转换为JS源代码字符串
- 日期类的toString()方法,返回一个可读的日期和时间字符串
- RegExp类的toString()方法,将RegExp对象转换为表示正则表达式直接量的字符串
- valueOf()
-
- 这个方法任务并未详细定义,如果存在任意原始值,它就默认将对象转换为表示它的原始值
- 对象是复合值,而且大多数对象无法真正表示为一个原始值,因此简单的valueOf()方法简单的返回对象本身,而不是返回一个原始值
- 重复声明与遗漏声明
-
- 使用var语句重复声明变量是合法且无害的。如果重复声明带有初始化器,那么这就跟一条简单的赋值语句没什么两样
- 如果试图读取一个没有声明的变量值JS会报错
- 给一个没有声明的变量赋值也会报错
- 变量作用域
-
- 变量的作用域是程序源代码中定义这个变量的区域
- 全局变量拥有全局作用域
- 函数体内声明的变量作用域是局部性的,它们只在函数体内有定义
- 函数作用域和声明提前
-
- JS没有块级作用域,JS取而代之的使用了函数作用域
- JS的函数作用域是指在函数内声明的所有变量在函数体内始终可见,意味着变量在声明之前已经可用,JS的这个特性被非正式的成为声明提前,即JS函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部
- 作为属性的变量
-
- 当声明一个JS全局变量时,实际上是定义了全局对象的一个属性
- 当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除
- JS全局变量是全局对象的属性,这是ECMASscript规范中强制规定的,对于局部变量则没有如此规定
- ECMAScript 5 规范称“声明上下文对象”,JS可以允许使用this关键字来引用全局对象,却没有方法可以引用局部变量中存放的对象。这种存放局部变量的对象的特有性质,是一种对我们不可见的内部实现。
- 作用域链(未完全理解)
-
- JS是基于词法作用域的语言:通过阅读包含变量定义在内的数行源码就能知道变量的作用域。
- 全局变量在程序中始终都是有定义的
- 局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的
- /*
- 如果将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。
- 每一段JS代码(全局代码或函数)都有一个与之关联的作用域链。
- 这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。
- 当JS需要查找变量x的值的时候(这个过程称做变量解析),它会从链接中的第一个对象开始查找,如果这个对象有名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,JS会继续查找链接的下一个对象。
- 如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推
- 如果作用域链就上没有任何一个对象含有属性x,那么就认为这段代码的作用域链接上不存在x,并最终抛出一个引用错误异常
- */
- /*
- 在JS的最顶层代码中(也就是不包含在任何函数定义的代码),作用域链由一个全局对象组成
- 在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象
- 在一个嵌套的函数体内,作用域链上至少有三个对象
- 理解对象链的创建规则是非常重要的
- 当定义一个函数时,它实际上保存一个作用域链。
- 当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”
- 对于嵌套函数来讲,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的
- 内部函数在每次定义的时候都有微妙的差别,在每次调用外部函数的时候,每部函数的代码都是相同的,而且关联这段代码的作用域也不相同
- 表达式JS中的一个短语,JS解释器会将其计算出一个结果
- 常量、变量名都是一种简单的表达式
- 复杂表达式是由简单表达式组成的
- 简单表达式组合成复杂表达式最常用的方法就是使用运算符。根据运算符规则返回一个值
- 原始表达式
-
- 最简单的表达式是“原始表达式”
- 原始表达式是表达式的最小单位,它们不再包含其他表达式
- JS原始表达式包含常量、关键字、变量
- 对象和数组的初始化表达式
- 函数定义表达式
- 属性访问表达式
- 调用表达式
- 对象创建表达式
- 省略部分。。。。。。
- 表达式在JS中是短语,那么语句就是JS整句或命令
- 语句块的结尾不需要分号。块中的原始语句必须以分号结束,但语句块不需要
- 语句块中的缩进,这不是必须的,但能增强代码的可阅读性
- JS中没有块级作用域,在语句块中声明的变量并不是语句私有的
- 对象是JS的基本数据类型
- 对象是一种复合值
- 对象也可看做是属性的无序集合,每个属性都是一个名/值对
- 属性名是字符串,因此我们可以把对象看成是对字符串到值的映射
- 这种基本数据类型还有很多叫法,有些我们已然非常熟悉,比如散列、散列表、字典、关联数组
- 然而对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JS对象还可以从一个称为原型的对象继承属性
- 对象的方法通常是继承的属性,这种“原型式继承”是JS的核心特征
- JS对象是动态的可以增删属性
- 除了字符串、数字、true、false、unll、undefined之外,JS中的值都是对象
- 对象最常见的用法是创建、设置、查找、删除、检测、枚举它的属性
- 属性包括名字和值
- 属性名可以是包含空字符串在内的任意字符串
- 对象中不能存在两个同名的属性
- 值可以是任意JS值,或者(在ECMASscript5中)可以是一个getter或setter函数
- 除了名字和值之外,每个属性还有一些与之相关的值,称为“属性特性”
- 属性特性
-
- 可写,表明是否饿可以设置该属性的值
- 可枚举,表明是否可以通过for/in循环返回该属性
- 可配置,表明是否可以删除或修改该属性
- 对象特性
-
- 对象的原型,指向另为一个对象,本对象的属性继承自它的原型对象
- 对象的类,是一个标识对象类型的字符串
- 对象的扩展标记,指明了(在ECMAScript 5中)是否可以向该对象添加新属性
- 三类JS对象的两类属性作区分
-
- 内置对象,是由ECMAScript规范定义的对象或类。例如数组、函数、日期、增则表达式
- 宿主对象,是由JS解释器所嵌入的宿主环境(比如Web浏览器)定义的
- 自定义对象,是由运行中的JS代码创建的对象
- 自有属性,是直接在对象中定义的属性
- 继承属性,是在对象的原型对象中定义的属性
- 创建对象
-
- 对象直接量
- 关键字new创建
- Object.create()函数创建
- 对象直接量
-
- 创建对象直接量最简单的方式就是在JS代码中使用对象直接量
- 对象直接量是由若干名/值对组成的映射表
- 属性名可以是JS标识符也可以是字符串直接量(包括空字符串)
- 对象之间量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象
- 每次计算对象之间量的时候,也都会计算它的每个属性值
- 重复调用的函数中的循环体内使用了对象直接量,它将创建很多新对象,并且每次创建的对象的属性值也有可能不同
- 通过new创建对象
-
- new运算符创建并初始化一个新对象
- 关键字new后跟随一个函数调用,这里的函数称做构造函数
- JS语言核心中的原始类型都包含内置构造函数
- 原型
-
- 每一个JS对象(null除外)都和另一个对象相关联,就是原型,每一个对象都从原型继承属性
- 所有对象直接量创建的对象都具有同一个原型对象,并可以通过JS代码Object.prototype获得对原型对象的引用
- 通过new和构造函数调用创建的对象的原型就是狗找函数的prototype属性的值
- 没有原型的对象为数不多,Object.prototype就是其中之一
- Object.create()
-
- Object.create()是一个静态函数,而不是提供给某个对象调用的方法
- 可以传入参数null创建一个没有原型的新对象,这种方式创建的对象不会继承任何东西,甚至不包括基础方法,比如toString(),也就是说它将不能和“+”运算符一起正常工作
- 属性的查询和设置
- 作为关联数组的对象
-
- 第一种语法使用点运算符和一个标识符运算符,这和C和Java中访问一个结构体或对象的静态字段非常类似
- 第二种语法使用方括号和一个字符串,看起来更像数组,这个素组元素是通过字符串索引而不是数字索引,这种数组就是关联数组,也称散列、映射、字典
- 继承(未完全理解)
-
- JS对象具有“自有属性”,也有一些属性从原型对象继承而来
- 假设o对象的属性x赋值,如果o中已经有属性x(这个属性不是继承来的),那么这个赋值操作只会改变这个已有属性x的值
- 如果o中不存在属性x,那么赋值操作给o添加一个新属性x
- 如果之前o继承自有属性x,那么这个继承的属性就被新创建的同名属性覆盖了
- 属性赋值操作首先检查原型链,以此判断是否允许赋值操作
- 如果o继承自一个只读属性x,那么赋值操作是不允许的,如果允许赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值,而不会去修改原型链
- 在JS中,只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关,这是JS的一个重要特性,该特性让程序员可以有选择地覆盖继承的属性
- 属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性,但有一个例外,如果o继承自属性x,而这个属性是一个具有setter方法的accessor属性,那么这时将调用setter方法而不是给o创建一个属性x
- 需要注意的是,setter方法是由对象o调用的,而不是定义这个属性的原型对象调用的。因此如果setter方法定义任意属性,这个操作只是针对o本身,并不会修改原型链
- 属性访问错误
-
- 属性访问并不总是返回或设置一个值
- 查询一个不存在的属性并不会报错,查找对象自有属性或继承的属性中均为找到属性,属性访问表达式返回undefined
- 如果对象不存在,那么试图查询这个不存在对象的属性就会报错,因为找到对象返回的是undefined而unll与undefind都没有属性
- 给unll和undefined设置属性也会报类型错误
- 对象自有属性是只读的,不能给只读属性重新赋值(defineProperty()方法中有一个例外,可以对可配置的只读属性重新赋值)
- 对象继承自有属性,且它是只读的,不同通过同名自有属性覆盖只读的继承属性
- 免去其它不可赋值情况下,对象的可扩展性是false,那么不能定义新属性
- 删除属性
-
- delete运算符可以删除对象的属性
- delete运算符只是断开属性和宿主对象的联系,而不会去操作属性中的属性
- delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)
- 当删除一个嵌套对象,由于删除的属性引用依然存在,可能因为这种不严谨的代码而造成内存泄漏。所有在销毁对象的时候,要遍历属性中的属性,依次删除
- 当delete表达式删除成或没有副作用(比如删除不存在的属性)时,它返回true,如果delete后不是一个属性访问表达式(比如 delete 1),同样返回true
- delete不能删除那些可配置性为false的属性(尽管可以删除不可扩展对象的可配置属性)。
- 检测属性
-
- in——in与那算符左侧是属性名(字符串),右侧是对象。如果对象自有属性或继承属性中包含这个属性则返回true
- hasOwnProperty()——方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回false
- propertyIsEnumerable()——是hasOwnProperty()增强版,只有检测到是自有属性且这个属性的可枚举性为true时它才返回true。通常由JS代码创建的属性都是可枚举的,除非使用特定方法改变属性的可枚举性
- !==——比较值
- 有一种场景必须使用in运算符,当对象值为undefined时
- 枚举属性
-
- 许多实用工具库给Object.prototype添加了新的方法或属性,这些属性方法可以被所有对象继承并使用
- for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。
- 对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的(除非转为成不可枚举)
- ECMAScript 5中定义了两个用以枚举属性名称的函数Object.keys()、Object.getOwnPropertyNames()
- 属性getter和setter
- * 属性的特性
-
- 除了包含名字和值之外,属性还包含一些标识它们可写、可枚举、可配置的特性
- 可以认为一个属性包含一个名字和4个特性。数据属性的4个特性分别是它的值、可写性、可枚举性、可配置性
- 存取属性不具有值特性和可写性,它们的可写性是由setter方法存在于否决定的。因此存取器属性的4个特性是读取、写入、可枚举、可配置
- 为了实现属性特性的查询和设置操作,ECMAScript 5中定义了一个名为“属性描述符”的对象,这个对象代表那4个特性
- 描述对象的属性和它们所描述的属性特性是同名的。因此,数据属性的描述符对象的属性有value、writable、enumerable、configurable
- 存取器属性的描述符对象则用get、set属性代替value和writable,其中writable、enumerable和configurable都是布尔值
- 通过调用Object.getOwnPropertyDescriptor()可以获得某个对象特定属性的属性描述符
- getOwnPropertyDescriptor()只能得到自有属性的描述符,想要得到继承属性的特性需要遍历原型链
- 通过definePeoperty()设置属性的特性,属性秒速符对象不必包含所有4个特性。这个方法要么修改已有属性要么新建自有属性,但不能修改继承属性
- 如果要同时修改或创建多个属性,则需要使用Object.defineProperties()
- 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性
- 如果属性是不可配置的,则不能修改它的可配置性和可枚举性
- 如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转换为数据属性
- 如果数据属性是不可配置的,则不能将它转换为存取器属性
- 如果数据属性是不可配置的,则不能将它的可写性从false修改为true,但可以从true修改为false
- 如果数据属性是不可配置且不可写的,则不能修改它的值。然而可配置但不可写属性的值是可以修改的(实际上是先将它标记为可写的,然后修改它的值,最后转换为不可写的)
- 对象的三个属性
-
- 每一个对象都有与之相关的原型、类、可扩展性
- 原型属性
-
- 对象的原型属性是用来继承属性的
- 原型属性是在实例对象创建之初就设置好的
- 通过对象之间量创建的对象使用Object.prototype作为它们的原型
- 通过new构造函数创建的对象使用构造函数的prototype属性作为它们的原型
- 通过object.create()创建的对象使用第一个参数(也可以是null)作为它们的原型
- 在ECMScript 5中,将对象作为参数传入Object.getPrototypeOf()可以查询它的原型
- 通过new表达式创建的对象,通常继承一个constructor属性,这个属性指代创建这个对象的构造函数
- 通过直接量与object.creat创建的对象包含一个名为constructor的属性,这个属性指代Object构造函,因此,constructor.prototype才是对象直接量的真正原型,但对于object.create()创建的对象则往往不是这样
- 使用isPrototypeOf()方法,检测一个对象是否是另一个对象的原型
- isPrototypeOf()函数实现的功能与instanceof运算符非常类似
- _proto_,可设置对象的原型,但是应用不广泛,不推荐使用
- 类属性
-
- 对象的类属性是一个字符串,用以表示对象的类型信息ECMAS 3与5都未提供设置个属性的方法,并只有一种间接方法可以查询它。默认的toString()方法
- 很多对象继承的toString()方法被重写了,为了能调用正确版本的toString,必须间接的调用Function.call()方法
- 通过内置构造函数创建的对象包含“类属性”,它与构造函数名称相匹配
- 通过对象直接量和Object.create创建的对象的类属性是‘object’,自定义构造函数创建的对象也是一样,因此对于自定义函数来说,没办法通过类属性类区分对象的类
- 可扩展性
-
- 对象的可扩展性用以表示是否可以给对象添加新属性,所有内置对象和自定义对象都是显式可扩展的
- ECMAScript 5 定义了用来查询和设置对象可扩展性的函数。Object.esExtensible()判断是否可扩展
- 将对象转换为不可扩展的,需要调用Object.preventExtensions(),一旦将对象转换为不可扩展的,就无法再将其转换回可扩展,并且只影响对象本身的可扩展性
- 给一个不可扩展的原型对象添加属性,这个不可扩展对象同样会继承这些新属性
- Object.seal()和Object.preventExtensions()类似,除了能够将对象设置为不可扩展的,还可以将对象的所有自有属性都设置为不可配置的。也就是说不能给这个对象添加新属性,而且它已有的属性也不能删除或配置,不过它已有的可写属性依然可以设置。
- 对于已经封闭的对象是不能解封的。可以使用Object.isSealed()来检查对象是否封闭
- Object.freeze()将更严格的锁定对象——“冻结”。除了将对象设置不可扩展和不可配置外,还可以将它已有数据属性设置为只读(如果对象的存取器属性具有setter方法,存取器属性将不受影响,仍可以通过属性赋值调用它们)。使用Object.isFrozen()来检测对象是否冻结
- preventExtensions()seal()freeze()都返回传入的对象,也就是说,可以通过函数嵌套的方式调用它们
- 序列化对象
-
- 序列化对象是指将对象的状态转换为字符串,也可将字符串还原为对象
- ECMAScript 5 提供了内置函数JSON.stringify()和JSON.parse()用来序列化和还原JS对象
- JSON语法是JS语法的子集,并不能表示JS所有对象
- 子集
-
- 大多数代码都会定义它们的子集,用以更安全的方式执行第三方代码
- 子集的设计目的是在容器或“沙箱”中更安全的运行不可信的第三方JavaScript代码
- 所有能破坏这个沙箱并影响全局执行环境的语言特性和API在这个安全子集中都是禁止的
- 为了让代码静态的通过JavaScript安全检查,必须移除一些JavaScript特性
-
- 禁止使用this,因为在(非严格模式中)this访问全局变量
-
- 严格模式
-
- 严格模式是将更好的的错误检查引入代码中的方法
- 严格模式时,无法使用隐式声明的变量、将赋值给只读属性或将属性添加到不可扩展的对象等
- 消除JS代码中一些不合理、不严谨之处,减少代码怪异行为
- 程序或函数开头使用use strict 来声明严格模式
- with、带.或[]表达式、function、windows和对Document的引用
- 支持对象、数组、字符串、无穷大数字、true、false和null,并可以序列化和反序列化
- NaN/Infinity和-Infinity序列化的结果是null
- RegExp、Error对象和undefined值不能序列化和还原
- JSON.stringify()只能序列化对象可枚举的自有属性
- 对于不能序列化的属性来说,在序列化后的输出字符串中会将这个属性省略掉
- stringify()parse都可以接受第二个可选参数,通过传入需要序列化或还原的属性列表来定制自定义的序列化或还原操作
- 对象方法
- toString方法
-
- toString方法没有参数,返回一个表示调用这个方法的对象值的字符串
- 很多类都带有自自定义的toString方法
- toLocaleString方法
-
- 除了基本的tostring方法外,对象都包含toLocaleString方法,这个方法返回一个表示这个对象的本地字符串
- Date、Number类对toLocaleString方法做了定制
- valueOf
-
- valueOf与toString非常类似,当JS需要将对象转换为某种原始值而非字符串的时候才会调用它,尤其转换为数字的时候
- 在需要使用原始值的地方使用了对象,JS就会自动调用这个方法
- 数组是值的有序集合
- 每个值叫做一个元素,每个元素在数组中有一个位置,以数字表示,称为索引
- 数组可以是任何类型,并且同一个数组中的不同元素也可能有不同的类型
- 数组的元素甚至也可能是对象或其他数组,这允许创建复杂的数据结构
- JS数组的索引是基于零的32位数值
- JS数组是动态的,根据需要他们会增长或缩减,并且在创建数组时无需声明一个固定大小或者在数组大小变化时无需重新分配空间
- JS数组可能是稀疏的,数组元素的索引不一定要连续的,它们之间可以有空缺
- 每个JS数组都有一个length属性。针对非稀疏数组该元素就是数组元素的个数
- 针对稀疏数组,length比所有元素的索引要大
- JS数组是JS对象的特殊形式
- 通常,数组的实现是经过优化的,用数字索引来访问数组元素一般来说比访问常规对象属性要快很多
- 数组继承自Array.prototype中的属性,它定义了一套丰富的数组操作方法
- 数组直接量中的值不一定是常量,可以是任意表达式
- 它可以包含对象直接量或其他数组直接量
- 如果省略数组直接量中的某个值,省略的元素将被赋予undefined值
- 数组直接量的语法允许有可选的结尾的逗号
- 调用构造函数 Array()是创建数组的另一种方法,这种形式可以预分配一个数组空间,但数组中没有存储值,甚至数组的索引属性还未定义
- 数组的特别之处在于,当使用小于2的32次方的非负整数作为属性名时数组会自动维护其length属性值
- 所有数组都是对象,可以为其创建任意名字的属性。但如果使用的属性是数组的索引,数组的特殊行为就是将根据需要更新它们的length属性值
- 可以使用负数或非整数来索引数组。在这种情况下,数值转换为字符串,字符串作属性名来用。既然名字不是非负整数,它就只能当作常规的对象属性,而非数组的索引
- 使用了是非负整数的字符串,它就当作数组索引,而非对象属性
- 数组索引仅仅是对象属性名的一种特殊类型,这意味着JS数组没有“越界”错误的概念
- 当试图查询任何对象中不存在的属性时,不会报错,只会得到undefined值。类似对象,对于对象同样存在这种情况
- 既然数组是对象,那么它们可以从原型中继承元素。在ECMASscript 5中,数组可以定义元素的getter和setter方法
- 如果一个元素确实继承了元素或使用了元素的getter和setter方法,访问这种数组的元素的时间会与常规对象属性的查找时间相近
- 访问数组
-
- 数组是对象的特殊形式。使用方括号访问数组元素就像用方括号访问对象的属性一样
- JS将指定的数字索引转换成字符串——索引值1变成“1”——然后将其作为属性名来使用
- 对于常规JS对象也可以使用方括号来操作
- 创建数组
-
- 直接量创建
- new Array()构造函数创建
- × 数组元素的添加和删除
-
- push()方法在数组末尾增加一个或多个元素
- unshift()方法在数组的首部插入一个元素,并且将其他元素依次移到跟高的索引处
- delete 直接删除数组某个元素,并不影响数组长度
- 数组有pop方法(它和push()一起使用),后者一次使减少长度1并返回被删除元素的值
- shift方法(它和unshift一起使用),从数组头部删除一个元素。和delete不同的是shift方法将所有元素下移到比当前索引低1的地方
- 稀疏数组
-
- 稀疏数组就是包含从0开始的不连续索引的数组
- 通常数组中的length属性值代表数组中元素的个数。如果数组是稀疏的,length属性值大于元素的个数
- 可以用delete操作符来生产稀疏数组
- 足够稀疏的数组通常在实现上比稠密的数组更慢、内存利用率更高,在这一的数组中查找元素的时间与常规对象属性的查找时间一样长
- 通过数组删除 Array.shift() 或 Array.pop() splice() 不会产生稀疏数组
- 数组长度
-
- 每个数组有一个length属性,就是这个属性使其区别于常规的JS对象
- 在数组中肯定找不到一个元素的索引值大于或等于它的长度。为了维持此规则不变化,数组有两个特殊的行为
- 1:当为数组元素赋值,他的索引i大于或等于现有数组长度时,length属性的值将设置i+1
- 2:设置length属性为一个小于当前长度的非负整数n时,当前数组中那些索引值大于或等于n的元素将从中删除
- 如果让一个数组元素不能配置,就不能删除它。如果不能删除它,length属性不嫩设置为小于不可配置元素的索引值
- 数组的遍历
-
- for
-
- 这是遍历数组最常用的方法
- 在嵌套循环或其他性能非常重要的上下文中,可以看到这种基本的数组遍历需要优化,数组的长度应该只查询一次而非每次循环都要查询
- for/in
-
- 自动处理稀疏数组
- 循环每次将一个可枚举的属性名(包括数组索引)赋值给循环变量。不存在的所有将不会被遍历
- ECMAScript规范允许for/in循环以不同的孙旭遍历对象的属性。通常数组元素的遍历实现是升序的,但不能保证一定是这样。特别地,如果数组同时拥有对象属性和数组元素,返回的属性名很可能是按照创建的顺序而非数值的大小顺序。如果算法依赖于遍历顺序,那么不推荐这种遍历方法
- forEach
-
- ECMAScript 5定义了一些遍历数组元素的新方法,按照索引的顺序按个传递给定义的一个函数。这些方法中最常用的就是forEach
- 多维数组
-
- JS不支持真正的多维数组,但可以用数组的数组来近似。
- × 数组方法
- join
-
- Array.join()方法将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串
- 可以指定一个可选的字符串在生产的字符串中来分割数组的各个元素。如果不指定分割符,默认使用逗号
- join是string.split方法的逆向操作,后者是将字符串分割成若干块来创建一个数组
- reverse
-
- 将数组中的元素颠倒顺序,返回逆序的数组
- 它不通过重新排列的元素创建新的数组,而是在原先的数组中重新排序它们
- sort
-
- Array.sort()方法将数组中的元素排序并返回排序后的数组。当不带参数调用sort()时,数组元素以字母表顺序排序
- 如果数组包含undefined元素,它们会被排到数组的尾部
- concat
-
- Array.concat()创建并返回一个新数组
- slice
-
- Array.slice()返回指定数组的一个片段或子数组
- splice
-
- Array.splice()方法是在数组中插入或删除元素的通用方法
- push和pop
-
- 允许将数组当作栈来使用
- unshift和shift
-
- 行为非常类似于push和pop,不一样的是前者在数组的头部而非尾部进行元素的插入和删除操作
- toString和toLocalString
-
- 数组和其他JS对象一样拥有toString方法
- forEach
-
- 从头至尾遍历数组,为每个元素调用指定的函数
- map
- filter
- every和some
- reduce和reduceRight
- indexOf和astIndexOf
- 数组类型
- 类数组对象
- 作为数组的字符串
- 函数是这样一段JS代码,它只定义一次,但可能被执行或调用任意次
- JS函数是参数化的,函数的定义包括一个称为形参的标识符列表,这些参数在函数体内像局部变量一样工作
- 函数调用会为形参提供实参的值
- 函数使用它们实参的值来计算返回值,成为该函数调用表达式的值
- 除了实参之外,每次调用还会拥有另一个值——本次调用的上下文——这就是this关键字的值
- 函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文,也就是该函数的this的值
- 用于初始化一个新创建的对象的函数称为构造函数
- 在JS里,函数即对象,程序可以随意操控它们,比如可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所有可以给它们设置属性,甚至调用它们的方法
- JS的函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义是所处的作用域中的任何变量。这意味着JS函数构成了一个闭包。它给JS带来了非常强劲的编程能力
- 形参和实参的区别,形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数
- 函数定义
-
- 函数使用function关键字来定义,它可以用在函数定义表达式或者函数声明语句里。在两种形式中,函数定义都从function关键字开始,其后跟随这些组成部分
- 嵌套函数
-
- 有趣之处在于它们的变量作用域规则:它们可以访问嵌套它们(或多重嵌套)的函数的参数和变量
- 函数调用
-
- 作为函数
- 作为方法
- 作为构造函数
- 通过它们的call()和apply()方法间接调用
- 方法调用
-
- 一个方法无非就是保存在一个对象的属性里的函数。这里的函数表达式本身就是一个属性访问表达式,这意味着该函数被当作一个方法,而不是普通函数
- 方法调用与函数调用有一个重要的区别,即,调用上下文。属性访问表达式由两部分组成,一个对象和属性名称。
- 方法和this关键字是面向对象编程范例的核心
- 任何函数只要作为方法调用实际上都会传入一个隐式的实参——这个实参是一个对象,方法调用的母体就是这个对象,方法调用的语法已经清晰表明了函数将基于一个对象进行操作
- this是一个关键字,不是变量,也不是属性名。JS的语法不允许给this赋值
- 和变量不同,this关键字没有作用域的限制,嵌套函数不会从调用它的函数中继承this
- 如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)
- 方法链
-
- 当方法返回值是一个对象,这个对象还可以再调用它的方法
- 构造函数调用
-
- 如果函数或者方法调用之前带有关键字new,它就构造函数调用。
- 构造函数调用和普通函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同
- 如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函数调用和方法调用是一致的。如果构造函数没有形参调用都可以省略圆括号
- 构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用作其调用的上下文,因此构造函数可以使用this关键字来引用这个新创建的对象
- 构造函数通常不使用关键字return,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显示返回
- 如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果
- 间接调用
-
- JS中的函数也是对象,和其他JS对象没什么两样,函数对象也可以包含方法。其中两个方法call和apply()可以用来间接调用函数。两个方法都允许显示指定调用所需的this值,也就是说任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参
- 函数的实参和形参
-
- JS中的函数定义并 未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。
- JS函数调用甚至不检查传入形参的个数
- 可选形参
-
- /*optional*/来强调形参是可选的
- 可变长的实参列表:实参对象
-
- 当调用函数的时候传入的实参个数超过函数定义是的形参个数时,没有办法直接获得未命名值的引用
- 在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象
- arguments并不是真正的数组,他是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性,但并不是真正的数组。可以理解为碰巧具有数字为索引的属性
- callee和caller属性
- 非标准的,但是大多浏览器都有实现,通过caller属性可以访问调用栈
- 将对象属性用作实参
-
- 当一个函数包含超过三个形参时,对程序员来说,要记住嗲用函数中实参的正确顺序非常头疼。通过传入对象可以简化这一步
- 作为函数的值
-
- 函数可以定义,也可调用,这时函数最重要的特性
- 函数定义和调用是JS的词法特性
- JS中函数不仅是一种语法,也是值,可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另外一个函数等
- 自定义函数属性
-
- JS中的函数并不是原始值,而是一种特殊的对象,也就是说函数可以拥有属性
- 作为命名空间的函数
-
- 函数作用域的概念:在函数中声明的变量在函数体内都是可见的(包括在嵌套函数中),在函数的外部是不可见的
- JS中无法声明只在一个代码块内部可见的变量,基于这个原因,我们常常是用函数用作临时命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间
- × 闭包
-
- JS采用词法作用域,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的
- JS函数对象内部不仅包含函数的代码逻辑,还必须引用当前的作用域链
- 函数对象通过作用域链互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”
- 从技术角度将,所有JS函数都是闭包:它们都是对象,它们都关联到作用域链
- 定义大多数函数时的作用域链在调用函数时依然有效,但这并不影响闭包
- 待更新........
- 函数属性、方法和构造函数
- length属性
-
- arguments.length表示传入函数的实参的个数。而函数本身的length属性是只读的,它代表函数定义时形参的数量
- prototype属性
-
- 每一个函数都包含一个prototype属性,这个属性指向一个对象的引用,这个对象称作“原型对象”
- call方法和apply方法
- bind方法
- toString方法
- function构造函数
- 可调用的对象
- 函数式编程
-
- JS并非函数式编程语言
- JS中可以向操作对象一样操控函数,也就是说可以在JS中应用函数式编程技术
- 使用函数处理数组
-
- 高阶函数就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数
- 高阶函数
- 不完全函数
- 记忆
- 在JS中可以定义对象的类,让每个对象都共享某些属性
- 在JS中类的实现是基于其原型继承机制的
- 两个实例都从同一个原型对象继承了属性,我们说他们是同一个类的实例
- 如果两个对象继承自同一个原型,往往意味着(担不是绝对)它们是由同一个构造函数创建并初始化
- JS中类的一个重要特性是“动态可继承”
- 类和原型
-
- 在JS中,类的所有实例对象都从同一个原型对象上继承属性,因此,原型对象是类的核心
- 类和构造函数
-
- 构造函数是用来初始化新创建的对象的
- 调用构造函数的一个重要特征是,构造函数的prototype属性被用做新对象的原型。这意味着同一个构造函数创建的所有对象都继承自一个相同的对象,因此他们都是同一个类的成员
- 构造函数和类的标识
-
- 原型对象是类的唯一标识,当且仅当两个对象继承自同一个原型对象时,它们才是属于同一个类的实例。而初始化的构造函数则不能作为类的标识,两个构造函数的prototype属性可能指向同一个原型对象。那么这两个构造函数有创建的实例属于同一个类
- 构造函数是类的“外在表现”。构造函数名字通常用做类名
- instanceof的语法强化了“构造函数是类的公有标识”概念
- constructor属性
-
- 任何JS函数都可以用作构造函数,并且调用构造函数是需要用到一个prototye属性的
- 每个JS函数(ECMAScript5中的Function.bind()方法返回的函数除外)都自动拥有一个prototype属性。这个属性的值是一个对象,这个对象包含唯一一个不可枚举属性constructor。constructor属性值是一个函数对象
- JS中Java式的类继承
-
- 构造函数对象
-
- 构造函数(对象)为JS类定义名字。任何添加到这个构造函数对象中的属性都是类字段和类方法(如果属性值是函数的话就是类方法)
- 原型对象
-
- 原型对象的属性被类的所有实例所继承,如果原型对象的属性值是函数的话,这个函数就作为类的实例的方法来调用
- 实例对象
-
- 类的每个实例都是一个独立的对象,直接给这个实例定义属性是不会为所有实例对象所共享的。定义在实例上的非函数属性,实际上是实例的字段
- JS中定义类的步骤
-
- 第一步,定义一个构造函数,并设置初始化新对象的实例属性
- 第二步,给构造函数的prototype对象定义实例的方法
- 第三步,给构造函数定义类字段和类属性
- 类的扩充
-
- JS中基于原型的继承机制是动态的,对象从其原型继承属性,如果创建对象之后原型的属性发生变化,也会影响到继承这个原型的所有实例对象
- 类和类型
-
- JS定义了少量的数据类型:null、undefined、布尔值、数字、字符串、函数和对象
- instanceof运算符
-
- 左操作符是待检测其类对象,右操作数是定义类的构造函数。返回布尔值,这里检测继承可以不是直接继承