《你不知道的JavaScript》总结

1、作用域和闭包

作用域

作用域是什么

  作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。
  引擎在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
  查找声明时存在两种查询类型:LHS(赋值操作的目标是谁)以及RHS(谁是赋值操作的源头)
  如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询。如果目的是获取变量的值,就会使用 RHS 查询。

function foo(a) {
  console.log( a ); // 2
}
foo( 2 );
-----
foo( 2 )对 foo 进行 RHS 查询
为参数a赋值时存在隐式的a=2进行LHS查询
console.log( a )对console和a进行RHS查询。

  LHS 和 RHS 查询都会在当前执行作用域中开始。根据作用域链,一层层向上级查找,直到到达全局作用域。
  不成功的 RHS 引用会导致抛出 ReferenceError 异常、不成功的 LHS 引用会导致自动隐式地创建一个全局变量(非严格模式下)。
  若找到了变量但对其进行的操作是非法的或不合理的,引擎会抛出TypeError异常
JavaScript中的作用域是基于编译器语义的词法作用域,由写代码时将变量和块作用域写在哪里来决定的。

提升

JavaScript 引擎将var a = 2分为两个阶段的任务:编译阶段(var a)和执行阶段(a = 2)。因此无论作用域中的声明出现在什么地方,都会在代码本身执行前首先进行处理。这种将所有声明移动到各自作用域顶端的过程称为变量提升。

闭包

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包

2、this和对象原型

this

this是什么

  this 是在运行时进行绑定的,它的上下文只取决于函数的调用位置。当一个函数被调用时,会创建一个执行上下文,其中包含调用栈、函数的调用方式、传入的参数、this等信息。

调用位置与调用栈

那么调用位置是怎么确认的呢?其中就涉及到了调用栈,函数的调用位置就在当前调用栈的前一个调用中。

function baz() {
  // 当前调用栈是:baz
  // 因此,当前调用位置是全局作用域
  console.log( "baz" );
  bar(); // <-- bar 的调用位置 
}
function bar() {
  // 当前调用栈是:baz -> bar
  // 因此,当前调用位置在 baz 中
  console.log( "bar" );
}
baz(); // <-- baz 的调用位置

this绑定规则

  1. 由new调用则绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用则绑定到指定的对象。
  3. 由上下文对象调用则绑定到那个上下文对象。
  4. 默认在严格模式下绑定到undefined,否则绑定到全局对象。

箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,其继承外层函数调用的 this 绑定

对象与对象原型

对象

JavaScript 中的对象有字面形式var a = { .. }和构造形式var a = new Object(..),其是七个基本类型之一,此外还包含function、Array等对象的子类型,不同子类型具有不同的行为。

对象的属性分为两种:数据属性和访问器属性。
数据属性包含一个保存数据值的位置,存在四种特性描述它们的行为。
[[Configurable]]:表示属性是否可以通过delete删除,是否可修改特性,是否可改为访问器属性。
[[Enumberable]]:表示是否可以通过for-in循环返回。
[[Writable]]:表示属性的值是否可修改。
[[Value]]:包含属性实际的值。
访问器属性不包含数据值,其包含一个获取函数和设置函数(非必须),存在四种特性描述它们的行为。
[[Configurable]]:表示属性是否可以通过delete删除,是否可修改特性,是否可改为访问器属性。
[[Enumberable]]:表示是否可以通过for-in循环返回。
[[Get]]:获取函数,在读取属性的时候调用。
[[Set]]:设置函数,在写入属性的时候调用。

对象原型

https://www.cnblogs.com/hugebook/p/15694581.html

3、类型和语法

类型

JS中的类型指的是值的类型,变量是没有类型的。

类型分类

JavaScript 有七种内置类型:

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

判断类型分类

typeof 运算符可查看值的类型,返回的是类型的字符串值。

 typeof undefined === "undefined"; //true
 typeof true === "boolean"; //true
 typeof 42 === "number"; //true
 typeof "42" === "string"; //true
 typeof { num:42 } === "object"; //true
 typeof Symbol() === "symbol"; //true
 typeof function a(){} === "function"; //true
 typeof [1,2,3] === "object"; //true
 typeof null === "object"; // true. **需要特殊判断(!a && typeof null === "object")**

类型转换

抽象操作(仅供内部使用)

ToString

负责处理非字符串到字符串的强制类型转换
基本类型值的字符串化规则为:
null 转换为 "null";
undefined 转换为 "undefined";
true 转换为 "true";
数字的字符串化则遵循通用规则;
对于普通对象, toString()(Object.prototype.toString())返回内部属性 [[Class]]的值,如 "[object Object]";
数组的默认 toString() 方法经过了重新定义,将所有单元字符串化以后再用 "," 连接起 来;

ToNumber

