我只想面个CV工程师,面试官偏偏让我挑战造火箭工程师,加上今年这个情况更是前后两难,但再难苟且的生活还要继续,饭碗还是要继续找的。在最近的面试中我一直在总结,每次面试回来也都会复盘,下面是我这几天遇到的面试知识点。但今天主题是标题所写的66条JavaScript知识点,由浅入深,整理了一周,每(zhěng)天(lǐ)整(bù)理( yì)10条( qiú)左(diǎn)右(zàn), 希望对正在找工作的小伙伴有点帮助,文中如有表述不对,还请指出。
网络:
另外更全面的面试题集我也在整理中,先给个预告图:
下面进入正题:
JavaScript一共有8种数据类型,其中有7种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol(es6新增,表示独一无二的值)和BigInt(es10新增);
1种引用数据类型——Object(Object本质上是由一组无序的名值对组成的)。里面包含 function、Array、Date等。JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。
原始数据类型:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
在 JS 中类型转换只有三种情况,分别是:
此外还有一些操作符会存在隐式转换,此处不做展开,可自行百度00
(1)typeof
typeof 对于原始类型来说,除了 null 都可以显示正确的类型
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型,所以想判断一个对象的正确类型,这时候可以考虑使用 instanceof
(2)instanceof
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
可以看出直接的字面量值判断数据类型,instanceof可以精准判断引用数据类型(Array,Function,Object),而基本数据类型不能被instanceof精准判断。
我们来看一下 instanceof 在MDN中的解释:instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。其意思就是判断对象是否是某一数据类型(如Array)的实例,请重点关注一下是判断一个对象是否是数据类型的实例。在这里字面量值,2, true ,'str'不是实例,所以判断值为false。
(3)constructor
js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函 数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构 造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。
涉及知识点:
详细资料可以参考:《标准内置对象的分类》
《JS 所有内置对象属性和方法汇总》
对于 undeclared 变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。但是我们可以使用 typ eof 的安全防范机制来避免报错,因为对于 undeclared(或者 not defined )变量,typeof 会返回 "undefined"。
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义, null 代表的含义是空对象(其实不是真的对象,请看下面的注意!)。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 js 中不是一个保留字,这意味着我们可以使用 undefined 来作为一个变量名,这样的做法是非常危险的,它 会影响我们对 undefined 值的判断。但是我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等 号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
详细资料可以参考:
《JavaScript 深入理解之 undefined 与 null》
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。
作用域链: 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前 端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找。
作用域链的创建过程跟执行上下文的建立有关....
详细资料可以参考:《JavaScript 深入理解之作用域链》
详细资料可以参考:《JavaScript 深入理解之对象创建》
详细资料可以参考:《JavaScript 深入理解之继承》
特点:
《JavaScript 深入理解之原型与原型链》
也可以看看我写的:「前端料包」深入理解JavaScript原型和原型链
闭包是指有权访问另一个函数作用域内变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以 访问到当前函数的局部变量。
闭包有两个常用的用途。
其实闭包的本质就是作用域链的一个特殊的应用,只要了解了作用域链的创建过程,就能够理解闭包的实现原理。
DOM 指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局) 对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 locati on 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对 象的子对象。
《DOM, DOCUMENT, BOM, WINDOW 有什么区别?》
《Window 对象》
《DOM 与 BOM 分别是什么,有何关联?》
《JavaScript 学习总结(三)BOM 和 DOM 详解》
事件 是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
《一个 DOM 元素绑定多个事件时,先执行冒泡还是捕获》
事件委托 本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到 目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
使用事件代理我们可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理我们还可以实现事件的动态绑定,比如说新增了一个子节点,我们并不需要单独地为它添加一个监听事件,它所发生的事件会交给父元素中的监听函数来处理。
《JavaScript 事件委托详解》
当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在“当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。
事件传播有三个阶段:
当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在捕获阶段,事件从window开始,一直到触发事件的元素。window----> document----> html----> body \---->目标元素
假设有如下的 HTML 结构:
对应的 JS 代码:
addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。如果单击child元素,它将分别在控制台上打印window,document,html,grandparent和parent,这就是事件捕获。
事件冒泡刚好与事件捕获相反,当前元素---->body \----> html---->document \---->window。当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达window为止。
假设有如下的 HTML 结构:
对应的JS代码:
addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。如果单击child元素,它将分别在控制台上打印child,parent,grandparent,html,document和window,这就是事件冒泡。
(1)创建新节点
(2)添加、移除、替换、插入
(3)查找
(4)属性操作
《DOM 概述》
《原生 JavaScript 的 DOM 操作汇总》
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
我了解到的几种方式是:
《JS 延迟加载的几种方式》
《HTML 5 <script> async 属性》
我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。
由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。
后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。
现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
《Javascript 模块化编程(一):模块的写法》
《前端模块化:CommonJS,AMD,CMD,ES6》
《Module 的语法》
js 中现在比较成熟的有四种模块加载方案:
它们之间的主要区别有两个方面。
《前端模块化,AMD 与 CMD 的区别》
,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
详细资料可以参考:《requireJS 的用法和原理分析》
《requireJS 的核心原理是什么?》
《requireJS 原理分析》
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
js代码执行过程中会有很多任务,这些任务总的分成两类:
当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。,我们用导图来说明:
我们解释一下这张图:
那主线程执行栈何时为空呢?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
以上就是js运行的整体流程
需要注意的是除了同步任务和异步任务,任务还可以更加细分为macrotask(宏任务)和microtask(微任务),js引擎会优先执行微任务
面试中该如何回答呢?下面是我个人推荐的回答:
最后可以用下面一道题检测一下收获:
《浏览器事件循环机制(event loop)》
《详解 JavaScript 中的 Event Loop(事件循环)机制》
《什么是 Event Loop?》
《这一次,彻底弄懂 JavaScript 执行机制》
arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,如:forEach、reduce、filter和map。
注意:箭头函数中没有arguments对象。
当我们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。使用rest语法,可以解决这个问题。
这会自动将所有参数值放入数组中。
原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:
《深入理解 V8 的垃圾回收原理》
《JavaScript 中的垃圾回收》
《JavaScript 内存泄漏教程》
《4 类 JavaScript 内存泄漏及如何避免》
《杜绝 js 中四种内存泄漏类型的发生》
《javascript 典型内存泄漏及 chrome 的排查方法》
ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件 只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)
ECMAScript说什么JavaScript就得做什么!
JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!
——突然感觉JavaScript好没有尊严,为啥要搞个人出来约束自己,
那个人被创造出来也好委屈,自己被创造出来完全是因为要约束JavaScript。
暂存死区
const
在本例中,ES5 版本中有function(){}声明和return关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()括号,不需要 return 语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回。
箭头函数不能访问arguments对象。所以调用第一个getArgs函数会抛出一个错误。相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。
箭头函数没有自己的this值。它捕获词法作用域函数的this值,在此示例中,addAll函数将复制computeResult 方法中的this值,如果我们在全局作用域声明箭头函数,则this值为 window 对象。
类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。
重写方法并从另一个类继承。
所以我们要怎么知道它在内部使用原型?
《ECMAScript 6 实现了 class,对 JavaScript 前端开发有什么意义?》
《Class 的基本语法》
模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。
在 ES5 中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦:
在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。
在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用+运算符。在模板字符串s中,我们可以使用${expr}嵌入一个表达式,这使其比 ES5 版本更整洁。
对象析构是从对象或数组中获取或提取值的一种新的、更简洁的方法。假设有如下的对象:
从对象获取属性,早期方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新变量。假设我们有一个大对象,它有很多属性和方法,用这种方法提取属性会很麻烦。
使用解构方式语法就变得简洁多了:
我们还可以为属性取别名:
当然如果属性值为 undefined 时,我们还可以指定默认值:
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
我们可以使用Set构造函数创建Set实例。
我们可以使用add方法向Set实例中添加一个新值,因为add方法返回Set对象,所以我们可以以链式的方式再次使用add。如果一个值已经存在于Set对象中,那么它将不再被添加。
我们可以使用has方法检查Set实例中是否存在特定的值。
我们可以使用size属性获得Set实例的长度。
可以使用clear方法删除 Set 中的数据。
我们可以使用Set对象来删除数组中重复的元素。
另外还有WeakSet, 与 Set 类似,也是不重复的值的集合。但是 WeakSet 的成员只能是对象,而不能是其他类型的值。WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet对该对象的引用。
Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程形成对比,面向对象中应用程序的状态通常与对象中的方法共享和共处。
函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。当然,编程范式的其他示例也包括面向对象编程和过程编程。
函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但如果不熟悉它以及与之相关的常见模式,函数式的代码也可能看起来更密集杂乱,并且 相关文献对新人来说是不好理解的。
高阶函数只是将函数作为参数或返回值的函数。
这样的函数也称之为第一级函数(First-class Function)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)。这样的多重身份让JavaScript的函数变得非常重要。
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
JavaScript的深浅拷贝一直是个难点,如果现在面试官让我写一个深拷贝,我可能也只是能写出个基础版的。所以在写这条之前我拜读了收藏夹里各路大佬写的博文。具体可以看下面我贴的链接,这里只做简单的总结。
浅拷贝的实现方式:
深拷贝的实现方式:
深拷贝的终极探索(99%的人都不知道)
call 函数的实现步骤:
apply 函数的实现步骤:
bind 函数的实现步骤:
《JavaScript 深入之 call 和 apply 的模拟实现》
这个问题如果你在掘金上搜,你可能会搜索到类似下面的回答:
说实话,看第一遍,我是不理解的,我需要去理一遍原型及原型链的知识才能理解。所以我觉得MDN对new的解释更容易理解:
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:
接下来我们看实现:
上面的代码相信不用解释,大家都懂。我们来看最后一行带new关键字的代码,按照上述的1,2,3,4步来解析new背后的操作。
第一步:创建一个简单空对象
第二步:链接该对象到另一个对象(原型链)
第三步:将步骤1新创建的对象作为 this 的上下文
第四步:如果该函数没有返回对象,则返回this
需要注意的是如果 Dog() 有 return 则返回 return的值
接下来我们将以上步骤封装成一个对象实例化方法,即模拟new的操作:
回调函数是一段可执行的代码段,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码。
在JavaScript中函数也是对象的一种,同样对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外一个函数,这个作为参数的函数就是回调函数。
在本例中,我们等待id为btnAdd的元素中的click事件,如果它被单击,则执行clickCallback函数。回调函数向某些数据或事件添加一些功能。
回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个事件存在依赖性:
这就是典型的回调地狱,以上代码看起来不利于阅读和维护,事件一旦多起来就更是乱糟糟,所以在es6中提出了Promise和async/await来解决回调地狱的问题。当然,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return。接下来的两条就是来解决这些问题的,咱们往下看。
Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。
一个 Promise有以下几种状态:
这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 fulfilled/rejected 后,就不能再次改变。可能光看概念大家不理解Promise,我们举个简单的栗子;
假如我有个女朋友,下周一是她生日,我答应她生日给她一个惊喜,那么从现在开始这个承诺就进入等待状态,等待下周一的到来,然后状态改变。如果下周一我如约给了女朋友惊喜,那么这个承诺的状态就会由pending切换为fulfilled,表示承诺成功兑现,一旦是这个结果了,就不会再有其他结果,即状态不会在发生改变;反之如果当天我因为工作太忙加班,把这事给忘了,说好的惊喜没有兑现,状态就会由pending切换为rejected,时间不可倒流,所以状态也不能再发生变化。
上一条我们说过Promise可以解决回调地狱的问题,没错,pending 状态的 Promise 对象会触发 fulfilled/rejected 状态,一旦状态改变,Promise 对象的 then 方法就会被调用;否则就会触发 catch。我们将上一条回调地狱的代码改写一下:
其实Promise也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。
promise手写实现,面试够用版:
「硬核JS」深入了解异步解决方案
【翻译】Promises/A+规范
Iterator是理解第61条的先决知识,也许是我IQ不够????,Iterator和Generator看了很多遍还是一知半解,即使当时理解了,过一阵又忘得一干二净。。。
Iterator(迭代器)是一种接口,也可以说是一种规范。为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator语法:
迭代器的遍历方法是首先获得一个迭代器的指针,初始时该指针指向第一条数据之前,接着通过调用 next 方法,改变指针的指向,让其指向下一条数据 每一次的 next 都会返回一个对象,该对象有两个属性
Iterator 的作用有三个:
遍历过程:
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
Generator函数可以说是Iterator接口的具体实现方式。Generator 最大的特点就是可以控制函数的执行。
上面这个示例就是一个Generator函数,我们来分析其执行过程:
Generator 函数一般见到的不多,其实也于他有点绕有关系,并且一般会配合 co 库去使用。当然,我们可以通过 Generator 函数解决回调地狱的问题。
async/await是一种建立在Promise之上的编写异步或非阻塞代码的新方法,被普遍认为是 JS异步操作的最终且最优雅的解决方案。相对于 Promise 和回调,它的可读性和简洁度都更高。毕竟一直then()也很烦。
async 是异步的意思,而 await 是 async wait的简写,即异步等待。
一个函数如果加上 async ,那么该函数就会返回一个 Promise
相比于 Promise,async/await能更好地处理 then 链
现在分别用 Promise 和async/await来实现这三个步骤的处理。
使用Promise
使用async/await
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,优雅整洁,几乎跟同步代码一样。
优缺点:
async/await的优势在于处理 then 的调用链,能够更清晰准确的写出代码,并且也能优雅地解决回调地狱问题。当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
「硬核JS」深入了解异步解决方案
以上21~25条就是JavaScript中主要的异步解决方案了,难度是有的,需要好好揣摩并加以练习。
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
实现 instanceof:
函数防抖 是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
函数节流 是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
详细资料可以参考:
《轻松理解 JS 函数节流和函数防抖》
《JavaScript 事件节流和事件防抖》
《JS 的防抖与节流》
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
S – Single Responsibility Principle 单一职责原则
O – OpenClosed Principle 开放/封闭原则
L – Liskov Substitution Principle 里氏替换原则
I – Interface Segregation Principle 接口隔离原则
D – Dependency Inversion Principle 依赖倒转原则
外观模式是最常见的设计模式之一,它为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。简而言之外观设计模式就是把多个子系统中复杂逻辑进行抽象,从而提供一个更统一、更简洁、更易用的API。很多我们常用的框架和库基本都遵循了外观设计模式,比如JQuery就把复杂的原生DOM操作进行了抽象和封装,并消除了浏览器之间的兼容问题,从而提供了一个更高级更易用的版本。其实在平时工作中我们也会经常用到外观模式进行开发,只是我们不自知而已。
场景
优点
缺点
是为一个对象提供一个代用品或占位符,以便控制对它的访问
假设当A 在心情好的时候收到花,小明表白成功的几率有 60%,而当A 在心情差的时候收到花,小明表白的成功率无限趋近于0。小明跟A 刚刚认识两天,还无法辨别A 什么时候心情好。如果不合时宜地把花送给A,花 被直接扔掉的可能性很大,这束花可是小明吃了7 天泡面换来的。但是A 的朋友B 却很了解A,所以小明只管把花交给B,B 会监听A 的心情变化,然后选 择A 心情好的时候把花转交给A,代码如下:
场景
ES6 的 proxy 阮一峰Proxy
优点
代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用
代理对象可以扩展目标对象的功能;通过修改代理对象就可以了,符合开闭原则;
缺点
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。
场景
如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择
将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式;
需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性
优点
创建对象的过程可能很复杂,但我们只需要关心创建结果。
构造函数和创建者分离, 符合“开闭原则”
一个调用者想创建一个对象,只要知道其名称就可以了。
扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
缺点
添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度
什么时候不用
当被应用到错误的问题类型上时,这一模式会给应用程序引入大量不必要的复杂性.除非为创建对象提供一个接口是我们编写的库或者框架的一个设计上目标,否则我会建议使用明确的构造器,以避免不必要的开销。
由于对象的创建过程被高效的抽象在一个接口后面的事实,这也会给依赖于这个过程可能会有多复杂的单元测试带来问题。
顾名思义,单例模式中Class的实例个数最多为1。当需要一个对象去贯穿整个系统执行某些任务时,单例模式就派上了用场。而除此之外的场景尽量避免单例模式的使用,因为单例模式会引入全局状态,而一个健康的系统应该避免引入过多的全局状态。
实现单例模式需要解决以下几个问题:
如何简便的访问Class的唯一实例?
Class如何控制实例化的过程?
如何将Class的实例个数限制为1?
我们一般通过实现以下两点来解决上述问题:
Javascript中单例模式可以通过以下方式实现:
实现的关键点有:
我们可以验证下单例对象是否创建成功:
场景例子
定义命名空间和实现分支型方法
vuex 和 redux中的store
优点
划分命名空间,减少全局变量
增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
且只会实例化一次。简化了代码的调试和维护
缺点
策略模式简单描述就是:对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。把它们一个个封装起来,并且使它们可以互相替换
场景例子
如果在一个系统里面有许多类,它们之间的区别仅在于它们的'行为',那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
一个系统需要动态地在几种算法中选择一种。
表单验证
优点
利用组合、委托、多态等技术和思想,可以有效的避免多重条件选择语句
提供了对开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,理解,易于扩展
利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的代替方案
缺点
会在程序中增加许多策略类或者策略对象
要使用策略模式,必须了解所有的strategy,必须了解各个strategy之间的不同点,这样才能选择一个合适的strategy
如果你看到这,ES6中的迭代器 Iterator 相信你还是有点印象的,上面第60条已经做过简单的介绍。迭代器模式简单的说就是提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象的内部表示。
迭代器模式解决了以下问题:
一个迭代器通常需要实现以下接口:
为Javascript的数组实现一个迭代器可以这么写:
验证一下迭代器是否工作:
比如我们实现一个 Range 类用于在某个数字区间进行迭代:
验证一下:
观察者模式又称发布-订阅模式(Publish/Subscribe Pattern),是我们经常接触到的设计模式,日常生活中的应用也比比皆是,比如你订阅了某个博主的频道,当有内容更新时会收到推送;又比如JavaScript中的事件订阅响应机制。观察者模式的思想用一句话描述就是:被观察对象(subject)维护一组观察者(observer),当被观察对象状态改变时,通过调用观察者的某个方法将这些变化通知到观察者。
观察者模式中Subject对象一般需要实现以下API:
unsubscribe(): 接收一个观察者observer对象,使其取消订阅自己
fire(): 触发事件,通知到所有观察者
用JavaScript手动实现观察者模式:
验证一下订阅是否成功:
验证一下取消订阅是否成功:
场景
优点
支持简单的广播通信,自动通知所有已经订阅过的对象
目标对象与观察者之间的抽象耦合关系能单独扩展以及重用
增加了灵活性
观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。
缺点
在中介者模式中,中介者(Mediator)包装了一系列对象相互作用的方式,使得这些对象不必直接相互作用,而是由中介者协调它们之间的交互,从而使它们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用,保证这些作用可以彼此独立的变化。
中介者模式和观察者模式有一定的相似性,都是一对多的关系,也都是集中式通信,不同的是中介者模式是处理同级对象之间的交互,而观察者模式是处理Observer和Subject之间的交互。中介者模式有些像婚恋中介,相亲对象刚开始并不能直接交流,而是要通过中介去筛选匹配再决定谁和谁见面。
场景
聊天室成员类:
聊天室类:
测试一下:
优点
使各对象之间耦合松散,而且可以独立地改变它们之间的交互
中介者和对象一对多的关系取代了对象之间的网状多对多的关系
如果对象之间的复杂耦合度导致维护很困难,而且耦合度随项目变化增速很快,就需要中介者重构代码
缺点
访问者模式 是一种将算法与对象结构分离的设计模式,通俗点讲就是:访问者模式让我们能够在不改变一个对象结构的前提下能够给该对象增加新的逻辑,新增的逻辑保存在一个独立的访问者对象中。访问者模式常用于拓展一些第三方的库和工具。
访问者模式的实现有以下几个要素:
Receiving Object:接收对象,拥有一个 accept() 方法
visit(receivingObj):用于Visitor接收一个Receiving Object
accept(visitor):用于Receving Object接收一个Visitor,并通过调用Visitor的 visit() 为其提供获取Receiving Object数据的能力
简单的代码实现如下:
验证一下:
场景
对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
优点
符合单一职责原则
优秀的扩展性
灵活性
缺点
具体元素对访问者公布细节,违反了迪米特原则
违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
具体元素变更比较困难
JavaScript设计模式es6(23种)
《前端面试之道》
《JavaScript 设计模式》
《JavaScript 中常见设计模式整理》
前端是个大杂烩,各种框架层出不穷,但万变不离JS,务实基础才是根本,如果你觉得本文对你有所帮助,点个赞支持一下吧~
为什么分库分表单表数据量过大,会出现慢查询,所以需要水平分表可以把低频、高频的字段分开为多个表,低频的表作为附加表,且逻辑更加清晰,性能更优随着系统的业务模块的增多,放到单库会增加其复杂度,逻辑不清晰,不好维护,所以会对业务进行微服务拆分,同时拆分数据库怎么分库分表对于水平,不同的业务按不同的条件去分,比如财务数据,可以按年份作为库,月份做为表。 比如多用户系统,可以按用户id取模划分。对于垂直,
源码版本作者Spring Boot是基于2.4.0。每个版本有些变化,读者尽量和我保持一致,以防源码有些出入。从哪入手?相信很多人尝试读过Spring Boot的源码,但是始终没有找到合适的方法。那是因为你对Spring Boot的各个组件、机制不是很了解,研究起来就像大海捞针。至于从哪入手不是很简单的问题吗,当然主启动类了,即是标注着@SpringBootApplication注解并且有着mai
1、鸿蒙上的类似adb的工具名叫hdchdc(HarmonyOS DeviceConnector)是HarmonyOS为开发人员提供的用于调试的命令行工具,通过该工具可以在window/linux/mac系统上与真实设备或者模拟器进行交互。(1)hdc list targets(2)hdc file send local remote(3)hdc install package File这里列举
整体上是按照由浅入深的顺序来的。
作者:Jake Zhang前言我只想面个CV工程师,面试官偏偏让我挑战造火箭工程师,加上今年这个情况更是前后两难,但再难苟且的生活还要继续,饭碗还是要继续找的。在最近的面试中我一直在总结,每次面试回来也都会复盘,下面是我这几天遇到的面试知识点。但今天主题是标题所写的66条JavaScript知识点,由浅入深,整理了一周,每(zhěng)天(lǐ)整(bù)理( yì)10条( qiú)左
1.介绍一下js的数据类型有哪些,值是如何存储的JavaScript一共有8种数据类型,其中有7种基本数据类
对象里面可以存函数 对象相当于Python字典闭包可以控制变量作用域 通过在函数中 返
前言上篇文章给大家介绍在java面试中常见一些面试问题并给出了相应的参考答案,主要包括Java SE中的多线程问题、反射中常见的一些面试题以及对象的拷贝和Java Web中常见的一些问题。本文继续给大家介绍java中的常见面试题,首先给大家介绍的就是java中常见的异常问题:一、异常1、 throw 和 throws 的区别?• throws:是声明可能会抛出一个异常。2、 final、final
主要是总结Java面试当中经常问到的Java知识点,总结到一篇博客当中,并给出参考解答或者参考链接。Java知识点java双亲委派机制及作用什么是双亲委派机制?一个类加载器收到类加载请求之后,首先判断当前类是否被加载过。已经被加载的类会直接返回,如果没有被加载,首先将类加载请求转发给父类加载器,一直转发到启动类加载器,只有当父类加载器无法完成时才尝试自己加载。顺序: 加载类顺序:Bootstr
js书写位置1、行内式可以将单行或少量 JS 代码写在HTML标签的事件属性中(以 on 开头的属性),如:onclick可读性差, 在html中编写JS大量代码时,不方便阅读;引号易错,引号多层嵌套匹配时,非常容易弄混; 注意单双引号的使用:在HTML中我们推荐使用双引号, JS 中我们推荐使用单引号 特殊情况下使用 2、内嵌式 可以将多行JS代码写到 script 标签中 3、外部J
整理学习过程中的 js 知识点,防遗忘!!! JavaScript 基础知识点一、事件1、事件流1)事件冒泡2)事件捕获2、事件委派3、事件处理程序1)DOM0事件处理程序:onclick2)DOM2事件处理程序:addEventListener()3)IE事件处理程序:attachEvent()4)事件处理程序中的this5)事件处理程序总结6)跨浏览器的事件处理程序4、事件对象1)DOM事件对
知识点:数据类型:数值、字符串、布尔值、数组、函数常见的属性和方法 语句和运算符: 条件、循环、异常处理语句,一元、二元、三元运算符 DOM:获取元素、修改元素、事件 BOM:window等各种对象,cookie ajax 正则表达式 闭包定义 JavaScript 是世界上最流行的脚本语言。 JavaScript 是属于 web 的语言,它适用于 PC、笔记本电脑、平板电脑和移动电话。 Jav
面试知识点 jq Vue 初级 生命周期 v-if,v-show区别 组件通信方式 路由传参 哪几种方式 区别 data 为什么是函数不是对象 create 和 mounted 区别 computed 和 watch 区别 指令有哪些 修饰符有哪些 中级 双向绑定原理 模板原理 虚拟dom mvvm ...
本文介绍了MyBatis动态数据源切换的实现方案。核心思路是通过ThreadLocal存储数据源标识,利用AbstractRoutingDataSource实现动态路由,并配合MyBatis拦截器根据参数动态切换。文中提供了完整的代码示例,包括数据源路由类、上下文管理、拦截器实现及Spring集成配置。方案支持基于租户ID等参数自动切换数据源,并强调了事务管理、性能优化和线程安全等注意事项。该方案适用于多租户等需要动态切换数据源的场景。
3.工作区与暂存区操作。2.仓库初始化与克隆。
span里写的内容 超链接名称点击跳转
细化算法(Thinning Algorithm)是图像形态学处理中的经典算法,用于将二值图像中的前景对象逐层剥离,最终提取其骨架(Skeleton)——即单像素宽度的中心线,同时保持原对象的拓扑结构(连通性、端点、孔洞)基本不变。 核心特征 目标:将粗线条、区域对象简化为线状骨架 原则:不改变对象基 ...
正交匹配跟踪算法(OMP)1. 简介考虑以下情况,给定 和 ,我们可以计算 这是非常简单地,也就是 .现在让我们介绍比较难的部分,给定 和 ,如何找到最接近的x?在压缩感知技术中,给定x和A,求解y的过程被称为压缩,然而另一方面,给定y和A,求解x的过程称为重构。重构问题是比较难的一个任务。下面这段来自西瓜书,周志华老师专门为压缩感知写了一个小节来讲这个问题。在现实