JavaScript ES5 入门笔记

引言

最近抽空学习了下 JavaScript 语言,当然还是入门级别的那种。学习过程中也做了些笔记。总的来说,ES5 学完后,对 JavaScript 本身了解了大概,语言本身设计上也存在一些坑,所以搭配《JavaScript 精粹》这本书看看,可以进一步了解有哪些比较好的写法避免踩坑。

基础篇

  • 判断是否为未定义的变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 错误的写法
    if (v) {
    // ...
    }
    // ReferenceError: v is not defined
    // 正确的写法
    if (typeof v === "undefined") {
    // ...
    }
  • typeof null 也是 object

  • 使用 instanceof 可以判断是否为数组:v instanceof Array
  • nullundefined 非常类似,甚至 null == undefined 都成立,但是它们在 JavaScript 中还是有区别:

    • null 表示空对象,可转换为数值 0:Number(null) //0
    • undefined 表示没有定义的原始值,转换为数值为 NaN
  • JS 中所有数字都是以 64 位浮点数存储的,对于某些运算,只能用整数,JS 会将数字转换为 32 位整数再做运算

  • 数值计算比较需要注意,因为浮点数有精度丢失的问题。比如:0.3-0.2 !== 0.2-0.
  • JavaScript 对 15 位的十进制数都可以精确处理。
  • 对于码点在 U+10000 到 U+10FFFF 之间的字符,JavaScript 总是认为它们是两个字符(length 属性为 2)
  • base64 转换:btoa, atob
  • 属性删除 delete

    • 不能删除对象本身
    • 无论属性是否存在,删除总会返回 true
    • 继承的属性不能删除,虽然也会返回 true
    • 使用 defineProperty 定义的标记为不可删除的属性,delete 返回 false
  • 对象属性存在新判断:

    • in 可以判断,但是不能区分是继承的还是自身拥有的
    • hasOwnProperty 可以弥补上述缺点
  • for...in 用于遍历对象的属性:

    • 对象属性必须是可遍历的(enumerable)
    • 对于继承的属性也会被遍历到,如果不想要,可以搭配 hasOwnProperty 过滤掉
  • 一般也可以使用 Object.keys(o).map(k => { /*do stuff*/ }

  • 函数声明几种方式:
    • function func_name(params) {}
    • let say = function (word) {},匿名函数
    • let say = function f(word) { console.log(f) },注意,这里的函数名 f 只可以在该函数内部访问到,有两个好处:
      • 遍历在函数内部调用自己
      • 方便调试,排错时看到具体的函数名,而不再是匿名函数
    • 使用 Function 构造函数,注意,返回的函数的 type 的确也是 function(不推荐使用)
  • JS 引擎将函数名视为变量名,所以也存在「函数名提升」的效果,因此,在函数声明位置的上方调用也不会出错
  • 函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域
  • arguments 对象可以获取到函数传入的参数值:

    • 该对象不是数组,所以像 slice, forEach 方法就不存在
    • 如果想要转换成数组,一种方式就是:let args = Array.prototype.slice.call(arguments)
    • 在严格模式下,不能通过 arguments.callee 调用函数自身;对 arguments 的修改不会对原始传入的参数产生影响
  • 立即调用函数的两种方式:

    • (function () {console.log(10)})()
    • (function () {console.log(10)}())
  • 数组是特殊的对象,其键名就是一组数字,可以通过 Object.keys 返回所有的键名

  • 数组的 length 属性不过滤空位。所以,使用 length 属性进行数组遍历,一定要非常小心。如 delete arr[3] 之后,依然不会影响 length,只是产生了一个空位;所以可以使用 forEach 替代,可以过滤空位(但空位不等同于 undefined)。也就是说 var a = [, , ,] 表示的每个位置都是空位;var a = [undefined, undefined] 则是可遍历到的,只是值为 undefined 而已
  • 类似数组的数组(Array like object):
    • 对象拥有类似下面的定义:
      1
      2
      3
      4
      5
      6
      7
      8
      // 所有的键都是数字
      // 带有 length 属性
      var obj = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3
      };
  • 函数的 arguments,字符串,还有多数的 DOM 元素都是 array-like 的对象。可以使用 Array.prototype.slice.call(xx) 转换成数组

  • void 运算符:作用是执行一个表达式,不返回任何值(返回 undefined):

    • 推荐用法:void (x = 5)
    • 使用场景,防止网页跳转:<a href="javascript: void f()">文字</a>
  • 逗号运算符返回最后一个值 'a', 'b' // "b"

