本篇博客为 JavaScript 基础知识总结,长期更新。
JavaScript 简介
在 HTML 中使用 JavaScript
基本概念
变量、作用域和内存问题
基本类型和引用类型
按值访问:
按引用访问:
动态属性
只能给引用类型值动态添加属性
复制变量值
传递参数
- ECMAScript 中所有函数的参数都是按值传递的
- 命名参数:
检测类型
- 基本类型: typeof
- 引用类型: instanceof
执行环境及作用域
- 执行环境:
- 变量对象:
- 作用域链:
- 活动对象:
延长作用域链
两种情况下会延长: with 语句以及 try-catch 语句的 catch 块
没有块级作用域
- 声明变量:
- 查询标识符:
垃圾收集
原理:找到不再继续使用的变量,然后释放其占用的内存。
标记清除
JavaScript 中最常用的垃圾收集方式
引用计数
不太常见的垃圾收集策略。含义是跟踪记录每个值被引用的次数。
- 循环引用:
- 将变量设置为 null 意味着切断变量与它之前引用的值之间的连接
性能问题
讨论的是以怎样的标准来确定是否回收
管理内存
- 解除引用:优化内存的最佳方式,就是为执行中的代码只保存必要的数据。一旦不再有用,最好将其值设置为 null 来释放引用。(解除引用并不意味着自动回收所占内存,而是让值脱离执行环境,以便垃圾收集器下次将其回收)
小结
引用类型
在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。常被称为类,但并不妥当。因为尽管 ECMAScript 从技术上讲是一门面向对象的语言,但是不具备面向对象语言所支持的类和接口等基本结构。
- 对象定义:
- 引用类型有时候也被称为对象定义
- 实例:
- 构造函数:
Object 类型
- 创建:构造函数或者对象字面量
- 表达上下文:
- 语句上下文:
- 访问对象属性方式:点或者方括号,方括号会先计算后访问
Array 类型
- 创建:构造函数(可省略 new )或者数组字面量表示法
- 用字面量表示法创建时,不会调用相关函数(对象及数组都是这样)
- 数组的 length 可写
检测数组
- 对于只有一个全局作用域: instanceof
- 对于网页中包含多个框架: Array.isArray()
转换方法
所有对象都有 toLocalString(), toString(), valueOf() 方法,对于数组:
- toString():返回由数组每个值的字符串形式拼接而成并且以逗号隔开的字符串(实际上是调用数组每个值的 toString() 方法)
- valueOf(): 返回数组
- alert() 的参数应该是字符串形式,如果不是则会调用 toString() 方法
- toLocalString():除了是调用每个值的 toLocalString() 方法,其与和 toString() 没有区别
栈方法
- 栈:可以限制插入和删除项的数据结构,是一种 LIFO (last-in-first-out) 的数据结构
- push():参数任意,添加到数组末尾,返回的是 数字的长度
- pop():移除数组最后一项,返回的是 被移除的项
队列方法
- 队列:FIFO(First-in-First-Out)
- shift(): 移除第一项,并返回该项,数组长度减一
- unshift(): 在数组前端添加项,并返回数组长度
重排序方法
- reverse(): 反转数组的顺序
- sort(): 默认情况下按照升序排列,其原理是调用每个项的 toString() 方法,然后比较字符串,不适用大多情况,因此常传入比较参数。
操作方法
- concat(): 合并数组
- slice(): 取部分数组
- splice(): 接收多个参数,第一个为要删除的起始位置,第二个表示要删除的项数,第三个表示要插入的项。返回被删除的项,并且会改变原数组。
位置方法
- indexOf(): 两参数,分别为要查找项及开始位置。返回在第一个匹配项在数组中的位置,不存在则返回 -1。使用全等比较。
- lastIndexOf(): 同上,从数组末尾开始
迭代方法
每个方法接收两个参数:处理函数及作用域(可选)。处理函数接收三个参数:数组项、项的位置及数组对象。都不会改变原数组。
- every(): 在函数作用后,所有项返回 true 则返回 true
- filter(): 返回函数结果为 true 的项组成的数组
- forEach(): 无返回值
- map(): 返回函数作用后的新数组
- some(): 有一项返回 true 则返回 true
归并方法
归并数组所有项,最终返回一个值。每个方法接收两个参数:作用在每一项的函数及归并基础值(可选)。每个函数接收四个参数:前一个值、当前值、项的索引以及数组对象
- reduce(): 从左往右
- reduceRight(): 从右往左
Date 类型
- 日起对象: new Date(),自动获得当前日期和时间
- Date.parse(): 根据传入字符串返回对应毫秒数
- Date.UTC(): 返回毫秒数,但传入参数不同
- Date.now(): 调用该方法时的日期和时间毫秒数
继承的方法
日期格式化方法
日期/时间组件方法
都是日期对象中的属性,书本 P102
RegExp 类型
- 创建: 字面量形式
var expression = / pattern(模式) / flags(标志)
,或者用 new RegExp() 构造函数 - 模式:正则表达式,如包含元字符则需转义
- 标志:匹配行为。有 g,i,m
RegExp 实例属性
主要实例属性有:global, ignoreCase, lastIndex, multiline, source。能获取模式的各种信息,但用途不大
RegExp 实例方法
- exec():
- test(): 接收一个字符串参数,匹配则返回 true,不匹配返回 false
RegExp 构造函数属性
主要属性有 input, lastMatch, lastParen, leftContext, multiline, rightContext
模式的局限性
虽比较完备,但还是缺少一些特性,比如:
- 向后查找
- 交集和并集
- 原子组
- Unicode 支持
- 条件匹配
- 等等
Function 类型
函数是一个对象,所以函数名实际上是一个指向函数对象的指针,不会和函数绑定
没有重载(深入理解)
将函数名想象为指针,有助于理解为什么 ECMAScript 中没有重载的概念
函数声明和函数表达式
JavaScript 引擎是怎么确定代码中是否有函数声明的?先得扫描全部代码还是有特定的标记?
作为值的函数
函数可以作为值来使用
函数内部的属性
函数内部有两个特殊的对象:arguments, this
- arguments: 类数组对象。有一个特殊属性: callee,用于保存一个指向拥有此 arguments 对象的函数的指针(在阶乘函数中非常有用)
- this:引用的是函数执行的环境对象
- caller:保存了调用当前函数的函数的引用
函数属性和方法
每个函数都有两个属性:length 和 prototype
- length:函数希望接收的参数个数
- prototype:对于 ECMAScript 的引用类型而言,prototype 是保存所有实例方法的真正所在。不可枚举
每个函数包含两个非继承而来的方法:apply(),call()。都用于设置函数中的 this 的值。最强大的地方在于扩中函数作用域。
还有一个方法: bind(),用于绑定函数实例的 this 的值
函数继承而来的 toString(), toLocalString(), valueOf() 都会返回函数代码
基本包装类型
三个特殊引用类型:Boolean, Number, String。每当读取一个基本类型时,后台会自动创建一个对应的基本包装类型对象。
引用类型及基本包装类型的主要区别在于对象的生存期。
Boolean 类型
用处不大,容易造成误解
Number 类型
- toFixed(): 按照指定小数位返回数值字符串表示
- toExponential(): 以指数形式返回数字的字符串表示
- toPrecision(): 以合适的形式返回数字的字符串表示
String 类型
- length 属性:表示字符串中包含多少字符
- charAt(): 获得指定位置的字符
- charCodeAt(): 获得指定位置的字符编码
- 可以用方括号的方式访问字符
- concat()
- 字符串操作方法:slice(), substr(), substring()。对原始字符串无影响。这三者在传入参数为负数时有很大的差别。
- 字符串位置方法:indexOf(), lastIndexOf()。第一个参数为需查找字符,第二个为查找开始位置。可通过第二个参数找到字符串中所有字符
- trim():删除字符串前置及后缀的所有空格
- 大小写转换:toLowerCase(), toLocaleLowerCase(), toUpperCase(), toLocaleUpperCase()
- 模式匹配方法:match(),参数为正则表达式,返回一个数组;search(),参数也为正则表达式,返回第一个匹配项的索引,没有则返回 -1;replace(),替换字符。第一个参数为正则表达式或者字符串,第二个参数为字符串或者函数,函数有三个参数:模式匹配项、匹配项位置及原始字符串。split() 根据指定的分隔符将字符串分割并返回一个结果的数组。
- localCompare(): 比较两个字符串,并返回一个值(0,-1 或者 1)
- fromCharCode(): 将接收的字符编码转换成字符串
单体内置对象
由 ECMAScript 实现提供的,不依赖宿主环境的对象,在程序执行之前已经存在
Global 对象
- 编码方法:encodeURI(), encodeURIComponent(), decodeURI(), decodeURIComponent()
- eval(): 好比一个完整的解析器,参数为要执行的代码。代码中的变量或者函数不会被提升,因为是在 eval() 执行时创建的
- 属性:undefined, NaN, Infinity 等等
- window 对象
Math 对象
- 属性:Math.E, Math.LN10 等等
- 方法:Math.min(), Math.max()
- 舍入方法:Math.ceil(), Math.floor(), Math.round()
- 随机:Math.rondom()
- 其他方法:Math.abs(), Math.log() 等等
小结
面向对象程序设计
理解对象
属性类型
ECMAScript 中有两种属性:数据属性和访问器属性
- 数据属性:[[Configurable]], [[Enumerable]], [[Writtable]], [[Value]]。如要修改,需要使用 Object.defineProperty() 方法,接收三个参数:属性所在对象、属性名以及一个描述符对象
- 访问器属性:访问器属性不包含数据值,包含 getter 函数以及 setter 函数。有四个特征:[[Configurable]], [[Enumerable]], [[Get]], [[Set]]
定义多个属性
利用 Object.defineProperties() 方法一次定义多个属性
读取属性的特征
利用 Object.getOwnPropertyDescriptor() 方法,接收两个参数,分别为属性所在对象及要读取描述符的属性名
创建对象
工厂模式
抽象创建具体对象的过程。解决了创建多个相似对象的问题,却没解决对象识别的问题(即怎么知道一个对象的类型)
构造函数模式
- 与工厂模式三点不同:不显式的创建对象,直接将属性和方法赋给 this 对象,没有 return 语句
- constructor 属性:指向构造函数
- 与工厂模式相比:优点是——可以将自定义的构造函数的实例标识为特定类型;缺点——每个方法都要在每个实例上重新创建一遍
- 可以将同种方法提取放在全局解决上述缺点,但是又形成另一个缺点:如果对象需要定义的方法太多,则全局函数过多,那么就毫无封装性可言了
原型模式
prototype 属性是一个指针,指向的对象包含了由特定类型的所有实例共享的属性和方法
- 理解原型对象:prototype 属性。 [[Prototype]]是实例中指向构造函数原型对象的指针,虽无法访问,但可以通过 isPrototypeOf() 来确定这种关系。ES5 中可以通过 Object.getPrototypeOf() 来返回这个指针所指向的对象。不能通过对象实例重写原型对象中的属性,重新创建会屏蔽查找原型对象中的属性。hasOwnProperty() 用于判断属性存在实例中还是原型中。
- 原型与 in 操作符:单独使用时,只要能访问到的属性(不管是实例中还是原型对象中),都返回 true,因此可结合 hasOwnProperty() 判断属性。在 for-in 循环中使用时略有不同。获得所有可枚举的实例属性:Object.keys(),返回属性字符串数组。如果想获得所有实例属性,用 Object.getOwnPropertyNames() 方法。
- 更简单的原型语法:用对象字面量重写原型对象,但结果就是 constructor 属性不在指向当前构造函数,而是 Object 构造函数
- 原型的动态性
- 原生对象的原型
- 原型对象的问题:最大的问题是由共享的本性导致的。
组合使用构造函数模式和原型模式
构造函数模式定义实例属性,原型模式定义方法和共享属性
动态原型模式
将原型放在构造函数里面,通过 if 判断达到只执行一次的效果
寄生构造函数模式
和工厂模式基本一样,区别在于使用 new 操作符。。。
稳妥构造函数模式
- 稳妥对象:没有公共属性,其方法也不引用 this 的对象
- 与寄生模式不同之处:不使用 new 操作符,不引用 this
继承
OO 语言支持两种继承方式:接口继承及实现继承。前者继承方法签名,后者继承方法本身。由于在 ECMAScript 中函数无签名,所以只能实现继承,主要通过原型链来实现。
原型链
基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法。让一个原型对象等于另一个类型的实例
- 别忘记默认简单原型:所有的对象都继承自 Object 对象
- 确定原型和实例的关系:instanceof —— 只要构造函数在实例的原型链上出现过即返回 true;isPrototypeOf() 同样,只要原型链上出现过的原型都返回 true
- 谨慎的定义方法:先定义再添加,且添加的时候不能为对象字面量
- 原型链的问题:其一来自于包含引用类型值的原型;其二不能向超类型的构造函数传参
借用构造函数
借用构造函数的基本思想是在子类型构造函数的内部调用超类型构造函数
- 传递参数:可以向超类型构造函数传参
- 借用构造函数的问题:函数复用性差
组合继承
有时候也叫作伪经典继承,结合原型链继承和借用构造函数继承。使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。最大的问题是无论什么情况下都会两次调用超类型的构造函数:一次是在创建子类型原型的时候,一次是在子类型构造函数的内部
原型式继承
借助原型可以基于已有对象创建新对象。 ES5 中的 Object.create() 方法用于规范原型式继承
寄生式继承
思路与寄生构造函数和工厂模式类似
寄生组合式继承
这种方式可以解决在组合继承模式下的问题。寄生组合式继承通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。普遍认为这种方式是引用类型最理想的继承方式。
小结
函数表达式
定义函数的两种方式:函数声明及函数表达式
递归
递归函数是一个函数调用自身的情况下构成的。递归的时候,用 arguments.callee 比直接用函数名保险
闭包
闭包是指有权访问另外一个函数作用域的函数。
[[Scope]] 属性保存全局变量对象的作用域链
作用域链本质上是一个指向变量对象的指针列表,只引用但不包含实际的变量对象
闭包会携带包含函数的活动对象,所以比一般函数更占内存,所以使用的时候需要注意
闭包与变量
闭包只能取得包含函数中任何变量的最后一个值
关于 this 对象
内存泄漏
闭包中引用了包含函数的活动对象
模仿块级作用域
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。函数声明后面不能加圆括号,函数表达式可以。自执行匿名函数可以减少闭包占用内存的问题,因为执行完即销毁,没有指向匿名函数的引用了
私有变量
任何函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
特权方法:指的是有权访问私有变量和私有函数的公用方法
缺点:针对每个实例都会创建同样一组新方法
静态私有变量
在私有作用域中封装构造函数并将公有方法添加到构造函数的原型中。这样每个通过这个构造函数创建的实例都会共享同样的方法。好处是有利于代码复用,坏处是每个实例都没有了自己的私有变量
模块模式
模块模式:为单例创建私有变量和特权方法。单例指的是只有一个实例的对象。JavaScript 中一般利用对象字面量的方式创建单例
增强的模块模式
(不知所云)
小结
BOM
客户端检测
DOM
DOM 扩展
DOM2 和 DOM3
事件
事件流
事件流:描述从页面接收事件的顺序。一种是冒泡流(IE),一种是事件捕获流(网景)。
事件冒泡
从具体发生的元素到不具体的上层节点。
现代浏览器都支持事件冒泡
事件捕获
接收事件的顺序和事件冒泡相反
老版本浏览器不支持,建议放心使用事件冒泡,有特殊需要时再使用事件捕获
DOM 事件流
“DOM2 级事件” 中规定 的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段
事件处理程序
事件是用户或者浏览器自身执行的某种动作,而响应这个动作的函数就叫做事件处理程序
HTML 事件处理程序
- event 变量
- 利用 with 扩展作用域
- 缺点:其一是存在时差问题,其二是这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果,其三是 HTML 代码和 JavaScript 代码紧密耦合
DOM0 级事件处理程序
DOM2 级事件处理程序
定义了两个方法:addEventListener() 以及 removeEventListener(),都接收三个参数:要处理的事件名,事件处理函数以及布尔值
通过 addEventListener() 添加的事件处理程序只能通过 removeEventListener() 来移除
IE 事件处理程序
定义了两个方法:attachEvent() 以及 detachEvent(),都接收两个参数:事件处理程序名及处理函数,事件会添加到冒泡阶段
和 DOM0 级处理程序的差异:DOM0 级中事件处理程序在元素的作用域作用域中运行,而 IE 则在全局作用域,因此 this 值不同
有多个处理程序时,后添加的先执行
跨浏览器的事件处理程序
事件对象
触发 DOM 上的事件时会产生一个事件对象 event,这个对像包含所有与事件相关的信息
DOM 中的事件对象
- 在事件处理程序内部,对象 this 始终等于 currentTarget 的值,而 target 则只包含事件的实际目标
- enent 的 type 属性
- 阻止特定事件的默认行为,可使用 preventDefault() 方法。注意,只有 cancelable 属性设置为 true 的事件才可以使用此方法来取消其默认行为
- stopPropagation() 方法用于立即停止事件处理程序在 DOM 层次中的传播
- eventPhase 属性
- event 对象只存在于事件处理程序执行期间,执行完后即被销毁
IE 中的事件对象
跨浏览器的事件对象
将所有的情况封装到一个函数中,根据不同的情况判断后执行(其实主要是 IE 和其他浏览器)
后面的内容之后再来复习
表单脚本
使用 Canvas 绘图
HTML5 脚本编程
错误处理及调试
JavaScript 和 XML
E4X
JSON
Ajax 与 Comet
Ajax 指的是 Asynchronous JavaScript XML,技术的核心是 XMLHttpRequest 对象(简称 XHR)
XMLHttpRequest 对象
XHR 的用法
- open() 方法
- send() 方法
- 响应后的 XHR 对象属性:responseText, responseXML, status, statusText, readyState(0, 1, 2, 3, 4)(异步请求时的属性)
- readystatechange 事件
- abort() 方法
HTTP 头部信息
- setRequestHeader():设置自定义头部信息,需要在 open() 方法之后,sent() 方法之前调用
- getResponseHeader(): 取得特定响应头部信息
- getAllResponseHeaders(): 返回多个头部信息
GET 请求
常用于向服务器查询某些信息
查询字符串的参数名和值都需要使用 encodeURIComponent() 编码后才能放在 URL 末尾
POST 请求
向服务器发送应该被保存的数据
模仿表单提交来发送数据
XMLHttpRequest 2 级
并非所有浏览器都完整的实现了 2 级规范,但所有的浏览器都实现了它规定的部分内容
FormData
FormData 为序列化表单及创建与表单格式相同的数据(用于通过 XHR 传输)提供了便利
方便之处体现在不需要明确的在 XHR 对象上设置请求头部
超时设定
IE8 是唯一支持 timeout 属性的浏览器
overrideMimeType() 方法
进度事件
6个进度事件:loadstart, progress, error, abort, load, loadend。目前大部分支持前面五个事件,目前还没有浏览器支持 loadend 事件。
load 事件
progress 事件
三个属性:lengthComputable, position, totalSize
可以根据这个属性为用户创建一个进度指示器
跨域源资源共享
CORS(Cross-Origin Resource Sharing) 跨域源资源共享
基本思想:使用自定义的 http 头部让浏览器与服务器进行沟通,从而决定请求或者响应是否成功
IE 对 CORS 的实现
在 IE8 中引入了 XDR(XDomainRequest) 类型,与 XHR 类似,但能实现安全可靠的跨域通信。
所有 XDR 请求都是异步执行的,不能用它来创建同步请求
其他浏览器对 CORS 的实现
其他浏览器都通过 XMLHttpRequest 对象实现了对 CORS 的原生支持
Preflighted Reqeusts
带凭据的请求
withCredentials 属性
跨浏览器的 CORS
条件判断浏览器,主要区别在于 XDR 和 XHR
其他跨域技术
图像 Ping
图像 Ping 是与服务器进行简单、单向的跨域通信的一种方式
常用于跟踪用户点击页面或动态广告曝光次数。缺点有二:其一是只能发送 GET 请求,其二是无法访问服务器的响应文本。
图像 Ping 只能用于浏览器与服务器间的单向通讯
JSONP
JSONP 是 JSON with padding 的简写,由回调函数及数据组成。
极为流行,因为简单易用。与图像 Ping 相比,优点在于可以直接访问响应文本,而且支持浏览器和服务器之间双通信。两点不足:从其他域加载涉及到安全问题,要确定请求失败并不容易。
Comet
Comet 是一种服务器向页面推送数据的技术。(由 Alex Russell 发明)
两种实现 Comet 方式:长轮询和流
长轮询是传统轮询(也称为短轮询)的翻版:页面发起一个服务器请求,然后服务器一直保持打开,直到有数据可发送
HTTP 流:浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向服务器发送数据
服务器发送事件
SSE(服务器发送事件):围绕只读 Comet 交互推出的 API 或者模式。支持短轮询、长轮询和 HTTP 流,而且在断开时能够自动确定何时重新连接。
- EventSource 对象
Web Sockets
目标是在一个单独的持久连接上提供双工、双向通信
好处:能够在客户端和服务器之间发送非常少的数据,适合移动应用
坏处:制定协议的时间比制定 JavaScript API 的时间还要长,而且可能存在一致性和安全性的问题
SSE 与 Web Sockets
考虑两点:其一是否有自由度建立和维护 Web Sockets 服务器;其二是否需要双向通信
安全
CSRF(Cross-Site Reqeust Forgery):跨站点请求伪造
小结
高级技巧
高级函数
安全的类型检测
利用 Object.prototype.toString.call(name) 对变量名为 name 的对象进行检测,返回的结果形式 [object NativeConstructorName] 就显示了其构造函数名。注意这种方法不能检测非原生构造函数的构造函数名。
作用域安全的构造函数
惰性载入函数
惰性载入表示函数执行的分支仅会发生一次。
实现方式:其一是在函数被调用时再处理函数,其二是在声明时就指定适当的函数
函数绑定
主要用于事件处理程序及定时器中
函数柯里化
基本方法:使用闭包返回一个函数
防篡改对象
一旦把对象定义为防篡改,就无法撤销了。
不可扩展对象
Object.preventExtensions() 以及 Object.isExtensible()
不可扩展,但已有成员可以修改和删除
密封的对象
密封对象不可扩展而且已有成员不能被删除
通过 Object.seal() 来密封,也可以通过 Object.isSealed() 来检测是否被密封
冻结的对象
最严格的防篡改级别是冻结对象,既不可扩展,又是密封的,而且数据属性的 [[Writtable]] 特性会被设置为 false。如果定义 [[Set]] 函数,访问器属性仍然是可写的。
Object.freeze()
高级定时器
关于定时器要记住的最重要的事情是:指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。
重复的定时器
主要的问题是某些间隔可能会被跳过,而且多个定时器的代码执行之间的间隔可能会比预期小。解决办法是链式调用延时定时器。
离线应用与客户端存储
最佳实践
新兴的 API
ECMAScript 6 入门
ECMAScript 6 简介
###