2023-08-02
JavaScript
00
请注意,本文编写于 477 天前,最后修改于 388 天前,其中某些信息可能已经过时。

目录

基本语法
背景介绍
为什么JavaScript慢?
一些概念
词法作用域
作用域相关概念
常见的作用域
严格模式
变量声明的6种方式
var和function声明变量的问题
let和const 块级作用域
class声明变量
import
数据类型
原始类型
类型判断
typeof
Object.prototype.toString
instanceof
类型转换
自动类型转换
类型转换规则表
对象
对象及属性的特性
检测对象属性
对象属性遍历
属性删除delete
序列化与反序列化
原型链
创建对象的几种方式
es6对象的扩展
es6对象新增的方法
数组
稀疏数组
es5数组方法
es6数组方法
类数组对象
模块化
函数
函数的属性
eval命令
手写new
手写call/apply
手写bind
函数式编程
函数参数默认值
rest参数
严格模式
箭头函数
参数尾逗号 catch参数省略
递归优化
todo

本文将系统梳理JavaScript知识体系,以分类总结、思维导图,问答的形式带你了解Javascript世界的全貌。

es5

es6

基本语法

背景介绍

了解一下JavaScript历史,有助于学习理解JavaScript。 推荐阅读 《JavaScript 语言的历史》

为什么JavaScript慢?

为什么WebApp没有原生APP体验好?

  1. JS诞生时需求很简单,为了解决表单校验而诞生(那个年代网络很慢,加载一个页面要很久,收集表单信息,一个选项填错就要等好久),所以设计的也很简单 单线程、动态语言、自动垃圾回收、无块级作用域
    • 单线程是指JS于DOM渲染互斥,这样设计就是为了简单, 试想一下多线程编程,要考虑并发性、事务、锁等概念。
    • 动态语言 运行时再确定类型,一方面容易产生问题,另一方面增加了解释器负担
    • 无块级作用域也是为了简单!但给开发人员带来一些问题如歧义、变量泄漏等,另一方面程序分为编译阶段(创建执行上下文、创建变量空间、可执行代码)和 运行阶段 两个过程
    • 自动垃圾回收也是因为开发更方便,代价是 比手动垃圾回收的语言要慢
  2. 解释型语言 这是Web开发互联特性决定的, 只是是解释型语言,边解释边运行
  3. 网络请求和加载 这一点也是Web特性决定的

一些概念

字符集

JavaScript 是采用 unicode字符集编写,但是只能支持一种编码 UCS-2UCS-2可以理解为2个字节的UTF-16编码 这也是JavaScript不能正确获取中文字符串长度的原因,es5及之前的字符串方法都有该问题, es6新增了许多字符串方法都支持4字节字符。

更多请阅读《JavaScript与字符集编码》

JavaScript区分大小写

标识符

标识符 必须以字母_$ 开头 后续字符可以是数字字母_$

