# 字符串 (ASCII--unicode--UTF),'🤣'.length 为 2,因为它的二进制表示超出了码元长度(UTF-16)
- 字符串最早期是:使用一个字节表示的 ASCII 码,表示常用的字符足够了,但是表示不了全球的其他语言与字符,后来就出现了定长的 unicode 编码,使用 2 个或 4 个字节表示,由于扩大了只用英语的国家的字符串占用,现如今常使用变长的 unicode 编码:如 UTF-8、UTF-16 等,特点是兼容 ASCII 码
- charCodeAt 和 codePointAt:对于定长的一个码元的字符(常常是 UTF-16 编码的字符),使用 charCodeAt 就足够了,但是对于不定长的需要两个码元表示的字符(UTF-8 编码的、需要两个码元表示的),这时候使用 charCodeAt 只能返回第一个码元,需要使用 codePointAt 才能正常显示。
- 码元和码点
码元:码元是工程妥协的产物,为了平衡空间效率和处理复杂度,因为直接使用码点进行存储的话,会导致很多空间浪费。js 采用 UTF-16 变长编码方式,当一些符号超出 16 位(2 字节)时,就需要多个码元辅助存储。
码点:码点是跨平台统一的逻辑身份,是每个字符全球唯一的标识号,便于跨平台互操作,采用 UTF-32 编码方式。
字符串的.length,计算的码元的个数,所以多码元字符的长度不是一,使用 for 循环时要注意长度(可以使用 for of 替代)。
# 原型添加
| var obj={}; |
| console.log(obj.__proto__) |
| obj.__proto__ = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] |
| console.log(obj.push(8)) |
| obj.length = 1; |
| obj.push(100) |
# js 运算规则
# js 语言类型
- js 通常被归类为解释型语言,但事实上它是一门编译型语言,同时也是一门动态(变量类型可变)弱类型(隐式类型转换)语言。
# js 解析步骤
- 词法分析 --- 语法分析 --- 目标代码生成 --- 编译为机器码执行
# 词法作用域
- eval 函数会修改其所处的词法作用域,with 声明会根据对象创建一个全新的词法作用域,严格模式下被禁用,两者不推荐使用,会影响性能。
- 词法作用域是由函数声明的位置来决定的,在编译阶段词法作用域就已经基本确定。
- 基于作用域的隐藏方法是依据于最小特权原则或者叫
最小授权原则 ,意思是应该最小限度地暴露必要的内容,而将其他内容隐藏起来,防止预期之外的调用 - 根据
最小授权原则 ,三方库通常只会暴露一个对象,所有待使用的 api 都在这个对象地内部,这个对象会被作为这些 api 的命名空间。
# 变量提升
- let 和 const 声明的变量也会进行变量提升,但在初始化赋值之前调用这个变量时会抛出一个异常,社区通常称之为:暂时性死区和块级作用域
- 类可以被赋值多次,实例化一个对象就意味着将类的行为复制到物理对象中去
# this 指向
- 除箭头函数之外,需要谨慎使用 this 来引用一个词法作用域内部的变量,因为 this 是在函数调用时发生的绑定,
this与函数的调用方式相关,函数的作用域链和函数的声明位置相关 。 - 箭头函数没有 this 和 arguments 等面向对象的内容,箭头函数的出现是为了解决
函数的二义性问题 ,箭头函数的 this 在定义时就已经确定,是 继承自外层的函数作用域 。 - this 的绑定优先级: new 绑定 > call、apply、bind 等显示绑定 > 隐式绑定 (实例调用) > 默认绑定 (浏览器环境是 window, 严格模式下是 undefined,node 环境是空对象)。
- javaScript 中函数是一等公民,因为函数本质上和普通对象一样,可以像操作其他对象一样操作函数 (比如赋值,传参,返回等)。
- js 中数据类型设计为:类型二进制表示如果前三位都是 0 的话会被判断为 object 类型,null 的二进制表示全是 0, 所以会被判断为 object 类型。
- 部分内建函数:Number、String 和 Boolean 等当作为函数调用时会返回字面量,当使用 new 调用时会返回对应类型的对象,其余内建函数如:Object、Array、Function、RegExp 和 Error, 无论怎么调用都是返回对象,null 和 undefined 只有文字形式,没有构造方法。
- 内建函数 Array 传递一个参数时,会被当作数组的长度而非一个元素。
- 对象中的属性名永远都是字符串,即使是数字也不例外,不过数字字符串会被作为排序属性。(Symbol 也可以作为属性名)
- 数组中仍然可以像对象一样存其他类型的属性。
# 对象的描述符
ES5 开始,所有的对象上的属性都具备了 描述符 ,分为 属性描述符 和 访问描述符 ,描述符可以通过 Object.getOwnPropertyDescriptors 获取(访问描述符默认没有)、通过 Object.defineProperty 设置,包含:
# 属性描述符
- value (值)、
- writable (是否可以被赋值)、
- configurable (是否可以被配置:----- 可以使用 Object.defineProperty 或 delete)
- enumerable (是否可以被枚举) 四个属性描述符
# 访问描述符(和 value 属性不能同时设置)
# 对象的不可变设置
- 针对对象的某一个属性,可以使用
writable 和 configurable 这两个属性描述符,就可以创建一个真正的常量属性 - 阻止扩展:对象禁止扩展 (禁止添加属性但是可以删除!!), 使用
Object.preventExtensions - 密封:对象禁止扩展或删除属性 (但是可以修改), 使用
Object.seal - 冻结:对象禁止扩展删除和修改 (冻结是 js 对象最高级别的不可变设置), 使用
Object.freeze
# 迭代器
- 对象默认没有内置迭代器(Symbol.iterator 属性),数组和 Map、Set 等集合内置了迭代器属性
# 面向对象
- 面向对象强调的是数据和操作数据的行为本质上是相互关联的,类并不是编程的继承,而是一个可选的代码抽象
- js 中有近似类的语法,但是 js 的机制似乎一直在阻止我们使用类设计模式
# 原型链
- 构造函数的 prototype 是其实例的原型、父类的实例
- 使用 new 调用构造函数产生的实例,其原型链继承于父类,而直接调用构造函数所产生的实例,其原型链指向 Object, 相当于由 Object 所构造 (this.key=value 的形式)
# 改变原型链
- 内置的构造函数改变 prototype 无效,其实例改变__proto__会改变原型链,但仍然可以使用原来的方法,不过原型链新的同名方法会进行覆盖;
- 自己写的构造函数改变 prototype 会导致:实例的 constructor 指向其原型的 constructor, 其实例改变__proto__会改变原型链,但原来方法仍然可以使用,不过同名方法不会覆盖原来的方法。
# constructor
- 实例的 constructor 根据调用方式和构造函数的 prototype 有关,但是一旦实例构造出来之后,其 constructor 便不会再被更改
- 构造函数不使用 new 调用时,其实例的 constructor 是 Object, 使用 new 调用时,其实例的 constructor 是构造函数 prototype 的 constructor
| function Foo() { |
| this.name = "" |
| return this |
| } |
| |
| const a = new Foo(); |
| console.log(a.constructor); |
- 当改变构造函数的 prototype 时,使用 new 构造的实例就失去了默认的 constructor 属性,而会将构造函数 prototype 的 constrcutor 作为其实例的 constructor
| function Foo() {} |
| |
| Foo.prototype = {}; |
| var a1 = new Foo(); |
| console.log(a1.constructor); |
| console.log( |
| a1.constructor === Foo.prototype.constructor |
| ); |
# class 类
- class 并不会像传统面向类的语言一样在声明时静态复制所有的行为,class 基本上只是现有委托机制 (prototype) 的一种语法糖.
- ES6 的 class 和 extends 关键字实现继承的方式是一种寄生组合式继承
# typeof 安全防范
- typeof 一个未定义的变量时不会报错,而是会返回 undefined
# 类数组
- 类数组对象可迭代可枚举,并具有 length 属性,但是类数组对象并没有数组那些原型上改变自身的方法
- 类数组对象设计的初衷是只用于被遍历和读取,而不需要我们使用方法去修改它,没有数组原型上的方法从而使得自身更加安全
- 数组和字符串也都属于类数组,除此之外,还有:arguments、NodeList、HTMLCollection
# 字符串
- js 中字符串是不可变的,数组是可变的
- 字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串,而数组的成员函数都是在原始值上进行操作
- 由于字符串的不可变性,因此它没有数组原型上 reverse 等改变自身的方法
# 数字
- js 中数字类型采用的是
64位双精度浮点数 ( 12位指数+52位尾数 )来进行存储的 - 0.2 + 0.1 由于精度丢失结果约为 0.30000000000000004, 而不等于 0.3
- js 设置了一个误差范围值 Number.EPSILON, 为 2^-52 大小,在误差值范围内就认定两个值相等
- Infinity/Infinity 为 NaN, 有穷正数除以 Infinity 为 0
- +0 和 -0 相等,这时可以使用 Object.is 区分两者
# NaN
- typeof NaN 返回 number
- NaN !== NaN
- isNaN () 用于检查是否是 NaN, 但是有个缺陷:传入 undefined 和非空字符串时也为 true, 其他为 false, 通常使用 Number.isNaN, 这个方法不会存在这样的问题,或者使用 Object.is 方法
# 值和引用
- js 中除了 import 导入的东西是引用复制之外,其他任何操作包括对象赋值都是将当前变量指向堆中的数据,而不是复制一个引用
- 标量基本类型值是不可更改的 (字符串和布尔值也是如此)
# Symbol
- Symbol 作为属性时,不可以被枚举
- Symbol 可以通过 Object.getOwnPropertySymbols () 进行公开
# 强制类型转换和隐式类型转换
- Object.prototype.toString 返回内部属性 [[Class]] 的值,如:"[object Object]"
- 如果对象有 toString 方法,字符串化时,就会调用该方法,并返回其返回值
- JSON.stringify 并非严格意义上的强制类型转换,因为它总是返回字符串
- 不安全 JSON 值是指:JSON 转换时会被忽略或丢失的值,undefined、function 和 symbol 和包含循环引用的对象都是不安全 JSON 值
- 对对象使用 JSON 转换时,首先会尝试调用它的 toJSON 方法,先转换为一个安全的 JSON 值,然后再被转换为字符串
- JSON.stringify 第二个参数可以是一个数组或对象,用于指定哪些属性应该被处理
- JSON.stringify 第三个参数是一个正整数或字符串,用于指定每一级缩进的字符数或字符
# 隐式类型转换
# 两个引用数据类型直接 false、不走转换
# 引用数据类型转基本数据类型
- 1、尝试使用
Symbol.toPrimitive 方法转为基本数据类型,失败则报错 - 2、比对字符串时:尝试使用
toString 方法转为基本数据类型,失败不报错 - 3、对比其他时(数字、布尔):尝试使用
valueOf 方法转为基本数据类型,失败不报错
# 基本数据类型转换
- 1、将 String 项转为 Number 项(使用 Number 方法)
- 2、将 Boolean 项转为 Number 项
- 3、null 和 undefined 不进行转换,但他俩隐式相等
- 4、转换失败,返回 false
# toBoolean
- 返回 false 的值有:undefined、null、false、+0、-0、NaN 和 ""
- 由于历史原因,存在一些假值对应比如:document.all 得到的对象,也会被转为 false
# 隐式类型转换
- 例如 == 或者 if 判断时或者不同类型之间进行数学运算时会进行转换
# 显示类型转换
- 例如 Number、toNumber、String、toString、Boolean 和 +、~、|、^、>> 等位运算
- 使用~~或者 >>0 可以截断数字的小数部分
- 允许 Symbol 类型到字符串类型的显示类型转换,而对其的隐式类型转换会产生错误
- "" 转换为数字是 0
- [] 转换为字符串是 "", 准换为数字是 0
# Symbol 类型转换
- Symbol 类型转换到 Boolean 类型:可以强制类型转换,也可以隐式类型转换
- Symbol 类型转换到 String 类型:可以强制类型转换,不能隐式类型转换
- Symbol 类型转换到 Number 类型:不能强制类型转换,也不能隐式类型转换
# 数字的二进制表示:补码表示法
- ~-1 等于 0
- 一个数的负数是通过取该数的反码然后加 1 得到的。反码是通过将一个数的二进制表示中的每个 0 变为 1,每个 1 变为 0 得到的。
- 补码系统中的加减法可以使用相同的硬件电路,因为减法可以看作加法的特殊情况 (加上负数)
- js 没有采用反码表示法:利用首位表示正负,其余部分与正数相同。因为反码表示法进行加减法时很麻烦,并且没有溢出检测等
# parseInt 类型转换
- parseInt (1/0,19) 返回 18, 原因是 1/0 是 Infinity, 然后被转为:"Infinity", 当以 19 为基数时,I 是 18,n 由于是 NaN, 因此停止解析,返回 18
# 进制转换
- 进制转换可以使用:Number.parseInt (string, radix), 或者使用:Number.prototype.toString (radix)
# 逻辑运算符 (选择器运算符)
- js 中的 && 和 || 被成为选择器运算符更合适,因为它的返回值是两个操作数中的一个,而不是布尔值
- 逻辑运算符存在短路机制,得到想要的结果时就不会继续执行,例如可以: say && say ()
# 双等判断
| |
| console.log(Boolean([])); |
| console.log(true == []); |
| |
| |
| |
| |
- 注意隐式类型转换和显示类型转换的规则不同,例如:[] 因为是对象,显示类型转换是 true, 但是隐式类型转换之后是 false
- 当布尔值与其他类型进行双等判断时,会调用内部 toNumber 操作,将 boolean 转换为 number 类型,再进行判断
- 建议永远不要使用布尔值的双等判断,因为较差的可读性可能会导致潜在的问题
- null==undefined, 内部判断 null 和 undefined 时,会调用 toBoolean 抽象操作进行判断
- boolean 被转换时,要被转换为数字,string 和 number 转换时,也要将 string 转换为数字
# 比较运算符 (> 和 <)
- 如果双方都是字符串,则按照字母顺序逐个字符进行比较 Unicode, 直到比较出大小
# 返回值
- 代码块的返回值就是最后一个语句 / 表达式的返回值
- 赋值表达式也是有返回值的,可以利用这个特性进行连续赋值 (链式赋值)
# 循环标志
- 循环可以打上标志,并且配合 continue 和 break 等关键字使用
# 运算符优先级
- && 优先级大于 || 的优先级,两者大于三元表达式的优先级
- 逻辑运算符表达式是左关联表达式,三元表达式是右关联表达式
# ASI 自动分号插入
# try catch finally
- finally 中的返回值会覆盖 try 和 catch 中的返回值
- finally 中省略 return 相当于返回 try 和 catch 中的返回值,而不是返回 undefined
# 浏览器环境特性
- 浏览器环境中创建全局变量会在 global 对象上创建一个同名属性
- 浏览器环境中带有 id 属性的 DOM 元素会创建同名的全局变量
# 事件循环
- 事件循环在整个 js 运行过程中一直存在,用于将异步队列中的异步任务放在执行栈中执行
- 事件循环队列通常被称为微任务队列,是 js 语法的一部分,任务队列又叫做宏任务队列,通常由 js 的运行环境所实现
# 回调地狱
- 回调地狱的缺点并不是简单的因为难以阅读,它还会导致异步数据流的跟踪异常困难
- 回调函数同时会受到控制反转的影响,因为回调暗中将控制权交给第三方 (通常是不受控制的第三方工具), 来调用,这种控制权转移会导致一系列的信任问题,比如回调函数调用的次数是否会超出预期
# Promise
- Promise 是用于解决回调地狱问题的
- 判断一个值是否是 Promise 使用 instanceOf 存在一些弊端:其他窗口接收到的 Promise 可能和当前窗口的 Promise 不同,因此这样的检查无法识别 Promise 实例,可以通过
鸭子类型 来进行判断,即:拥有 then 方法 - Promise 的执行器函数会捕获错误,将自身的状态变为 rejected。
# 生成器函数
- 生成器函数是一种状态机切换的行为模式,底层使用一个寄存器去监控迭代器实例的状态,从而根据状态返回对应的值
- 生成器函数返回值是一个迭代器对象 (具有 next 方法的对象)
- ES6 之前几乎都存在一个普遍依赖的设定:一个函数一旦开始执行,就会运行到结束,但是生成器函数打破了这种设定
- 生成器函数的 yield 和迭代器对象的 next 相互组合,构成了一个双向的消息传递系统
- 迭代器对象的 next 方法中传入的参数会在下一次赋值给 yield 表达式
| function* gen(x) { |
| let y = x * (yield); |
| const res = (yield y) * 100; |
| return res; |
| }; |
| const iter = gen(6); |
| |
| console.log(iter.next()); |
| |
| |
| console.log(iter.next(10).value); |
| console.log(iter.next(1).value); |
- 同一个生成器函数创建的迭代器实例之间可以同时运行,并且可以彼此交互
- 迭代器委托:生成器中可以使用 yield 关键字委托给另一个生成器,从而实现生成的迭代具有 yield 部分的拼接效果
| function* foo1() { |
| yield "a1"; |
| yield *foo2(); |
| yield "a2"; |
| } |
| function* foo2() { |
| yield "b1"; |
| yield "b2"; |
| } |
| const iter = foo1(); |
| let res = iter.next(); |
| while (!res.done) { |
| console.log(res.value); |
| res = iter.next(); |
| } |
- 生成器函数内部有着看似完全同步的代码,除了 yield 关键字本身,yield 一个函数执行结果时,函数内部的运行也可以完全异步
- 最巧妙地地方在于:可以在生成器函数中使用 yield 去抛出 Promise, 由于这种模式十分好用,后来便出现了 async await 关键字
# async await
- async 函数的返回值一定是一个 Promise, 如果是值会被封装为 Promise.resolve ()
- 使用 async 标记函数之后,函数就是一个异步函数,在异步函数内部可以使用 await 关键字来暂停函数的执行,去等待后面 Promise 的 resolve 结果 (不是 promise 的会被包装为 promise.resolve), 异步函数中在 await 后面的部分会被作为这个 promise 的.then 回调进行执行
- await 后面需要跟 Promise 对象 (不是 promise 的话会被封装为 promise), 不然就没有意义,并且 await 后面的 Promise 对象不必写 then, 因为 await 的作用之一就是获取到后面 Promise 对象成功状态传递出来的参数
# async 函数的返回值
- async 函数会默认对返回值进行包装处理,对不同类型的返回值进行包装,开启不同数目的微任务,等待指定个数的微任务执行完之后再将任务放入微任务回调中。
- return 结果值:非 thenable、非 promise(不等待)
- return 结果值:thenable(等待一个 then 的时间)
- return 结果值:promise(等待两个 then 的时间)
| async function testC () { |
| return new Promise((resolve, reject) => { |
| resolve() |
| }) |
| } |
| testC().then(() => console.log(1)); |
| Promise.resolve() |
| .then(() => console.log(2)) |
| .then(() => console.log(3)) |
| .then(() => console.log(4)) |
| |
| async function testB () { |
| return { |
| then (cb) { |
| cb(); |
| } |
| }; |
| } |
| testB().then(() => console.log(1)); |
| Promise.resolve() |
| .then(() => console.log(2)) |
| .then(() => console.log(3)); |
| |
| async function test () { |
| console.log(1); |
| await 123 |
| console.log(2); |
| } |
| test(); |
| console.log(3); |
| Promise.resolve() |
| .then(() => console.log(4)) |
| |
# async 函数中使用 await 等待跟返回值一样,非 thenable 不会嵌套,thenable 会嵌套一层,但是 await 一个 promise 时,不会嵌套(TC39 优化)
| async function test () { |
| console.log(1); |
| await { |
| then:(cb)=>{ |
| cb() |
| } |
| } |
| console.log(2); |
| } |
| test(); |
| console.log(3); |
| Promise.resolve() |
| .then(() => console.log(4)) |
| .then(() => console.log(5)) |
| .then(() => console.log(6)) |
| .then(() => console.log(7)); |
| |
# async 函数 await 等待 promise 时,直接放入微任务队列中,没有微任务嵌套。
- TC39 优化:为了获得更快的 async 函数和 promise,当 await 后面等待 promise 时,会移除多余的两个微任务嵌套。
| async function test () { |
| console.log(1); |
| await new Promise((resolve, reject) => { |
| resolve() |
| }) |
| console.log(2); |
| } |
| test(); |
| console.log(3); |
| Promise.resolve() |
| .then(() => console.log(4)) |
| .then(() => console.log(5)) |
| .then(() => console.log(6)) |
| .then(() => console.log(7)); |
| |
# await 下面的代码
- await 下面书写的代码会被封装为微任务回调,当 await 的 promise 的最后一个.then 执行时,推入到微任务队列中。
| function func () { |
| |
| |
| |
| |
| |
| |
| return Promise.resolve() |
| .then(() => console.log(5)) |
| .then(() => console.log(6)) |
| .then(() => console.log(7)) |
| } |
| async function test () { |
| await func(); |
| console.log(3); |
| } |
| test(); |
# Worker
- 在 Worker 内部是无法访问主程序的任何资源的,意味着你不能访问它的任何全局变量,也不能访问页面的 DOM 或者其他资源,他是一个完全独立的线程
- 应用场景:
- worker 之间的通信常使用 strcturedClone 方法,这样就不用付出性能损失了,另一个好的选择 (尤其是针对大数据集而言) 就是使用 Transferable 对象,这样数据的所有权也会转移
- SharedWorker 可以创建一个整个站点或 app 的所有页面实例都可以共享的中心 Worker, 这个 Worker 需要通过某种方式来得知消息来自于哪个程序,这个唯一标识符成为端口
# SIMD
- SIMD 打算把 CPU 级的并行数学运算映射到 JavaScript API,以获得高性能的数据并行运算,比如在大数据集上的数字处理
# TCO 尾调用
- 尾调用就是一个出现在另一个函数结尾处的函数调用,调用结束之后就没有其余事情要做了
| function foo(x) { |
| return x; |
| } |
| function bar(y) { |
| return foo( y + 1 ); |
| } |
| function baz() { |
| return 1 + bar( 40 ); |
| } |
| baz(); |
- 尾调用会避免创建多余的帧栈,这在嵌套层级很深的递归调用中作用就更加重要
# 模板字面量函数调用
- 函数的模板字面量调用方式的第一个参数是模板字符数组,后续参数是模板字符串的变量
| const person = "人"; |
| const age = 28; |
| function myTag(strings, personExp, ageExp) { |
| console.log(strings,'模板字符被分割数组'); |
| console.log(strings.raw,'raw方法会保留转义字符的内容'); |
| console.log(personExp,'模板变量1'); |
| console.log(ageExp,'模板变量2'); |
| } |
| const output = myTag`That \n ${person} is a ${age}.`; |
# 计算机执行
| function sum(num1, num2 = num1) { |
| console.log(num1 + num2); |
| } |
| sum(10); |
| let a = 10; |
| |
| console.log(++a + a++ + a); |
# 输出题
| let person = { name: "Lydia" }; |
| const members = [person]; |
| person = null; |
| console.log(members); |
| console.log(3 + 4 + "5"); |
| const num = parseInt("7*6", 10); |
| function greeting() { |
| throw "出错了"; |
| } |
| function sayHi() { |
| try { |
| const data = greeting(); |
| console.log("捕获错误后不会执行", data); |
| } catch (e) { |
| console.log("捕获到错误", e); |
| } |
| } |
| sayHi(); |
- 顶层直接赋值相当于在 globalThis 上绑定属性
| (() => { |
| let x = y = 10; |
| })(); |
| console.log(x); |
| console.log(y); |
# ESM 和 CommonJS
- CommonJs 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化不会影响到这个值.
- ESM 模块是动态引用,并且不会缓存,模块里面的变量绑定其所有的模块,而是动态地去加载值,并且不能重新赋值。
- ES6 输入的模块变量,只是一个 “符号连接符”,所以这个变量是只读的,对它进行重新赋值会报错。如果是引用类型,变量指向的地址是只读的,但是可以为其添加属性或成员。
# 基本数据类型作为函数变量传入时,是一份栈的拷贝
| let num = 10; |
| const increaseNumber = () => num++; |
| |
| const increasePassedNumber = number => number++; |
| |
| const num1 = increaseNumber(); |
| |
| const num2 = increasePassedNumber(num1); |
| console.log(num1); |
| console.log(num2); |
# 对象解构时,只会解构并拷贝最外层,当最外层是基本数据类型时相当于一份新的数据
| const value = { number: 10 }; |
| |
| const multiply = (x = { ...value }) => { |
| console.log(x.number *= 2); |
| }; |
| multiply(); |
| multiply(); |
| multiply(value); |
| multiply(value); |
# reduce 方法的第二个参数
- Array.prototype.reduce 方法,当不存在第二个参数 initialValue 时,会将 0 号元素作为初始值,从第一个元素开始迭代
| [1, 2, 3, 4].reduce((x, y) => console.log(x, y)); |
# ESM 中静态导入的模块会被提升并且先执行,CommonJs 中的模块是函数,调用了才会执行.
| |
| console.log("在模块之后执行"); |
| import { sum } from './sum.js'; |
| console.log("最后执行"); |
| |
| console.log('静态提升并且先执行'); |
| export const sum = (a, b) => a + b; |
- String.prototype.padStart/padEnd 第一个参数是 pad 后的长度,第二个参数是 pad 添加的字符.
- String.prototype.raw 用于获取一个模板字符串的原始字符串 (保留了 \ 和 \n 等转义字符)
# this 指向
- 关于 this 指向,普通函数调用时,会根据调用方式绑定 this, 不过箭头函数没有自己的 this, 会沿着作用域链向上进行查找,直到找到 this
- 对象的属性是字符串或 symbol
| const obj = { |
| 1:"你好" |
| } |
| |
| |
| console.log(obj['1'] === obj[1]); |
| console.log(obj.hasOwnProperty('1')); |
| console.log(obj.hasOwnProperty(1)); |
# Object.defineProperty 传入属性描述符
Object.defineProperty() 的第三个参数以及 Object.defineProperities() 和 Object.create() 的第二个参数传入的是属性描述符,而不是普通对象!。
# typeof 和 instanceof
typeof 是底层的操作,用于读取某个变量或字面量的标记类型的。八种数据类型基本对应( typeof 数组会返回:object ),但由于历史遗留问题: 对于typeof null 会返回object 。
instanceof 仅用于操作对象,用于判断该对象的原型链上是否包含某个构造函数的 prototype。
# forEach 中操作原数组
forEach 中操作原数组无影响
# ol 标签 type 属性和 start 属性
type 指定类型,start 指定从类型中哪一个开始
# 设计模式基本类型
创建型 结构型 行为型
# $ 符号可以作为变量名
var $$a = ''
# arr 的 splice 方法的第二个参数是删除个数,后面的参数是添加到 startIndex 位置的元素们。
# js 引擎对于变量提升(函数声明和变量定义)只会提升一层
| console.log(test); |
| console.log(window.test); |
| function name(params) { |
| return function test(params) {} |
| } |
| name(); |
| console.log(test); |
# 具名函数
| var func = function test() {} |
| console.log(typeof test); |
| console.log(typeof test()); |
# Number () 调用
Number、String 等如果直接调用会返回值类型,如果被用于 new 调用的话会返回对应类型的对象,当使用 Object (item) 时,如果 item 是对象直接返回对象,如果 item 是数字则会返回数字对象(new Number (item)),其他类型同理。
# this 指向
| |
| |
| |
| |
| |
| |
| |
| |
| function call() { |
| this.name = "张三" |
| } |
| const obj = { |
| name: call.bind(globalThis) |
| } |
| const res = new obj.name(); |
| console.log(res); |
# bind 链式绑定以第一个为准
| function foo() { |
| console.log( this.a ); |
| } |
| var obj1 = { |
| a: 'obj1' |
| }; |
| var obj2 = { |
| a: 'obj2' |
| } |
| var tmp = foo.bind(obj1).bind(obj2) |
# 当显示绑定 null 或者 undefined 时,this 直接绑定到 window 上
| function log() { |
| console.log(this); |
| } |
| const fn = log.bind(undefined) |
| const obj = { |
| fn |
| } |
| console.log( |
| obj.fn() |
| ); |
# var 和 let const 的区别
- var 不存在块级作用域,let const 存在块级作用域
- let const 存在暂时性死区,在初始化之前调用会抛出异常
- var 声明在非 strict 模式下相当于在 window 上挂载元素