Skip to content

你不知道的JavaScript读书笔记 #1

@XiaoChengyin

Description

@XiaoChengyin

你不知道的JavaScript读书笔记

作用域

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。

  • 词法作用域
  • 动态作用域

编译

  • 分词/词法分析:将代码字符串转换为词法单元 eg. var a = 2; => vara=2;
  • 解析/语法分析:将词法单元流转换为抽象语法树(AST)
  • 代码生成:将AST转换为可执行代码

the-super-tiny-compiler

作用域链

引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。

找不到变量

  • RHS查询找不到变量会直接抛出ReferenceError异常。
  • LHS查询找不到变量,非严格模式下会在全局作用域下创建一个具有该名称的变量并返回给引擎,严格模式下同RHS查询一样抛出ReferenceError异常。

LHS和RHS的含义是“赋值操作的左侧或右侧”

词法作用域

词法作用域就是定义在词法阶段的作用域,也就是由写代码时将变量和块作用域写在哪里来决定的。
作用域查找会在找到第一个匹配的标识符时停止,如果在多层的嵌套作用域中定义同名标识符,则无法访问外层的标识符(遮蔽效应:内部标识符遮蔽外部标识符)

动态作用域

动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,动态作用域的作用域链是基于调用栈的,而不是代码中的作用域嵌套。
JavaScript 并不具有动态作用域。

function foo() {
    console.log(a)
}
function bar() {
    var a = '动态作用域'
    foo()
}
var a = '词法作用域'
bar() // 调用栈为:bar -> foo

闭包

当函数可以记住并访问所在的词法作用域时,即使函数式在当前词法作用域之外执行,这时就产生了闭包。
无论何时何地,如果将函数作为第一级的值类型到处传递,就会应用到闭包。
在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers、或其他任何异步任务中,只要使用了回调函数,实际上就是在使用闭包。

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz(); // 2
function setupBot(name, selector) {
    $(selector).click(function() {
        console.log('Activating: ' + name);
    })
}
setupBot('Closure Bot 1', '#bot_1');
setupBot('Closure Bot 2', '#bot_2');

THIS

this 机制很像动态作用域,即函数内 this 的值由运行时的调用栈决定,而不是代码中的作用域嵌套

绑定方式

  • 默认绑定:当无法应用其他绑定规则时,非严格模式下 this 指向 window 对象,严格模式下,this 指向 undefined
var a = 2
function foo() {
    console.log(this.a)
}
foo() // 2

function bar() {
    'use strice'
    console.log(this)
}
bar() // undefined
  • 隐式绑定:当函数是由上下文对象调用时,this 指向上下文对象
var obj = {
    a: 2,
    foo: function() {
        console.log(this.a)
    }
}
var bar = obj.foo
var a = 'global'
bar() // 'global'
obj.foo() // 2
  • 显式绑定:callapplybind方法绑定,但不能绑定箭头函数。当显示绑定传入参数为 nullundefined 时,实际应用默认绑定。
  • new 绑定:当函数被 new 操作符调用时
    • 创建一个全新的对象
    • 将这个新对象绑定到函数调用的 this
    • 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象

优先级

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

箭头函数不使用 this 的四种规则,而是根据外层作用域来决定 this,也就是使用词法作用域规则

对象

属性描述符

var myObject = {
    a: 2
}
Object.getOwnPropertyDescriptor(myObject, 'a')
/*  
 *  {
 *      value: 2, // 值
 *      writable: true, // 可写
 *      enumerable: true, // 可枚举
 *      configurable: true // 可配置
 *  }
 */ 
  • writablewritable 决定是否可以修改属性的值

writable 属性是 false 时,非严格模式下修改属性值会静默失败,严格模式下会抛出 TypeError 错误

  • configurableconfigurable 决定是否可以使用 Object.defineProperty 方法修改属性描述符

不管是不是处于严格模式,尝试修改一个不可配置的属性描述符都会抛出 TypeError 错误
所以,把 configurable 修改成 false 是单向操作,无法撤销
configurable: false 会禁止删除该属性。(静默失败)

  • enumerable:不可枚举属性能够被访问,但不会出现在对象属性的遍历中
var obj = {}
Object.defineProperty(obj, 'a', {enumerable: true, value: 2})
Object.defineProperty(obj, 'b', {enumerable: false, value: 3})
obj.propertyIsEnumerable('a') // true
obj.propertyIsEnumerable('b') // false
Object.keys(obj) // ['a']
Object.getOwnPropertyNames(obj) // ['a', 'b']

