编写高质量代码
在北京待了五天,没卫浴没网络没水没得正常吃饭,今天准备出去找房子打游击战。开始编写高质量代码。
语言基础
- 防止浮点数溢出,可转换为整数计算再转小数
- 调用
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 代码 /* ]]>*/旧浏览器使用局部变量减少对象成员访问
- 不同浏览器对长时间运行脚本的检测方法不同,基于时间或指令数量