将非数字值转化为数字。
true 转换为 1;
false 转换为 0;
undefined 转换为 NaN;
null 转换为 0;
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字;

为了将值转换为相应的基本类型值,抽象操作 ToPrimitive会首先检查该值是否有 valueOf()方法。 如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()的返回值来进行强制类型转换,如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

ToBoolean

JavaScript 中的值可以分为以下两类:
(1) 可以被强制类型转换为 false 的值
(2) 其他(被强制类型转换为 true 的值
布尔强制类型转换结果为 false的值为假值。
假值列表有:
undifined、null、false、+0、-0、NaN、""等7个
假值列表以外的值都是真值。

比较关系

相等关系
类型 转换规则
数字与字符串 转换为数字
其他与布尔 转换为数字
null 和 undefined true
对象和非对象 对象转化为基本类型(ToPrimitive)
大小关系

比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强 制类型转换为数字来进行比较。
注:>=会自动处理为<
特殊:NaN == !NaN

4、异步和性能

异步

只用回调管理异步存在的问题

控制反转

回调暗中把控制权交给第三方来调用代码中延续的行为。这种控制转移导致一系列麻烦的信任问题,比如回调被调用的次数是否会超出预期。

回调地狱

一个异步函数嵌套着一个异步函数,后一个异步函数依赖于前一个异步函数,其会使代码变得更脆弱,因为它没考虑可能使在异步函数嵌套逐步执行的过程中出现偏差的异常情况。若第2步失败了,第3步永远不会到达,不管是重试第2步,还是跳转到其他错误处理流程等等。一旦指定(也就是预先计划)了所有的可能事件和路径,代码就会变得非常复杂,以至于无法维护和更新。

Promise是什么

  Promise意为承诺,在现在,这个承诺是存在的,但是还未实现,到了未来,一旦承诺实现了,就可以用承诺值替换这个承诺,但也存在承诺完成不了的情况,可以看到承诺(未来值)可能成功,也可能失败。
JavaScript 中Promise是ES6新增的引用类型(对象),可以通过new操作符来实例化(承诺)。需要传入一个执行器函数(处理承诺)作为参数。执行器函数有两个参数resolve函数和reject函数,分别可以将承诺设置为兑现(fulfilled)和拒绝(rejected)状态,若未调用这个两函数,则状态为待定(pending),待定也是承诺的初始状态,一旦落定为fulfilled或rejected,则不可再改变。

let p = new Promise((resolve, reject) => {
  resolve()   //决议这个promise
  // or
  reject()    //拒绝这个promise
})

Promise如何解决回调地狱问题

Promise 利用了三大技术手段来解决回调地狱:回调函数延迟绑定、链式流、错误冒泡。

readDataAsyn('url1').then(data => {
    // data doSomething
    return readDataAsyn('url2');    // 1
}).then(data => {
    return doSomething(data); // 2
}).then(data => {
    // data doSomething // 3
}).catch(err => {
  console.log(err) // 4
  // doSomething
})

  回调函数延迟绑定:上面代码中回调函数没有在readDataAsyn中直接声明,而是通过 then 方法传入的。
  链式流:每次调用then()都会创建并返回一个新的 Promise,可以将这些Promise链接起来形成链式流,不管从 then() 调用的完成回调(第一个参数)返回的值是什么,它都会被自动设置为被链接 Promise的完成。
  错误冒泡:若2中出现了错误,错误信息会通过Promise对象逐层传递,直到遇到catch错误处理函数。

then() 存在两个的参数,分别为成功回调函数、错误回调函数,即

then(function fulfilled(response2){ 
  // doSomething
},
function rejected(err){ 
  // doSomething
})
catch(()=>{}) 是then(undefined,()=>{})的语法糖。

生成器

  生成器是 ES6 的一个新的函数类型,它并不像普通函数那样总是运行到结束。取而代之 的是,生成器可以在运行当中(完全保持其状态)暂停,并且将来再从暂停的地方恢复运行。
  yield/next(..)这一对不只是一种控制机制,实际上也是一种双向消息传递机制。yield ..表达式本质上是暂停下来等待某个值,接下来的 next(..) 调用会向被暂停的 yield 表达式传回 一个值(或者是隐式的 undefined)。
  在异步控制流程方面,生成器的关键优点是:生成器内部的代码是以自然的同步 / 顺序方 式表达任务的一系列步骤。其技巧在于,我们把可能的异步隐藏在了关键字 yield 的后面, 把异步移动到控制生成器的迭代器的代码部分。
  生成器为异步代码保持了顺序、同步、阻塞的代码模式,这使得大脑可以更自 然地追踪代码,解决了基于回调的异步的两个关键缺陷之一。

性能

posted on 2022-02-21 12:02  HHH_B  阅读(115)  评论(0编辑  收藏  举报

导航