不变性

  • 对象常量:writable: false, configurable: false 可以创建一个不可修改、重定义、删除的常量属性
  • 禁止扩展:Object.preventExtensions 能够禁止添加新属性,非严格模式静默失败,严格模式抛出 TypeError
  • 密封:Object.seal 在现有对象上调用 Object.preventExtensions 并把所有现有属性标记为 configurable: false
  • 冻结:Object.freeze 在现有对象上调用 Object.seal 并把所有“数据访问”属性标记为 writable: false

GetterSetter

// 字面量
var a = {
    get a() {}
    set a(val) ()
}
// 定义属性
Object.defineProperty(obj, 'prop', {
    get() {},
    set(val) {}
})

存在性

  • 'prop' in objin 操作符会检查属性是否在对象及其原型链中
  • obj.hasOwnProperty('prop'):只会检查是否在对象中

类型

七种内置类型

  • 空值(null)
  • 未定义(undefined)
  • 布尔值(boolean)
  • 数字(number)
  • 字符串(string)
  • 对象(object)
  • 符号(symbol)

typeof

// 两种特殊情况
typeof null === 'object' // true
typeof function() {} === 'function' // true

数字

42.toFixed( 3 ); // SyntaxError
// 42. 是一个合法的数字,等于 42.0,
42..toFixed( 3 ); // 合法
(42).toFixed( 3 ); // 合法
整数检测
Number.isInteger(42) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
NaN

NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

var a = 2 / 'foo' // NaN
typeof a === 'number' // true

// NaN 和自身不相等
a == NaN // false
a === NaN // false

isNaN(a) // true
isNaN('foo') // true isNaN 只能检查参数是否不是NaN,也不是数字
Number.isNaN(a) // true 可靠
a !== a // true NaN 是 js 中唯一不等于自身的值
特殊等式

能使用 ===== 时就尽量不要使用 Object.is(),因为前者效率更高、更为通用。Object.is() 主要用来处理那些特殊的相等比较。

NaN === NaN // false
Object.is(NaN, NaN) // true

+0 === -0 // true
Object.is(+0, -0) // false

强制类型转换 🙃

ToString

.toString()String()12 + ''

  • null => 'null'
  • undefined => 'undefined'
  • 普通对象 => '[object Object]' 引用类型调用 toString() 方法
  • [1,2,3] => 1,2,3

ToNumber

Number() +new Date()

  • true => 1
  • false => 0
  • undefined => NaN
  • null => 0
  • '' => 0
  • [] => 0
ToPrimitive

引用类型被强制类型转换时先通过 ToPrimitive 操作将值转换为基本类型,然后再将该基本类型值转换为对应的类型。
ToPrimitive 操作会首先检查该值是否有 valueOf() 方法,如果有并返回基本类型值就使用该值进行强制类型转换,如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。如果 valueOf()toString() 均不返回基本类型值,会产生 TypeError 错误。

let date = new Date()
+date // 1568801394219
date.valueOf() // 1568801394219

ToBoolean

Boolean()!!number

假值
  • undefined
  • null
  • false
  • +0-0NaN
  • ''
真值

不在假值列表中的值都是真值

Symbol

String(Symbol()) => 'Symbol'

Symbol 类型不能转换成数字,不能隐式转换成字符串。但可以被转换为布尔值

相等比较

宽松相等 == 在比较过程中包含隐式强制类型转换
严格相等 === 没有强制类型转换
Object.is() 比较与严格相等基本相同

  • 字符串和数字进行宽松相等比较时,先把字符串转为数字
  • 布尔值和其他类型进行宽松相等比较,先把布尔值转为数字
  • null == undefined,除此之外 nullundefined 不和其他任何值宽松相等
  • 对象和非对象进行宽松相等比较,先把对象转为基本类型(ToPrimitive操作)
特殊情况
  1. 重写对象原型链上的 toString()valueOf() 方法
Number.prototype.valueOf = function() {
    return 3;
};
new Number( 2 ) == 3; // true

var i = 2;
Number.prototype.valueOf = function() {
    return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
    console.log( "Yep, this happened." );
}
  1. 假值的相等比较
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕! false => 0
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕! [] => '', false => 0, '' => 0
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!  
0 == {}; // false
  1. 极端情况
[] == ![] // true

2 == [2] // true
"" == [null] // true [null] => ''

抽象关系比较(不等式比较)

如果比较双方都是字符串,直接进行比较
如果是其他情况,双方首先调用ToPrimitive,如果结果出现非字符串,就调用ToNumber将双方转为数字进行比较

// 特殊情况
var a = { b: 42 };
var b = { b: 43 };
a < b; // false [object Object] < [object Object]
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
// a <= b --> !(a > b)

其他

[] + {} // '[object Object]'
{} + [] // 0  {} 被当作空代码块跳过

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions