编写高质量代码
在北京待了五天,没卫浴没网络没水没得正常吃饭,今天准备出去找房子打游击战。开始编写高质量代码。
语言基础
- 防止浮点数溢出,可转换为整数计算再转小数
- 调用
Object
对象定义的默认toString
方法判断对象类型,Object.prototype.toString.apply( value )
,仅适用于基本数据类型和内置对象。检测非内置对象使用instanceof
和constructor
- NaN,使用 isFinite 和 typeof 检验数字类型
- 逗号运算符使用括号强迫进行连续运算,否则会出现先赋值再计算的问题
hasOwnProperty
可被修改(那解决方法咧)(通过 iframe 获得原始对象)- 伪数组不包含数组的方法
continue
影响性能(具体咧)(jsperf 测试发现几乎无差距)- 关于
new
,避免使用和给出的原因感觉很不合理
字符串、正则表达式和数组
- 基于函数的迭代比基于循环的迭代占用时间多了八倍
replace
的使用($&, $`, $’, $123
)- 数组长度不限,长度大于 2^32 或小于0时
length
不变,但可成功赋值和索引(IE9 和 chrome 测试) arguments.callee
可以调用当前匿名函数
函数式编程
- 使用
Function
构造函数创建的函数具有顶级作用域(运行时动态执行的缘故),function语句和函数直接量定义的函数都有自己的函数作用域(局部作用域) arguments
可通过apply调用数组的方法实现补丁- Javascript 的非惰性求值特征,函数参数无论是否使用都会被计算;惰性编程的角度要求消除不必要的计算
- 惰性实例化,闭包添加
getInstance
方法延迟实例化 惰性载入函数,在函数内部改变自身,使得函数执行的分支仅会发生一次(代码有内存泄漏的可能?)
function foo() {foo = function() {alert(1);}return foo();}函数绑定,创建一个闭包,并传入上下文
function bind(fn, context) {return function() {return fn.apply(context, arguments);};}// el.addEventListener('click', bind(fn, ctx), false)函数节流,简单的函数如下
function throttle(fn, context) {clearTimeout(method.tid);fn.tid = setTimeout(function() {fn.call(context);});}
- 作用域安全的构建函数,针对没有使用
new
实例化的构建函数,使用instanceof
检查this
并做处理
执行上下文与作用域链
每个执行上下文(execution context)与一个作用域链(scope chain)关联,并且与其关联的作用域链只会被 with 语句和 catch 子句影响。进入一个函数,将从当前上下文进入一个新的执行上下文。创建执行上下文的过程如下:
创建激活对象
激活对象是在进入新的执行上下文时被创建出来的,与新的执行上下文关联。在初始化构造函数时,该对象包含一个名为arguments
的属性。激活对象在变量初始化过程中会被用到。Javascript 代码不能直接访问该对象,但可以访问改对象的成员(如arguments
)创建作用域链
每一个 function 都有一个内部属性[[scope]]
,它的值是一个包含多个对象的链。创建作用域链主要是将上一步的激活对象添加到 function 的[[scope]]
属性对象的链的前面变量初始化
对 function 所需的变量进行初始化。初始化时使用的对象是第一步创建的激活对象,此时称为变量对象。初始化的变量包括 function 调用时传入的实际参数、内部 function 和局部变量。
补充:
此过程属于 javascript 对函数的预编译过程,详细可参见编译运行原理笔记
执行函数
- 形参被创建为变量对象的命名属性,如果调用函数时传递的参数与形参一致,则将参数的值赋值给这些命名属性,否则为
undefined
; - 局部变量只是在变量对象中创建了同名属性,值为
undefined
,执行过程中才会被赋值; - 内部定义函数(非嵌套函数)会以其声明时所用名称创建同名属性,对应的函数被创建为函数对象,并将其赋值给该属性(先预声明变量,再预定义函数);
- 由于
arguments
属性与函数局部变量对应的命名属性都属于同一个调用对象,因此可以将arguments
作为函数的局部变量看待 - 最后,创建 this 对象并对其进行赋值。如果赋值是一个对象,则指向该对象的引用;否则指向全局对象
- 执行函数结构体内语句
返回函数返回值
全局 Javascript 代码是在全局执行上下文中运行的,该上下文的作用域链只包含一个全局对象
面向对象编程
- 对象(Object)没有原型,只有构造函数拥有原型,而构造类的实例对象都能通过
protoype
属性访问原型对象。构造函数的prototype
属性存储着一个引用对象的指针,该指针指向一个原型对象,内部存储着构造函数的原始属性和方法,借助prototype
属性可以访问原型对象内部成员。构造函数的实例对象可以访问构造函数的原型成员 - 检索原型链的过程称为原型委托
hasOwnProperty
不会检查原型链- Javascript 中类似指针特性的标志符
this
,动态指针,利用call
或apply
可被转换为静态指针callee
,函数的参数集合包含的一个静态指针,始终指向参数集合所属的函数prototype
,函数包含的一个半静态指针,默认情况下指向函数指向的原型对象,可被修改constructor
,对象包含的指针,指向创建该对象的构造函数
类继承,将父类的原型保存到子类中,实现方法覆盖
function A() {}function B() {var args = Array.prototype.slice(arguments, 0);A.apply(this, args);}闭包实现的单例模式可作为静态类使用(建议100关于类的静态成员的示例中将公共方法绑定到 window,理解不能)
享元类,是创建类的类。其操作的对象是类,而不是具体的数据。一般享元类返回的类型是类。当类返回引用类型或函数体,则类的成员将不可访问(此处的示例代码让人疑惑)
function F() {this.x = 1;return function() {return this.x;};}var f = new F();// 此处引用原文示例// f.x === undefined// this.x 除非通过 that 否则无法访问,原因在于 F 的调用对象丢失?掺元类(mixin),可通过原型继承实现
DOM编程
- 空格和换行符会被 DOM 作为一个节点解析
- 浏览器解析页面并创建两个内部数据结构,DOM树(表示页面结构)和渲染树(表示DOM节点如何显示)。
- 改变布局信息(
offsetTop
、scrollTop
、clientTop
等)会立即刷新渲染列表,即使部分浏览器通过队列化修改和批量显示来优化重拍版过程 - 工人线程
Worker
客户端编程
- 浏览器在默认情况下使用冒泡型事件流,可显式设置并使用捕获型事件流。除了元素能够响应事件外,DOM 标准还规定文本节点也可以响应时间,但 IE 并不支持响应事件(不懂)
- 解析
scrollHeight
和scrollWidth
属性
浏览器 | 计算公式 |
---|---|
IE | padding + 高度 |
FireFox | padding-top + 高度 |
Opera | 高度 + 底部滚动条高度 |
Safari | padding-top + 高度 |
- 解析
clientHeight
和clientWidth
属性
浏览器 | 计算公式 |
---|---|
IE | padding + 高度 |
FireFox | padding + border + 高度 |
Opera | padding + 高度 |
Safari | padding + 高度 |
<html>
在DOM中表示为document.documentElement
,获取窗口大小可通过如下方法- 获取绝对位置可通过
offsetParent
遍历实现,但是无法处理元素的边框
数据交互与储存
- JSONP(json with padding)异步通信协议,默认以
jsonp
作为参数名指定回调函数 - Multipart XHR ,可通过检验
readyState = 3
并载入已经加载的图片或者代码。缺点是无法被服务器缓存,IE8 才开始支持readyState = 3
和data:URL
- 使用 XHR 将数据发回服务器比使用 GET 快,因为向服务器发送一个 GET 请求要占用一个单独的数据包。另外,一个 POST 请求至少发送两个数据包,一个用于信息头,另一个用于 POST 体。POST 适合向服务器发送大量数据,因为它既不关心额外数据包的数量,也没有 IE 的 URL 长度限制
- 使用数组格式压缩 json,牺牲了可读性
split()
是最快的字符串操作之一- XSS( Cross Site Script )
- 反射型:请求数据在服务响应页面中呈现为未编码和未过滤,通过 URL 注入
- 持久型:包含恶意代码的请求数据被保存在 Web 应用的服务器上,每次用户访问某个页面时恶意代码会被执行
- XSS 防范
- 不要信任用户的人和输入,采用白名单技术验证输入参数
- 输出的时候对用户提供的内容进行转义处理
- Javascript 挟持。攻击者在恶意站点的页面中通过
<script>
标签调用被攻击站点的一个 JSON 动态数据接口,并通过 Javascript Function Hook 等技术取得这些 JSON 数据。具体是用户在登陆被攻击站点后,访问恶意站点。假设其身份认证基于 Session Cookie 来保存,则恶意站点发送的请求被认为是合法的。整个过程相当于一个站外类型的跨站点请求伪造(CSRF)攻击
JavaScript 引擎与兼容性
- UA 检验系统,包括 “Win”, “Mac”, “X11”
- IE 初始化数组
在 IE 中函数表达式中的标志符在闭包的上下文环境是可见的,因为表达式函数被视为闭包内的函数声明,而其他浏览器将报错
var a = function foo(t) {if (t) {foo(false); // IE 中可正常执行}}a(true);IE 和 Opera 中,
with
中使用函数声明定义一个函数时,函数作用域绑定到全局作用域,而与with
的运算值无关。使用函数表达式定义表现正常- IE 不允许通过
for in
属性枚举类型的自定义属性(不清楚) - IE 中,用于捕获异常的变量在当前上下文环境是可见的,在
catch
字句执行完毕之后变量依然存在,但在该上下文环境被注销后会消失 - IE 中,
Array.prototype.join
在缺少参数时会将undefined
作为分隔符 - IE 中,
Array.prototype.unshift
返回undefined
而不是数组长度 - IE 中,调用日期原型的
valueOf
方法返回 0,而标准规定NaN
- IE 中,
getYear
类似于getFullYear
- IE 中,
typeof window.alert === object
,同时(a = widnow.alert).call(null, 1)
将报错 - IE 中,使用最后一次出现的函数声明定义函数,无视
if else
- EMCAScript v3 规范不允许其他变量间接调用全局函数
eval()
,否则抛出 EvalError 异常,但 IE、FF、Safari 引擎允许间接调用eval()
parseInt()
方法当基数为 0 时,IE 和 FF、Safari 引擎会把它解析为八进制数字, Chrome 不会- 不同引擎对各种
toString
方法的返回值不同 - IE 和 Opera 会把字符串带符号的十六进制数字转换为 NaN,而其他引擎会把它转换为负数
- IE9 以下,
event.srcElement
相当于event.target
,但是前者返回HTML Element
,后者返回节点,包括文本节点 - IE 中使用
event.keyCode
获取键盘值,其他浏览器使用event.which
- 鼠标位置
浏览器 | 绝对位置 | 相对位置 |
---|---|---|
IE | event.x (event.clientX) event.y (event.clientY) |
event.offsetX event.offsetY |
非IE | event.PageX event.PageY |
event.layerX event.layerY |
- IE 中,
input.type
为只读属性,其他浏览器为读 / 写 - IE 中,使用
currentStyle
获取样式,其他浏览器使用getComputedStyle
- IE 通过
htmlFor
访问标签for
属性
Javascript编程规范和应用
- Javascript 按代码块预编译和执行
- 避免二次评估,即避免使用
eval
,new Function()
编译执行代码,包括setTimeout
,setInterval
- 使用直接量赋值更快
使用位操作符执行逻辑运算
10 & 3 === 011 & 1 === 1<script>
的 defer 属性只适用于 IE 和 FF 3.5以上- IE 不支持
<script>
标签的onload
事件,但实现了readyState
属性
状态 | 说明 |
---|---|
uninitialized | 默认状态 |
loading | 下载开始 |
loaded | 下载完成 |
interactive | 下载完成但不可用 |
complete | 所有数据准备完成 |
- HTMLCollection 对象并不是一个固定的值,而是一个动态的结果
- 通过
setTimeout
拆分任务 - Web Worker
- Javascript 是一种垃圾收集式语言,其对象的内存是根据对象的创建分配给该对象,并且会在没有对该对象引用时由浏览器回收
- 两个对象相互应用会造成内存泄漏,常见于闭包内的 DOM 对象
- HTML 中的
<script>
标签内的 Javascript 代码被解释为 CDATA(Character Data,XML 中的一种类型,用于包含任意的字符数据) 使 XML 的语法校验器忽视这段内容,同时利用注释使代码能够被 Javascript 引擎识别
/* <![CDATA[ */ 插入 Javascript 代码 /* ]]>*/旧浏览器使用局部变量减少对象成员访问
- 不同浏览器对长时间运行脚本的检测方法不同,基于时间或指令数量