语法专题

  • Number 用于强制转换成数值类型,与 parseInt 相比,转换要严格得多,只要遇到不可转换为数值的字符,转换就失败
  • Boolean 所有对象(含空对象)的转换结果都是 true,如 Boolean(new Boolean(false)) // true
  • JS 中所有抛出的错误都是 new Error(xx) 出来的,Error 的基本属性:

    • message:错误提示信息(必须)
    • name:错误名称(非标准属性,看 JS 引擎)
    • stack:错误的堆栈(非标准属性,看 JS 引擎)
  • 派生错误对象:

    • SyntaxError
    • ReferenceError
    • RangeError:数组长度为负数;Number 对象的方法参数超出范围;函数堆栈超过最大值
    • TypeError
    • URIError
  • throw 可以抛出任意类型的值,并且在 throw 的位置就中断了执行,JS 引擎会接收到抛出的信息

  • console 一些重要的方法:

    • info
    • error
    • debug
    • warn
    • table
    • log
    • dir:类似 inspect
  • 可以在需要的地方加 debugger 作为断点,用于调试

标准库

  • Object() 函数可以将任意值转换成对象,如果传入的参数是原始类型值,则会转换成其包装对象的实例;如果入参已经是对象了,则不会转换
  • 可以利用上述特性判断变量是否为对象:value === Object(value)
  • Object.keys() 静态方法可以返回对象的所有属性名(非继承,且可枚举),该遍历方法优先
  • Object.getOwnPropertyNames() 静态方法则会返回对象的所有属性名(包括不可枚举的)
  • Object.prototype.xxx 实例方法:

    • valueOf():返回对象的值,默认为自身
    • toString():返回对象的字符串表示
    • Object.prototype.toString.call(value) 返回 [object <TypeName>],第二个元素可以得到具体的值类型
  • JS 提供了内部数据结构,描述对象的属性,控制其行为,示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    value: 123,
    writeable: false,
    enumerable: true,
    configurable: false,
    get: undefined,
    set: undefined
    }
  • Object.getOwnPropertyDescriptor(obj, 'a') 可以获取属性描述对象

  • 默认情况下,如果属性被设置为 writeable=false 后,继承者将无法重新定义同名属性;除非可以通过覆盖属性描述对象来绕过限制
  • 如果属性的 enumerablefalse,则有三种操作不能取到该属性:

    • for...in
    • Object.keys:无法获取对象继承的属性,此时可以用 getOwnPropertyNames
    • JSON.stringify
  • configurable:控制是否可以修改描述符属性,并且当 configurable=fasle 时,无法删除

  • 判断对象是否为数组:Array.isArray

面向对象

  • JavaScript 中的对象体系,是基于构造函数 constructor 和原型链 prototype 的。构造函数就是对象生成的「模板」
  • 构造函数特点:

    • 内部使用 this,指向生成的对象实例
    • 需要配合 new 关键字
  • new 干了什么:

    • 新建空对象,作为要返回的对象
    • 将空对象的 __proto__ 指向构造函数的 prototype
    • 将该空对象赋值给函数内部的 this 关键字
    • 执行构造函数中的代码
  • 函数内部可使用 new.target 判断是否使用了 new 调用构造函数

  • this 在函数体内部,指向当前的运行环境
  • 如果 this 所在的方法不在对象的第一层,这时 this 只是指向当前一层的对象,而不会继承更上面的层
  • 函数实例的 call 方法可以指定 this 指向的环境,此外可以跳过同名函数覆盖,直接使用原始的函数;appycall 类似,只是接收一个列表参数
  • bind 也可以绑定对象,变换 this 指向
  • JS 继承机制的核心:原型中所有属性和方法都可以被多个实例共享
  • instanceof 运算符:
    • 只能用于非原始类型对象
    • 对象的原型 prototype 均为 null 时失效
    • undefined instanceof Objectnull instanceof Object 均为 false

异步

  • Promise 的回调函数不是正常的异步任务,而是微任务。前者会追加到下一轮事件循环,而微任务则追加到本轮事件循环。所以微任务执行要早于常规异步任务。
  • setTimeout(f, 0) 可以让任务在下轮事件循环中执行

DOM

  • 元素的 hidden 属性只有在 CSS 没有明确设定当前元素的可见性时才会生效,也就是 CSS 的 display 用于可见性控制的优先级高于元素自身的 hidden 属性
  • 网页元素可以自定义 data-* 属性,添加数据,可以使用 .dataset 返回数据对象
  • 为防止遭受攻击,对于文本内容,建议使用 textContent 替代 innerHTML 设置文本

CSS

  • CSS 负责视觉效果;JavaScript 负责与用户的行为互动
  • 可以通过 style 属性设置 CSS 样式
  • CSSStyleDeclaration 对象用于操作元素的样式,包括:

    • 元素的 style 属性
    • CSSStyle 实例的 style 属性
    • window.getComputedStyle() 返回值(元素的全部样式需要它获取到)
  • 需要注意的是,CSSStyleDeclaration 的属性名和原生 CSS 还是有点区别,命名需要修改,如 backgroud-color -> backgroudColor;如果是 JS 的保留字,则需要加上 css 前缀

  • 属性操作:
    • setProperty
    • removeProperty