可选的分号

  • JavaScript 并不是在所有的换行处都会添加分号,只有缺少了分号就无法运行的代码,JavaScript才会填充分号。通常来讲、如果一条语句以"(""[""/""+""-" 或 ` 开始,那么它极有可能和前一条语有合在一起解析。
  • return break continue 和随后的表达式之间不能有换行

注释

  • // 单行注释
  • /**/ 多行注释

垃圾回收

  • JavaScript解释器有自己的内存管理机制,可以自动对内存进行垃圾回收
  • 基于 代际假说和分代回收 的思想 更多请移步这里

语言特性
JavaScript是一门解释型、动态类型、弱类型、多范式编程、基于原型、单线程、跨平台的高级语言 更多解释

词法作用域
JavaScript采用词法作用域,通过阅读包含变量定义在内的数行源码就能知道变量所在的作用域

补充知识点,有两个例外

  • new Function('代码片段') 会在全局作用域下执行
  • eval别名使用 如var eva = eval; eva('代码片段') 会在全局作用域下执行

词法作用域

作用域相关概念

  • 作用域 即变量的查找范围,作用域控制着变量和函数的可见性和生命周期。
  • 词法作用域 变量的查找范围在定义时就决定了,通过阅读变量定义在内的数行代码就能知道变量的作用域。
  • 动态作用域 变量的查找范围在运行时(函数调用时)决定
  • 执行上下文 执行一个函数前的准备阶段。当执行一段代码时,JS引擎会对其进行编译,并创建执行上下文
    • JS有三大执行上下文: 全局执行上下文、函数执行上下文、Eval执行上下文
    • 执行上下文中包含 变量环境、词法环境(小型栈结构,栈底是函数最外层的变量)、outer指针、this等
    • 变量环境 es5定义的变量有声明提前的作用,就是在函数编译时创建的并装入变量环境中。
    • outer指针 指向作用域链上的上一个节点
  • 调用栈 调用栈是管理函数调用关系的一种数据结构,调用栈里面存储着执行上下文,通过指针移动管理执行上下文的创建和销毁。
    • 栈溢出的两种情况:1超过最大调用栈数量,2超过最大栈空间
  • 作用域链 由多个执行上下文构成的链表就叫做作用域链
  • 闭包 根据JavaScript词法作用域,内部函数可以访问外部函数中声明的变量,当调用一个外部函数返回一个内部函数后,即使外部函数已经执行结束了,但是内部函数引用外部函数的变量还保存在内存中,我们把这些变量的集合称为闭包

常见的作用域

  1. 全局作用域
    • window对象
    • 非严格模式下 顶层代码的执行就是在全局作用域下进行的
  2. 函数(局部)作用域
  3. 块级作用域
    • es5因为没有块级作用域存在的问题:1> 全局变量污染 2> 重复声明 3> 泄漏为全局变量 4> 循环中得到非预期的值 5> 声明提前可能导致内层变量覆盖外层变量
  4. this作用域
    • 默认绑定、隐式绑定、显示绑定、new绑定 推荐阅读 《Hi 你真的懂this吗》
  5. Script作用域 or Node 模块作用域
  6. 其他 Eval作用域 CacthBlock作用域 With Block 作用域 Closure 作用域
js
// 以下代码在非严格模式中都是全局作用域,因为变量被挂在到window对象下面 var a = 1; b = 2; function fa (){ c = 3; } fa(); // 变量 a b c 和 fa都被挂在到window下面

关于作用域 更多内容推荐阅读 《JS的9种作用域》

严格模式

  • 由于JS一开始缺少规划,存在的问题很多(无块级作用域、不声明直接赋值、动态绑定)。为了消除这些不安全的问题ES5推出了严格模式。
  • 它限制了一些不安全的JS语法,它有助于提供更强的代码规范和错误检测,从而改善代码质量和开发过程。

使用严格模式的好处

  • 消除不安全的行为,禁止使用不合理的语法
  • 错误检测
  • 引擎优化
  • 未来特性支持
  • 更好的代码质量

如何使用严格模式

  • 通过 "use strict" 开启严格模式 在代码块第一行 或 函数体第一行去开启
  • 一些es6语法默认开始严格模式
    • ESM <script type='module'>
    • 使用函数参数模式值、rest参数、扩展运算符, 就会开启严格模式
      • 不能再次使用'use-strict'声明
      • 这个严格模式与'use-strict'开启的严格模式有差异, 如可以使用with语句、8进制直接量
    • class语法模式是严格模式

具体内容

-影响
禁止的语法1. 禁止使用with语句
2. 禁止使用八进制直接量( 0开头的不行,0o可以)
3. 变量必须声明才能使用
4. 新增了一些保留字
对象的影响对象属性(可写性、枚举行、值、存取器)相关 ,
对象相关(可扩展行)的违规操作 都会报错,
如删除已封锁或冻结的对象的属性;对只读属性的修改(包括存取器) 如修改Setsize属性;
delete运算符后跟随非法的标识符(比如变量、函数、函数参数)时,将会抛出一个类型错误异常(包括声明的变量及核心的客户端属性)
对函数的影响1. 禁止同名参数;
2. 限制了对调用栈的检测能力;arguments.calleearguments.callee.caller
3. arguments不再有魔术行为
4. 标识符evalarguments当做关键字,它们的值是不能修改,也不能声明为变量
5. eval限制在当前作用域
6.尾调用优化只在严格模式下开启
对this的影响1. 全局代码块、函数调用(不是方法调用)this默认指向window
2. call/apply传入null/undefined保持原样不被转换为window

变量声明的6种方式

注: 无声明直接赋值(严格模式已禁用)

var和function声明变量的问题

js
// var和function声明变量的问题 var a = 1 var a;// 可以重复声明 a仍是1 console.log(b); undefined var b = 2; fa(); // 函数声明语句 变量声明和函数体均提前 function fa() { return 3 } console.log(bb); // function声明优先级高, 这里打印的是function var bb = 3; function bb() { return 4; } // 编译阶段虽然丢弃了var声明,但是在执行阶段赋值语句bb=3还存在,所以结果是3 bb // 3 // var和function都会被泄漏为全局变量 并且delete不能删除 window.fa;// fa function window.bb; // 3 // var在循环中可能会得到预期之外的值 for(var i = 0; i< 3; i++) { setTimeout(() => { console.log(i); // 打印三次 3 }) } console.log(i); // 3 泄漏为全局变量 var tmp = new Date(); function f() { console.log(tmp); // undefined // 5> 因声明提前导致的内层变量可能会覆盖外层变量。 if (false) { var tmp = 'hello world'; } } f(); // 总结 var的问题 1> 全局变量污染 2> 重复声明 3> 泄漏为全局变量 4> 循环中得到非预期的值 // 5> 因声明提前导致的内层变量可能会覆盖外层变量。

let和const 块级作用域

块作用域声明变量的方式不存在varfunction声明的问题

  • 不存在变量提升
  • 不能重复声明
  • 全局变量污染,泄漏为全局对象,循环中预期之外的值,

块级作用域有个特性 暂时性死区, 它也使得typeof不是100%安全的操作了,它的存在 避免了内层变量覆盖外层变量的问题。

注: 块级作用域内避免使用函数声明语句, 原因点这里

const

  • 声明一个只读常量,这个常量是 变量的地址 不能发生改变 即不能重新给变量赋值,但可以修改变量的属性

class声明变量

  • 存在暂时性死区特性
  • 可以重复声明

import

  • 声明提前 es6的模块化设计是尽可能静态化, 使得编译时就能确定模块的依赖关系,以及输入和输出的变量, 所有 必须 声明提前(不存在暂时性死区)
  • 不能重复声明

数据类型

原始类型

  • numberstringbooleannullundefinedBigInt Symbol
  • 对象类型 object(狭义的对象) array function
  • JS对象分为值类型和引用类型,原始类型的数据都是值类型,对象类型的数据都是引用类型

包装对象

  • JavaScript会将基本类型在必要的时候转换为其类包装对象,来访问齐包装对象上的方法属性等。
  • 同样在必要时会将包装对象转换成原始值
js
var s = 'hello world'; var word = s.substring(s.indexOf(' ')+1, s.length);

null与undefined的区别

  • undefinednull 都是基本数据类型
  • undefined表示未定义,一般变量声明了但没有赋值时值为undefinednull代表的含义是空对象,表示程序运行预期之中的空值。
  • null是JavaScript保留字,而undefined不是,这意味着可以使用undefined来作为一个变量名,这也是很危险的,通常通过void 0 或函数参数默认值 来安全的获取undefined
  • typeof null等于 "object" null == undefined null !== undefined
  • 转number null --> 0 undefined --> NaN
  • null 是原型链的终点 undefined 会触发函数默认值

数值的表示

js
0b1010; // 二进制表示 0o12; // 八进制 10; // 十进制 0xa; // 十六进制 // 科学计数法 [digits][.digits][(E|e)[+|-]digits] 1e+1

为什么0.1+0.2 > 0.3

  • JavaScript数值精度采用64位浮点数表示法。
    • 第一位符号位 0表示正数 1表示负数
    • 2~12(共11位)表示指数
    • 第13到64位(共52位) 小数部分(即有效数字)
  • 十进制转二进制存在有效数字变多及无限循环的情况,低位数字溢出丢弃,这就导致了数值精度问题

更多请阅读《JS二进制运算与浮点数存储》

类型判断

  • typeof可以判断基本类型和函数类型
  • Object.prototype.toString 可以判断对象类型和数组类型
    • es6 使用 Reflect.toString
  • instanceof

typeof

js
typeof 123 // "number" typeof '123' // "string" typeof false // "boolean" typeof undefined // "undefined" typeof null // "object" typeof 12n // "bigint" typeof Symbol() // "symbol" typeof [] // "object" typeof {} // "object" typeof (() => {}) // "function" // typeof 引用类型 除 函数外 都是 "object" // 判断 null var isNull = (variable) => typeof variable === 'object' && !variable; // 判断 NaN NaN == NaN; // false NaN === NaN; // false // 全局变量 isNaN 会将参数转换为nuber类型在判断 // es6 Math.isNaN 如果不是number类型一律返回false var isNaN = (variable) => typeof variable === 'number' && variable !== variable; // 判断Infinity // 全局变量 isFinite 会将其转换为 number 类型 在判断是否是 Infinity, 不是返回false // es6 Math.isFinite 不会进行类型转换,如果不是 number类型 一律返回 false // 判断 0 和 -0 需要借助Infinity 或使用es6 Object.is 0 == -0 // true 0 === -0 // true 1/0; // Infinity 1/-0; // -Infinity Object.is(0, -0); // false

Object.prototype.toString

js
[ null, // [object Null] undefined, // [object Undefined] 123, // [object Number] false, // [object Boolean] 'abc', // [object String] 12n, // [object BigInt] Symbol(), // [object Symbol] [], // [object Array] {}, // [object Object] ()=>{}, // [object Function] /^a/, // [object RegExp] new Date(), // [object Date] new Set(), // [object Set] new Map(), // [object Map] new WeakSet(), // [object WeakSet] new WeakMap(), // [object WeakMap] ].forEach( (variable) => console.log( Object.prototype.toString.call(variable) ) )

instanceof

js
// L instanceof R 判断 L是否是 R的实例 function instanceOf(L, R) { while (true) { if (L.__proto__ === null) return false; if (L.__proto__ === R.prototype) { return true } else { L = L.__proto__; } } } // 因为JS是基于原型的继承, 所以以下结果为true [] instanceof Object; /^s/ instanceof Object; (() => {}) instanceof Object; {} instanceof Function; Object instanceof Function; // instanceof在判断iframe中对象时存在缺陷 // iframe.contentWindow.arr instanceof Array // false

类型转换

JavaScript 是一门动态类型的语言,在运行时,变量可能发生类型转换

强制类型转换

  • Number()
  • Boolean()
  • String()

自动类型转换

  • 转换为boolean值
    • !
    • 三元运算符
    • if语句会转换为falsy类型
    • 以下类型都是falsy类型false 0 -0 0n "" falsy NaN null undefined
  • 转换为字符串
    • +字符串连接,a + b 如果a 是字符串那么b会转换成字符串再于a 进行运算
  • 转换为数值
    • + - * /(加号排除字符串连接的情况)

类型转换规则表

image.png

  • {}(任意对象) 转字符串

    • 先调用toString()方法如果返回一个原始值 则使用String()包裹后返回
    • 如果不是原始值则调用valueOf()方法,如果返回原始值则使用String()包裹后返回
    • 如果不是原始值,就报错
  • {}(任意对象) 转数值

    • valueOf()方法如果返回一个原始值 则使用Number()包裹后返回
    • 如果不是原始值则调用toString()方法,如果返回原始值则使用Number()包裹后返回
    • 如果不是原始值,就报错
  • Symbol类型

    • 不能与其他类型进行运算和转换
    • 但可以转换为字符串
    • 不能new
  • BigInt

    • 不能与number类型进行运算, 只能与BigInt类型进行运算
    • number 转 bigint 使用 BigInt()
    • bigint 转 number 使用 Number()
    • 也可以转换为 string boolean类型 对应 String() Boolean()
    • 不能使用Math下面的es5方法

对象

对象及属性的特性

  • 对象的属性 有四个特性
    • 值 或 存取器 两者互斥 存取器可以实现数据的劫持,
    • 可写性
    • 可枚举行
    • 可配置行
    • 通过Object.definePrototype,Object.defineProperties可以设置对象属性
    • Object.getOwnPrototypeDescriptor() 可以获取某个属性的描述对象
  • 对象有个扩展性

控制对象状态的方法

  1. 使用Object.preventExtensions()可以防止对象扩展,即不能新增属性); 使用Object.isExtensible()判断对象是否可以扩展
  2. 使用Object.seal()可以禁用对象配置 ,除了不能添加新属性,也不可以修改它的可配置性和可枚举性,存取性与值特性不能转换,但可写性可以从true配置成false);使用Object.isSeal()可以检测对象是否禁用配置
  3. 使用Object.freeze()可以冻结对象, 即对象不能扩展,所有对象属性不能配置且不能修改值;使用Object.isFrozen()可以判断一个对象是否被冻结

检测对象属性

标题自身可枚举属性自身不可枚举属性自身Symbol属性
in
Object.prototype.hasOwnProperty()
Object.hasOwn()更安全一些
Object.prototype.propertyIsEnumerable()-

注: symbol属性也有 属性的四特征

对象属性遍历

-自身可枚举属性自身不可枚举属性自身Symbol属性继承的可枚举属性
for ... in--
Object.assign()--
JSON.stringify()---
Object.keys()
Object.values()
Object.entries()
---
Object.getOwnPrototypeNames()--
Object.getOwnPropertySymbols()---
Reflect.ownKeys()-

总结:

  • es6 新增的方法可以遍历对象symbol属性 如 Object.assign() Reflect.ownKeys() Object.getOwnPropertySymbols()
  • 只有 for ... in 会遍历继承的可枚举属性

属性删除delete

  • 可扩展的对象属性 可以删除
  • 未声明变量直接赋值 可以删除

注: delete删除不存在的属性返回true (严格模式下报错)

以下情况 delete语句无效(非严格模式返回false, 严格模式下报错)

  • var声明的变量不能删除
  • 不可配置的属性不能删除

序列化与反序列化

JSON.stringify的副作用

image.png

  • 上图可清晰的看到JSON.stringify的副作用,蓝色表示属性值发生了转换,红色表示属性丢失
  • 丢失的属性: undefined、symbol、function、不可枚举属性
  • 发生转换的属性: NaN Infinity -Infinity -0 Map Set WeakMap WeakSet get()

《JavaScript权威指南第6版》对JSON方法序列化的定义如下 image.png

原型链

extend.png

  • Object.setPrototypeOf(b, a) 设置对象b的原型为a, 即 b.proto === a
  • Object.getProtutypeOf(b) 获取对象b的原型
  • 查询属性会体现到继承的存在,而设置属性不会

创建对象的几种方式

  • 字面量 var obj = {}
  • Object()new Object()
  • Object.create()
    • a = Object.create(b)a.__proto__ = b
    • a = Object.create(b, prototype)a.__proto__ = b a.prototype = prototype
  • Object.create(null) 创建一个干净的对象,没有Object的实例方法

es6对象的扩展

  1. 属性的简洁表示法
  2. 属性名表达式
  3. 方法的name属性
  4. AggregateError 错误集合,配合Promise.any()方法使用
  5. Error 对象的 cause 属性 在生成错误是添加报错原因的描述

es6对象新增的方法

  1. Object.is 能识别 0-0NaN
  2. Object.assgin(target, source1, source2, ...) 用于对象的合并,将原对象的所有自身可枚举属性复制到目标对象。它的特征如下
    • 支持多个参数
    • 如果存在同名属性后者会覆盖前者
    • 如果第一个参数target不能是 null 或undfined,如果是其他基本类型会先换成对象(Object.create(target.prototype))
    • 如果source参数是string会转换成字符数组的形式
    • 如果source参数是其他基本类型 则无效果Object.assign({}, null, undefined, 12n, 23, false, Symbol.for('a'))
    • 如果source参数是数组会视为对象处理
    • 浅层copy, 只copy自身可枚举 自身Symbol属性
    • JSON.stringify一样将get存取器转换为值
  3. proto Object.getProtypeOfObject.setProtorypeOf
  4. 新增Object.keys()Object.values()Object.entries()
  5. Object.fromEntries() 它是Object.entries()的逆操作

数组

JavaScript数组有以下特征

  • 数组是无类型的
  • 数组的索引是基于0的32位数值
  • 数组是动态的
  • 数组可能是稀疏的

注: 数组也是一种特殊的对象,数组的索引即对象属性key

数组的两个特殊行为

  • 往数组中添加元素、删除元素会动态修改length属性
  • 设置length属性可以动态增加删除数组元素

稀疏数组

稀疏数组是指数组的索引不连续, 即数组length值大于元素个数。

什么情况下会产生稀疏数组?

js
var a = Array(5); var b = [,,3]; var c = []; c.length = 5; // 以上都会产生稀疏数组 // 注es6明确将数组空位明确为undefined 故es6 方法不会产生稀疏数组 var d = Array.from({length: 2}); [...['a',,'b']]; // [ "a", undefined, "b" ]

稀疏数组有什么影响呢?

  • forEach(), filter(), reduce(), every()some()都会跳过空位
  • map()会跳过空位,但会保留这个值
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。

因为数组也是对象,使用Object.seal()封锁或Object.freeze()冻结数组后,则不能通过修改length属性删除元素

es5数组方法

数组操作7个方法 会改变当前对象

  1. push 尾部插入元素
  2. pop 尾部删除元素
  3. unshift 顶部插入元素
  4. shift 顶部删除元素
  5. splice 可以删除添加元素,返回删除的元素
  6. reverse 翻转
  7. sort 排序

数组遍历

  1. forEach
  2. map
  3. filter
  4. every / some
  5. reduce / reduceRight
  6. indexOf / lastIndexOf

其他数组方法

  1. join 转字符串
  2. slice 复制数组

因为数组也是对象,也可以使用对象的方法 如toString valueOf

es6数组方法

静态方法

  1. Array.from 将一个可遍历的对象转换为数组, 它还提供了类似map的功能(第二个参数)
  2. Array.of 弥补Array()因参数不同创建数组实例的行为差异

实例方法

  1. copeWithin() Array.prototype.copyWithin(target, start = 0, end = this.length) 将索引[start, end)的元素复制到索引为target开始的元素上, 只能复制一次。
  2. find()findIndex()findLast()findLastIndex()
  3. fill(item, start=0, end=this.length) 填充元素, 第二个第三个参数可以指定填充开始结束位置 [start, end)
  4. keys()values()entries()
  5. includes()
  6. flat()flatMap() 将嵌套的数组拉平
  7. at() 让数组支持负的索引, 也可以用于字符串
  8. toReserved()toSorted()toSpliced()with() 这些方法 不改变原数组而是返回一个原数组的copy, 目前处于提案状态
  9. group()groupToMap() 对数组进行分组 返回一个对象或map

类数组对象

  • NodeList arguments string
  • 可以借助 Array.prototype.[method].call使用数组原型的方法
  • 或者通过 Array.from() or 扩展运算符 转换为数组对象

类数组大都实现了 Iterator 接口,因此可以使用扩展运算符、解构赋值等。

模块化

关于模块化请移步这里《JavaScript模块化》

函数

函数定义的两种方法

-示例特性
函数声明语句function(){}声明提前、函数体提前
函数定义语句var a = function(){}声明提前

Function Object Number Boolean使用new和不使用new效果一样

函数的属性

  • 函数本身也是一种对象,即可以挂在属性,用作标识、计算缓存等
  • name属性
  • length属性 受默认值、rest参数影响 用途:方法重载

运行时的属性

  • arguments 获取函数参数
    • 类数组对象
    • 严格模式下不能修改arguments的值
  • arguments.callee属性 返回它所对应的原函数
    • 用途:callee在匿名函数中可以通过callee递归调用自身
    • 严格模式下已禁止
  • arguments.callee.caller属性 它指向当前正在执行函数的函数 非标准

eval命令

  • eval命令接受一个字符串作为参数,并将这个字符串当作语句执行。
  • 非严格模式下eval在当前作用域内执行
js
// eval 在当前作用域内执行 eval('var a = 2'); a; // 2 // 严格模式下会创建eval作用域,以避免影响到外部作用域 (function f() { 'use strict'; eval('var foo = 123'); console.log(foo); // ReferenceError: foo is not defined })()
  • eval 别名调用使用全局作用域 (原因,静态分析无法分辨执行的是eval)
js
var a = 1; function f() { var a = 2; var e = eval; e('console.log(a)'); } f() // 1

手写new

js
function myNew(Ctor, ...args) { // 1. 创建一个新的空对象 // 2. 将这个空对象的__proto__指向构造函数的原型 const obj = Object.create(Ctor.prototype) // 3. 将this指向空对象 const result = Ctor.apply(obj, args); // 对构造函数返回值做判断,然后返回对应的值 return result instanceof Object ? result : obj; }

手写call/apply

js
Function.prototype.myCall = function(context, ...args) { context.that = this; const result = context.that(...args); delete context.that return result; } Function.prototype.myApply = function(context, args) { context.that = this; const result = context.that(...args); delete context.that return result; }

手写bind

js
// 简单版本 Function.prototype.myBind = function(context, ...args) { const that = this; return function() { that.call(context, ...args, ...arguments); } } // 支持new的版本 Function.prototype.myBind2 = function(context, ...args) { const that = this; const bound = function() { // that.call(new.target ? this : context, ...args, ...arguments); that.call(this instanceof bound ? this : context, ...args, ...arguments); } bound.prototype = this.prototype; return bound; }

函数式编程

  • 柯里化
  • 偏函数
  • 高阶函数
  • 纯函数
js
// 柯里化 function curring(fn, m) { return function(n) { return fn.call(this, m, n) } }

函数参数默认值

  • 惰性求值
  • 默认值的位置 后面不能有必须参数
  • 默认值有个单独的作用域
  • 应用: 设置不能省略的参数

rest参数

  • rest参数只能出现在最后

严格模式

  • 只要设置了函数参数默认值、rest参数、解构赋值 函数内部就是严格模式包括函数参数

箭头函数

  • 没有this对象或者说this指向外层代码块, 对箭头函数执行bind 无效,
  • 由此类推也没有 supernew.targetarguments
  • 不能使用newyield
  • 不适合的场景
    • 对象的属性最好不要使用箭头函数, 因为this指向外层(可能是全局作用域)
    • 需要动态this的时候 如dom事件监听

参数尾逗号 catch参数省略

递归优化

请移步这里

todo


友情提示: 本文正在撰写中

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!