在北京待了五天,没卫浴没网络没水没得正常吃饭,今天准备出去找房子打游击战。开始编写高质量代码。

语言基础

  • 防止浮点数溢出,可转换为整数计算再转小数
  • 调用 Object 对象定义的默认 toString 方法判断对象类型,Object.prototype.toString.apply( value ) ,仅适用于基本数据类型和内置对象。检测非内置对象使用 instanceofconstructor
  • 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 子句影响。进入一个函数,将从当前上下文进入一个新的执行上下文。创建执行上下文的过程如下:

  1. 创建激活对象
    激活对象是在进入新的执行上下文时被创建出来的,与新的执行上下文关联。在初始化构造函数时,该对象包含一个名为 arguments 的属性。激活对象在变量初始化过程中会被用到。Javascript 代码不能直接访问该对象,但可以访问改对象的成员(如 arguments

  2. 创建作用域链
    每一个 function 都有一个内部属性 [[scope]],它的值是一个包含多个对象的链。创建作用域链主要是将上一步的激活对象添加到 function 的 [[scope]] 属性对象的链的前面

  3. 变量初始化
    对 function 所需的变量进行初始化。初始化时使用的对象是第一步创建的激活对象,此时称为变量对象。初始化的变量包括 function 调用时传入的实际参数、内部 function 和局部变量。

补充:
此过程属于 javascript 对函数的预编译过程,详细可参见编译运行原理笔记

执行函数

  • 形参被创建为变量对象的命名属性,如果调用函数时传递的参数与形参一致,则将参数的值赋值给这些命名属性,否则为 undefined
  • 局部变量只是在变量对象中创建了同名属性,值为 undefined,执行过程中才会被赋值;
  • 内部定义函数(非嵌套函数)会以其声明时所用名称创建同名属性,对应的函数被创建为函数对象,并将其赋值给该属性(先预声明变量,再预定义函数);
  • 由于 arguments 属性与函数局部变量对应的命名属性都属于同一个调用对象,因此可以将 arguments 作为函数的局部变量看待
  • 最后,创建 this 对象并对其进行赋值。如果赋值是一个对象,则指向该对象的引用;否则指向全局对象
  • 执行函数结构体内语句
  • 返回函数返回值

    全局 Javascript 代码是在全局执行上下文中运行的,该上下文的作用域链只包含一个全局对象

面向对象编程

  • 对象(Object)没有原型,只有构造函数拥有原型,而构造类的实例对象都能通过 protoype 属性访问原型对象。构造函数的 prototype 属性存储着一个引用对象的指针,该指针指向一个原型对象,内部存储着构造函数的原始属性和方法,借助 prototype 属性可以访问原型对象内部成员。构造函数的实例对象可以访问构造函数的原型成员
  • 检索原型链的过程称为原型委托
  • hasOwnProperty 不会检查原型链
  • Javascript 中类似指针特性的标志符
    • this ,动态指针,利用 callapply 可被转换为静态指针
    • 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节点如何显示)。
  • 改变布局信息(offsetTopscrollTopclientTop等)会立即刷新渲染列表,即使部分浏览器通过队列化修改和批量显示来优化重拍版过程
  • 工人线程 Worker

客户端编程

  • 浏览器在默认情况下使用冒泡型事件流,可显式设置并使用捕获型事件流。除了元素能够响应事件外,DOM 标准还规定文本节点也可以响应时间,但 IE 并不支持响应事件(不懂)
  • 解析 scrollHeightscrollWidth 属性
浏览器 计算公式
IE padding + 高度
FireFox padding-top + 高度
Opera 高度 + 底部滚动条高度
Safari padding-top + 高度
  • 解析 clientHeightclientWidth 属性
浏览器 计算公式
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 = 3data: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 按代码块预编译和执行
  • 避免二次评估,即避免使用 evalnew Function() 编译执行代码,包括 setTimeoutsetInterval
  • 使用直接量赋值更快
  • 使用位操作符执行逻辑运算

    10 & 3 === 0
    11 & 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 代码 /* ]]>*/
  • 旧浏览器使用局部变量减少对象成员访问

  • 不同浏览器对长时间运行脚本的检测方法不同,基于时间或指令数量
小记:看了前面一部分,感觉很多都在语言精粹里面看过了,甚至文字的表述都让人很熟悉;正则表达式看到了一些新的东西,还没系统学习过所以就快速过了,回头必须补上;函数一章的内容多次刷新了自己对于作用域和上下文的理解,对于如何创建一个函数有刨根问底的解释,感觉要看多几次才能消化;然后渐渐发现书本中很多描述重复出现;数据交互与储存一章对 JOSNP 的描述以及后部分提到的 XSS 攻击方法很清晰;js 引擎实现与规范一章很粗糙的描述了不同浏览器引擎的兼容问题,忽略了浏览器更新修复支持的情况,导致这部分内容很糟糕。尽管存在一些问题,但这本书还是很值得购入认真看一遍,收获很多