DOM 变动监控

  • Mutation Observer API:DOM 的变动(节点增减、属性变动、文本内容变动),都可以通过该 API 获得通知
  • 与事件的区别:
    • MO 事件是在 DOM 变动时异步触发,并且是等到当前所有 DOM 操作都结束才触发
    • 事件则是同步触发,DOM 变动立刻触发
  • 总结来说:

    • 等待所有脚本任务完成后,才会触发
    • 把 DOM 变动记录封装成一个数组处理,而非每条都分别处理
    • 既可以观察 DOM 所有类型变动,也可以指定观察某类变动
  • 用法:

    1
    2
    3
    4
    5
    const observer = new MutationObserver((mutations, observer) => {})

    // 指定监听的元素和类型
    const article = document.querySelector("article")
    observer.observe(article, {'childList': true, 'attritubes': true})

事件

  • DOM 的事件操作(监听、触发),都是 EventTarget 中定义的。主要接口如下:

    • addEventListener
    • removeEventListener
    • dispatchEvent
  • HTML 中可以直接在元素属性中,定义某些监听代码。如 onload,这类监听代码只在冒泡阶段触发

  • 元素的 onclick 属性,也可以指定监听函数,并且在冒泡阶段触发
  • 事件传播:
    • 第一阶段:从 window 对象传导到目标节点(上层传到底层),即为「捕获阶段」(capture phase)
    • 第二阶段:从目标节点上触发,叫做「目标阶段」(target phase)
    • 第三阶段:从目标节点传导回 window 对象(从底层传回上层),叫做「冒泡阶段」(bubbling phase)
  • e.StopPropagation 则是会阻止事件传播,但不会处理其它的监听事件
  • e.stopImmediatePropagation 会彻底取消同名的时间
  • 网页中除了元素节点默认不可以拖拉,其它(图片、链接、选中的文字)都是可以拖拉的,可通过将元素节点的 draggable 属性设置为 true 实现
  • 一旦某个元素节点被设置为可拖拽后,就无法通过鼠标选中节点内部的文字或子节点了

浏览器对象

  • 嵌入脚本时,<script> 元素的 type 属性默认为 text/javascript,新浏览器可以写成 application/javascript。当然,也可以不填写。另外,integrity 属性可以设置脚本的 SHA256 签名,对于非法脚本浏览器会拒绝加载
  • asyncdefer 同时存在,则后者不起作用
  • window.navigator 指向包含浏览器和系统信息的 Navigator 对象,可以了解用户的环境信息
  • Cookie 是服务器保存在浏览器的一小段文本信息,每个 Cookie 的大小一般不超过 4KB,每次请求服务器,都会附带该信息
  • Cookie 包括的信息:

    • 名字
    • 到期时间
    • 所属域名(默认当前域名)
    • 生效的路径(默认当前网站)
  • 浏览器的安全基础是「同源政策」(same-origin policy)

  • 同源的含义:

    • 相同的协议
    • 相同的域名
    • 相同的端口
  • 非同源的限制:

    • 无法读取非同源网页的 Cookie, LocalStorage 和 IndexedDB
    • 无法接触非同源网页的 DOM
    • 无法向非同源地址发送 AJAX 请求(即便发送,浏览器也会拒绝接受响应)
  • document.domain 设置可共享 Cookie,适用于 ifame 和 Cookie,对于 LocalStorage 和 IndexedDB 无效;此外,服务器可以在设置 Cookie 时就指定好所属一级域名:.example.com,也可以对子域名达到共享 Cookie 的目的

  • 对于完全不同源的窗口,想要通信有两种方式:

    • fragment identifier:片段标识符。http://example.com/x.html#fragment 只是改变 #fragment 不会导致网页刷新,属于破解无法通信之法
    • H5 提供了新的 postMessage 方法发送消息,监听方可以监听 message 事件获取消息
  • AJAX 突破同源限制的方法:

    • JSONP
    • WebSocket
    • CORS (Cross-Origin Resource Sharing)
  • CORS 通信:

    • W3C 标准,允许浏览器跨域请求
    • 详细介绍
    • 区分简单请求和非简单请求,流程不同
    • 简单请求中,Origin Header 很重要
    • 非简单请求,先进行 /OPTIONS 预先检查,然后后续流程类似简单请求
  • Storage 接口:

    • sessionStorage:保存的数据用于浏览器一次会话,结束后会被清理
    • localStorage 保存数据长期存在
    • 只有同域名下的网页才可以读取,跨域会报错
    • length 返回数据项个数
    • setItem 保存 key + value
    • getItem 返回保存的 key 对应的 value
    • clear
    • key 返回对应位置的键值

参考

  1. MDN JavaScript
0%