js打乱数组的顺序_2020秋招前端面经知识点汇总(一) js部分

作者:北风菌
链接: https:// juejin.im/post/68766238 29574090760
来源:掘金

JS部分:


JS基本数据类型:
简单数据类型:Undefined、Null、Boolean、Number和String,es6xinzeng 复杂数据类型:Object 通过typeof查看数据的基本类型,其中null会返回object,理解为空对象。

0fc0b69443e1af57dede108e14b94409.png

Null和undefined的区别

  • Null表示一个无的对象,转成数值是0,一个尚未存在的对象,常用来表示函数企图返回一个不存在的对象 用法:
  1. 作为函数的参数,表示该函数的参数不是对象
  2. 作为对象原型链的终点
  • Undefined是一个表示无的原始值,转换成数值为NaN,当声明的变量未被初始化的时候,变量的默认值为undefined 用法:
  1. 变量被声明了,但没有被赋值时,就等于undefined
  2. 调用函数时,应该提供的参数没有提供,该参数等于undefined
  3. 对象没有赋值属性,该属性的值为undefined
  4. 函数没有返回值时,默认返回undefined

ES6新增
1.新增声明命令let和const,let表示变量,const表示常量。

  • let和const都是块级作用域,以{}代码块作为作用域范围,只能在代码块里面使用。
  • 不存在变量提升,只能先声明再使用,否则报错。在声明变量之前,该变量都是不可用的。
  • 在同一个代码块内,不允许重复声明。
  • Const声明的是一个只读常量,在声明时就需要赋值。(如果const的是一个对象,对象的值是可以被修改的,就是对象所指向的地址不能改变,而成员变量是可以修改的。)

2.模板字符串
用一对反引号作为标识,可以当做普通字符串使用,也可以用来定义多行字符串,也可在字符串中嵌入变量,js表达式或函数,变量,js表达式或函数需要写在${}中.

4f87f9fb435684149429d54214ab923e.png


3.函数相关扩展方法函数的默认参数
ES6为参数提供了默认值,在定义函数时便初始化这个值,以便在参数没有传递进去时使用。箭头函数
写法:函数名=(形参)=>{……},当函数体中只有一个表达式时,{}和return可以省略,当函数体中形参只有一个时,()可以省略。
特点:

  • 箭头函数是匿名函数,不能作为构造函数,不能使用new
  • 箭头函数不绑定arguments,用rest参数...代替
  • 箭头函数不绑定this,会捕获其所在上下文的this值,作为自己的this值
  • 任何方法都不能改变箭头函数中this的指向,箭头函数通过call(),apply()方法调用一个函数时,只传入参数,不改变this指向 箭头函数没有原型属性
  • 箭头函数不能当做Generator函数,不能使用yield关键字
  • 与普通函数的区别:普通函数中的this指向的始终是最后调用它的对象(比如在对象中定义的函数,这个函数内部的this,指向的是这个对象),如果没有直接调用对象,会指向undefined或者window,一般都会指向window,在严格模式下才会指向undefined。

普通函数中的this的四种调用模式

  • 函数式调用模式 如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时this指向了window

e529ed0f23af969384366bf53cad10c5.png
  • 方法式调用 当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this被绑定到当前对象

bfe1a0691afbd2df4f13a4f2f581b563.png
  • 构造函数式调用 如果函数是通过new关键字进行调用的,此时this被绑定到创建出来的新对象上。

2560c10bc049131c96cee12d4b7d9784.png
  • 上下文调用(借用方法模式) 上下文调用模式也叫方法借用模式,分为apply与call,使用方法:函数.call() 或者函数.apply()
  • 几种特殊的this指向 定时器中的this指向了window,因为定时器的function最终是由window来调用的。
    事件中的this指向的是当前的元素,在事件触发的时候,浏览器让当前元素调用了function

4.对象相关的扩展方法

  • 属性的简写。ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。

ff57fe62998e2bec365d039da6f0a3f0.png
  • Object.keys()方法,获取对象的所有属性名或方法名(不包括原型的内容),返回一个数组。

6c3c3a6cc22f854919f266e7a788cc3b.png
  • Object.assign (),assign方法将多个原对象的属性和方法都合并到了目标对象上面。可以接收多个参数,第一个参数是目标对象,后面的都是源对象。 第一级属性深拷贝,以后级别属性浅拷贝

ed849414a955b98b14aa70f14f1b0280.png

5.for of循环
是遍历所有数据结构的统一的方法。for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串。不能遍历对象,因为对象没有内置的迭代器

68a00121b895a6b7ba9fbcbc938ab8ac.png


6.import和export

  • ES6标准中,JavaScript原生支持模块(module)了。这种将JS代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用。
  • export用于对外输出本模块(一个文件可以理解为一个模块)变量的接口。
  • import用于在一个模块中加载另一个含有export接口的模块。
  • import和export命令只能在模块的顶部,不能在代码块之中。

4eac0551fafd9e67aa19ccaa08f3ec7a.png

7.解构赋值

  • ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
  • 数组的解构赋值,数组中的值会自动被解析到对应接收该值的变量中,数组的解构赋值要一一对应 如果有对应不上的就是undefined

084bc0a5bebe6519e9500850adcae789.png
  • 对象的解构赋值,对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

681640fd65668a11bf48d036aa89e2e4.png

8.set数据结构
Set数据结构,类似数组,所有数据都是唯一的,没有重复的值,它本身是一个构造函数
属性和方法:

  • size 数据的长度
  • add() 添加某个值,返回 Set 结构本身。
  • delete() 删除某个值,返回一个布尔值,表示删除是否成功。
  • has() 查找某条数据,返回一个布尔值。
  • clear() 清除所有成员,没有返回值。
  • 应用:数组去重

90b5e4fc4e213ba30d91bd785488f38e.png
  • 注意:new Set(arr)的返回结果是object,可以使用Array.from(new Set(arr))或[...new Set(arr)]来返回数组。

9.map数据结构
Map结构是键值对集合(Hash结构) 在JS中的默认对象的表示方式为{},即一组键值对,但是键必须是字符串。 为了使用Number或者其他数据类型作为键,ES6规范引入了新的数据类型Map。 Map是一组键值对的结构,具有极快的查找速度。初始化Map需要一个二维数组,或者直接初始化一个空Map。

3da495f9e6676c88bfee804290d883a2.png

map和object的区别

  • Obj的键只能用字符串、数字或者Symbol等简单数据类型当作键
  • Map的键可以是任意的数据类型
  • Map不存在同名碰撞问题,每个对象都是单独的一块内存地址
  • Map实现了迭代器,可用for...of遍历,而Object不行
  • Map可以直接拿到长度,而Object不行。
  • 填入Map的元Map可以使用省略号语法展开,而Object不行。填入元素会保持原有的顺序,而Object无法做到。

10.Spread Operator 展开运算符(...)
可以展开字符串,数组,对象等

  • 将字符串转换成数组

a2bec2bbc658558486b6560bc1b85216.png
  • 将集合转换成数据

0e806598a1ba03a37be51b0df93fd69b.png
  • 两个数组或对象的合并

2cf4241a4b549b73324f083509420170.png

64da45b9596ec66cf97904c9f633d123.png
  • 在函数中,用来代替arguments参数,
    rest参数 …变量名称
    rest 参数是一个数组 ,它的后面不能再有参数,不然会报错

7784213b00d40baf77a73f10a4269648.png

promise对象
Promise是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
promise构造函数是同步执行的,then方法是异步执行的。
它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败。
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。对于已经实例化过promise对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。then()方法接收两个参数:onResolve和onReject,分别代表当前 promise 对象在成功或失败时。Promise的实现原理 首先promise有三种状态
Pending 对象实例创建时候的初始状态
Fulfilled 可以理解为成功的状态
Rejected可以理解为失败的状态
构造一个Promise实例需要给Promise构造函数传入一个函数。传入的函数需要有两个形参,两个形参都是function类型的参数。分别是resolve和reject。
Promise上还有then方法,then 方法就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject时执行第二个函数(onRejected)
当状态变为resolve时便不能再变为reject,反之同理。

2abb919bf2932a5bdb146b92d71f2bec.png

Promise的缺点

  1. 无法取消,一旦新建就会立即执行,无法中途取消,
  2. 不设置回调函数的情况下,promise内部抛出的错误不会反映到外部
  3. 当处于pending状态时,无法得知目前进展到哪一个阶段,刚开始还是快结束

Promise.all()
将promise对象数组按照异步顺序全部完成后在then的第一个函数中传入完成的结果,这个list参数是arr这个promise对象数组中所有异步的then中返回的img按顺序组合成的一个数组。也可以理解为将arr中的每个promise对象完成后的then中的img按顺序加入到一个数组中,在全部完成后返回这个数组list
Promise.all(requestPromises).then(…).catch(…) 会在所有requestPromises都resolve时才会进then方法,并且把所有结果以一个数组返回。只要有一个失败,就会进catch。如果在单个请求中定义了catch方法,那么就不会进Promise.all的catch方法。因此,可以在单个的catch中将失败的promise放入一个list,待一轮请求完成后,再去请求失败的请求。

fc7745f0eb60194e1e6bf3830996261d.png

Promise.race()
的作用是将arr这个promise对象数组中最先执行完成的一个promise中then中返回的参数传入img中,也就是promise.race().then()的第一个函数的参数中。

e2e64d27f8a0164a3de087edd6d80169.png


New实例化函数的过程
使用new关键字调用函数(new ClassA(...))的具体过程

  1. 创建一个空对象 Var obj={};
  2. 设置新对象的constructor属性为构造函数的名称,将新对象的proto指向构造函数的prototype Obj.__proto__==ClassA.prototype
  3. 使用新对象调用构造函数,将构造函数中this指向新实例对象, ClassA.call(obj)
  4. 将初始化完毕的新对象地址,保存到等号左边的变量中。
  5. 若构造函数中返回this或返回值是基本类型(number,string,bool,null,undefined)或者无返回值,则返回新的实例对象,若是引用类型的值,则返回这个引用类型。

ES5中call,apply,bind的区别

  • Js中,call和apply都是为了改变函数运行时的上下文,也就是为了改变函数体内部的this指向。
  • 格式为function.call(object),function.apply(object),function.bind(object)()
  • 其中调用call和apply的方法会立即执行。
  • bind方法会创建一个新的函数,需要加上()才会执行,传入的参数放在()内

7d53c7e3153eeee26386b5a8d3c0f959.png

d682f271a7281c388196e1ec619a9686.png

ES6与 CommonJS 模块化的区别
什么是js模块化?
js一开始并没有模块化的概念,直到ajax被提出,前端能够像后端请求数据,前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理。当时使用子执行函数来解决这些问题,比如经典的jquery就使用了匿名自执行函数封装代码,将他们全都挂载到全局变量jquery下边。CommonJs
CommonJs通过nodeJs发扬光大,每个js文件就是一个模块,每个模块有单独的作用域。模块以module.exports为出口,输出一个对象。使用require方法读取文件,并返回其内部的module.exports对象。
CommonJs的问题在于,他的加载是同步的,这在服务端很正常,但是在充满了异步的浏览器里,就不适用了。为了适应浏览器,社区内部发生了分歧。AMD(异步模块定义
AMD规范通过define方法去定义模块,通过require方法去加载模块。RequireJS实现了这种规范。
AMD只有一个接口:define(id?,dependencies?,factory); 它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中。要是没什么依赖,就定义简单的模块(或者叫独立的模块)CMD(通用模块定义)
CMD是SeaJS 在推广过程中对模块定义的规范化产出。AMD和CMD的区别

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible(尽可能的懒加载,也称为延迟加载,即在需要的时候才加载)。
  2. CMD 推崇依赖就近(依赖项可以使用的时候定义),AMD 推崇依赖前置(依赖项必须在一开始就写好)。
  3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

es6中的模块化 在es6没有出来之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种,前者用于服务器,后者用于浏览器,ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
es6中的模块化有一个比较大的特点,就是实现尽量的静态化。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
模块功能主要由两个命令构成:export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一般来说,一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取,如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
Javascript 模块化主要有三种方案:

  1. CommonJS

049c4775634d10230446a5b8969da991.png
  1. AMD / CMD

0f7a434b68bb39df3e8395ba6e7072c9.png
  1. ES6

9f8ee71a2e7829977c73222e7e35684b.png

ES6模块与CommonJS的区别

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJs模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化不会影响到这个值。

04b2e4479582ea0625b3712d92047cec.png

你可以看到明明common.js里面改变了count,但是输出的结果还是原来的。这是因为count是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动的值。将common.js里面的module.exports 改写成

33ea56b0f672ea439391e0f755115a6f.png

这样子的输出结果是 1,2,2
而在ES6当中,写法是这样的,是利用export 和import导入的

b92bd8c8b8a30703bb86351d0f72a998.png

ES6模块是动态引用,并且不会缓存,模块里面的便令绑定其所在的模块,而是动态地去加载值,并且不能重新复制。
**es6模块的特点:

  1. 静态化,必须在顶部,不能使用条件语句,自动采用严格模式
  2. treeshaking和编译优化,以及webpack3中的作用域提升
  3. 外部可以拿到实时值,而非缓存值(是引用而不是copy) **es6模块和commonjs模块的区别:
  4. CommonJS 模块输出的是一个值的拷贝,一旦输出一个值,模块内部的变化不会影响到这个值,ES6 模块输出的是值的引用。
  5. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口并在使用的时候才进行调用。 **es6模块和commonjs模块的相同点:
  6. 两者都可以对对象内部属性的值进行改变

回调函数:
回调函数: 一个函数被作为参数传递给另一个函数(在这里我们把另一个函数叫做“otherFunction”),回调函数在otherFunction中被调用。
/注意到click方法中是一个函数而不是一个变量 //它就是回调函数 $("#btn_1").click(function() { alert("Btn 1 Clicked"); }); 复制代码
//或者 function click() { // 它就是回调函数 alert("Btn 1 Clicked"); } $("#btn_1").click(click); 复制代码
回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”(就像它的名字一样)
实现回调函数的基本原理
使用命名函数或者匿名函数作为回调
像之前的例子一样,第一种方法就是匿名函数作为回调(使用了参数位置定义的匿名函数作为回调函数)。第二种方式就是命名函数作为回调(定义一个命名函数并将函数名作为变量传递给函数)
什么是回调地狱
说起回调地狱,首先想到的是异步,在js中我们经常会大量使用异步回调,例如使用ajax请求
我们来看下面这段代码:
function a(functionb(){ c(function d(){ }) }) 复制代码
我们发现上面代码大量使用了回调函数(将一个函数作为参数传递给另个函数)并且有许多 })结尾的符号,使得代码看起来很混乱。
如何解决回调地狱呢
第一种使用ES6中的Promise,中文翻译过来承诺,意思是在未来某一个时间点承诺返回数据给你。
Promise有三种状态:pending/reslove/reject 。pending就是未决,resolve可以理解为成功,reject可以理解为拒绝。
同时Promise常用的三种方法 then 表示异步成功执行后的数据状态变为reslove catch 表示异步失败后执行的数据状态变为reject all表示把多个没有关系的Promise封装成一个Promise对象使用then返回一个数组数据。
但是如果过多的使用then也会照成新的执行流程问题。所以我们的另一位主角登场了,那就是ES6中的Generator(生成器)
Generator(生成器)是一种有效利用内存的机制,一边循环一边计算生成数值的机制。通过配合Promise可以更加优雅的写异步代码

711fde8f6485702aa05a86d4d297042d.png

Iterator(迭代器):当我们实例化一个生成器函数之后,这个实例就是一个迭代器。可以通过next()方法去启动生成器以及控制生成器的是否往下执行。
yield/next:这是控制代码执行顺序的一对好基友。
通过yield语句可以在生成器函数内部暂停代码的执行使其挂起,此时生成器函数仍然是运行并且是活跃的,其内部资源都会保留下来,只不过是处在暂停状态。
在迭代器上调用next()方法可以使代码从暂停的位置开始继续往下执行。
当然生成器不是最完美的,它的语法让人难以理解,所以ES7推出了async/await (异步等待),多么贴切。yield + Promise的写法需要我们对拿到的promise的决议进行人工处理(区分成功或失败),在ES7中提供了async/await帮我们省掉了这个步骤

d04cc9bf9cd8561d4b5e003849bc2a68.png
  1. 明确概念
  • async函数就是generator函数的语法糖。
  • async函数,就是将generator函数的*换成async,将yield替换成await。
  1. async函数对generator的改进
  • 内置执行器,不需要使用next()手动执行。
  • await命令后面可以是Promise对象或原始类型的值,yield命令后面只能是Thunk函数或Promise对象。
  • 返回值是Promise。返回非Promise时,async函数会把它包装成Promise返回。(Promise.resolve(value))
  1. 作用 异步编程的终极解决方案。
  2. 通俗理解(个人理解) async/await,就是异步编程回调函数写法的替代方法。(使代码以同步方式的写法完成异步操作)

原理(执行顺序) 函数执行时,一旦遇到await就会返回。等到触发的异步操作完成(并且调用栈清空),再接着执行函数体内后面的语句。 【个人理解】

  • await语句后面的代码,相当于回调函数。(即:await的下一行开始,都视作回调函数的内容)
  • 回调函数会被压入microtask队列,当主线程调用栈被清空时,去microtask队列里取出各个回调函数,逐个执行。
  • await只是让当前async函数内部、后面的代码等待,并不是所有代码都卡在这里。遇到await就先返回,执行async函数之后的代码。

Js原型链:
**什么是原型链?

  • 每个对象都可以有一个原型对象,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
  • 原型链的尽头指向null,原型链上未定义的属性返回undefined
  • prototype属性,它是函数所独有的,它是从一个函数指向一个对象,这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法,prototype属性可以给函数和对象添加可共享(继承)的方法、属性。
  • 每一个JavaScript实例对象(除了null )都具有的一个属性,叫proto,这个属性会指向该对象的原型,是查找某函数或对象的原型链方式。
  • 每个原型都有一个 constructor 属性指向关联的构造函数,实例原型指向构造函数

8edfe0e2f770fab02fa6402ee0a13c8e.png

Js中实现继承的几种方式:

  1. 原型链继承(父类的实例作为子类的原型)

06de94d17365c77995a12a1a3b00aa87.png

优点:
简单易于实现,父类的新增的实例与属性子类都能访问
缺点:
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
无法实现多继承
创建子类实例时,不能向父类构造函数中传参数

  1. 借用构造函数继承(伪造对象,经典继承
    ) 复制父类的实例属性给子类

60b28e42a266d42edcb22e0ce0e5d68a.png

优点:
解决了子类构造函数向父类构造函数传递参数的问题
可以实现多继承(call或者apply多个父类)
缺点:
方法都在构造函数中定义,无法复用
不能继承原型属性/方法,只能继承父类的属性和方法

  1. 实例继承(原型式继承)

840d3848bfb42a23634383d416215193.png

优点:
不限制调用方式,简单易实现
缺点:
不能多次继承

  1. 组合式继承
    调用父类的构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用

5a161dae8ecf1b62e9f74ecad6ccc4dc.png

优点:
函数可以复用,不存在引用属性问题,可以继承属性和方法,并且可以继承原型的属性和方法
缺点:
调用了两次父类,产生了两个实例

  1. ES6继承

54b2d7ceb4bf0840a43cdb5d236a7611.png

ES5继承和ES6继承的区别
es5继承首先是在子类中创建自己的this指向,最后将方法添加到this中
Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)
es6继承是使用关键字先创建父类的实例对象this,最后在子类class中修改this

闭包:

  1. 什么是闭包: 闭包是指有权访问另外一个函数作用域中的变量的函数。 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配。当在一个函数内定义另外一个函数就会产生闭包。
  2. 为什么要用:
  • 匿名自执行函数:我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,可以用闭包。
  • 结果缓存:我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
  • 封装:实现类和继承等。

闭包的应用: 实现每隔一秒输出1,2,3,4....
实现方式

  • ES6 let 块级作用域
  • ES5 闭包 匿名函数and函数自动执行,(function(i){})(i) 其中第一个()返回一个匿名函数,第二()起到立即执行的作用

52effd316fc307d6622f29ddf15889ed.png

63d3de93d4603f98467e13cb05a61657.png

实现add(1)(2)(3)输出6

闭包的优点

  • 可以重复使用变量,并且不会造成变量污染
  • 全局变量可以重复使用,但是容易造成变量污染。局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。闭包结合了全局变量和局部变量的优点。
  • 可以用来定义私有属性和私有方法。 闭包的缺点
  • 比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露(分配了一些‘顽固的’内存,浏览器无法进行回收,就会导致后面所用内存不足)。

Js内存泄漏:
内存泄漏是指由于疏忽或者错误造成程序未能释放已经不再使用的内存,内存泄漏并非指内存在物理内存上的消失,而是应用程序在分配某段内存之后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,造成了内存的浪费。

  1. 意外的全局变量,js对未声明变量会在全局最高对象上创建它的引用
  2. console.log
  3. 闭包
  4. DOM泄漏,我们一般将常用的DOM。我们会采用变量引用的方式会将其缓存在当前环境
  5. 被遗忘的timers

Js垃圾回收机制:
JS的内存生命周期:

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放、归还

js垃圾回收机制:

  1. 自动垃圾回收机制就是找出那些不再继续使用的值,然后释放其占用的内存空间。垃圾回收器每隔固定的时间段就执行一次释放操作。
  2. js最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,上面例子中的a = null 其实就是做了一个释放引用的操作,让a原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。因此,在适当的时候解除引用,是为页面获的更好性能的一个重要方式。

标记清除
这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量及被其引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
引用计数
另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
Ajax,fetch,Axios的区别:

de79707f21af2612d548515ef7d897f2.png

ajax使用步骤

  1. 创建XmlHttpRequest对象
  2. 调用open方法设置基本请求信息
  3. 设置发送的数据,发送请求
  4. 注册监听的回调函数
  5. 拿到返回值,对页面进行更新
    可这里的readyState的状态码代表什么呢?这里记录如下:
    0:未初始化,但是已经创建了XHR实例
    1:调用了open()函数
    2:已经调用了send()函数,但还未收到服务器回应
    3:正在接受服务器返回的数据
    4:完成响应

f3d4f8be614187b447a0c18b482c928f.png

3a7251f2a144f19b7d06dc296f39b3f6.png

96d633c80595c7acbb53bc78214a53e8.png
  1. 传统Ajax值得是XMLHttpRequest(XHR),最早出现的发送后端请求技术,隶属于原生js中,核心是使用XMLHttoRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。
    优点:局部更新,原生支持,不需要插件
    缺点:可能破坏浏览器的后退功能,产生回调地狱
  2. Jquery ajax是对原生XHR的封装,此外还添加了对JSONP的支持。
    优点:很多
    缺点:还是没有解决回调地狱的问题。
  3. Fetch是ajax的替代品,是ES6中出现的,使用了ES6中的promise对象,代码结构比ajax简单,参数类似Jquery ajax,但是fetch不是ajax的进一步封装,而是原生js,没有使用XHR对象
    优点:语法简洁,易于理解。基于promise实现,支持async/await。更加底层,丰富的API,脱离了XHR,是ES规范里的新实现方式
    缺点:只对网络请求报错,400,500都会当做成功的请求。默认不带cookie,需添加配置项。不支持超时控制。不能监测请求的进度。
  4. Axios是一个基于promise用于浏览器和node.js的http客户端,本质上是对XHR的封装,不过是基于promise实现,符合ES最新规范。
    优点:从浏览器中创建XHR,支持promise的API,浏览器端支持防止CSRF(跨站请求伪造),提供了并发请求接口,从node.js创建http请求,拦截请求和相应,转换请求和响应数据,自动转换json数据。

JS跨域:
跨域产生的原因:跨域是由于浏览器的同源策略引起的,是指页面请求的接口地址,必须与页面url地址处于同域(即域名,端口,协议相同)上,是为了防止某域名下的接口被其他域名下的网页非法调用,是浏览器对js施加的安全限制。
后端解决跨域的方式通常有两个:JSONP
利用script标签可跨域的特点,在跨域脚本中可以直接回调当前脚本的函数,即通过动态创建script,再请求一个带参的网址实现跨域通信。 在jQuery中如何通过JSONP来跨域获取数据?
第一种方法是在ajax函数中设置dataType为'jsonp':
第二种方法是利用getJSON来实现,只要在地址中加上callback=?参数即可:

f40b7ee301fe5eee1cc242fe150b9b46.png


当你需要的跨域时候,创建一个script标签,加到head中,设置这个script的src值为其它域的url,最好加上一个随机参数,以防WEB缓存机制。如生成这样的一个标签:
<script type="text/javascript" src="http://abc.com/js.php?t=1234" ></script> 复制代码
当然这应该通过document.createElement来完成。

194c6033409b0970bf1d55ba1b196751.png

CORS
服务器设置http响应头中Access-Control-Allow-Origin值。

786474898899fa30e9637379d82e7d24.png


关于cookie如何在跨域请求携带的问题:

b7e8f183fea36043fb03c42a2fa43774.png

通过设置 withCredentials: true ,发送Ajax时,Request header中便会带上 Cookie 信息。
服务器端通过在响应 header 中设置 Access-Control-Allow-Credentials = true 来允许携带cookie。
Access-Control-Allow-Origin:""和Access-Control-Allow-Credentials", "true" 同时设置的时候Access-Control-Allow-Origin不能设置为"*",只能设置为具体的域名。
以上两个方法严重依赖后端的协助。
<?php //加头部解决跨域问题 header('Access-Control-Allow-Origin: *'); 复制代码
如何用前端的方式实现跨域?

  1. 正向代理(forward)意思是一个位于客户端和原始服务器 (origin server) 之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标 (原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
    正向代理是为客户端服务的,客户端可以根据正向代理访问到它本身无法访问到的服务器资源。
    正向代理对客户端是透明的,对服务端是非透明的,即服务端并不知道自己收到的是来自代理的访问还是来自真实客户端的访问。
  2. 反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
    反向代理是为服务端服务的,反向代理可以帮助服务器接收来自客户端的请求,帮助服务器做请求转发,负载均衡等。
    反向代理对服务端是透明的,对客户端是非透明的,即客户端并不知道自己访问的是代理服务器,而服务器知道反向代理在为他服务。

总结:
正向代理即是客户端代理,即中间服务器代理客户端,服务端不知道实际发起请求的客户端
反向代理即是服务端代理,中间服务器代理服务端,客户端不知道实际提供服务的服务端。
简单的对比
使用charles等正向代理方式比较简单,需要掌握的知识点也比较少。但相应的其可配置性较弱,仅适合中小型项目使用。
使用nginx的反向代理则相对复杂一些,需要了解基本的nginx配置。但其可配置性较强,支持URL的正则匹配,设置优先级等,适合复杂的项目使用。
document.domain + iframe跨域:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
location.hash + iframe跨域:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
window.name + iframe跨域:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。
深拷贝与浅拷贝:
深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

  • 浅复制 —-只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
  • 深复制 —-在计算机中开辟了一块新的内存地址用于存放复制的对象。如果源对象发生了改变,复制的对象并不会发生变化。 通俗一点理解就是浅拷贝出来的数据并不独立,如果被复制的对象改变了,那么浅拷贝的对象也会改变,深拷贝之后就会完全独立,与拷贝对象断绝关系。
    深拷贝的实现方式:
  1. JSON.parse(JSON.stringify()) 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。
  2. 手写递归方法 递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

Js中的同步与异步:
首先js是一门单线程的语言,无论如何,做事情的只有一条线程,同步和异步的差别就在于这个线程上各个流程的执行顺序不同。 所有任务分成两种,一种是同步任务,同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,异步任务是指不进驻主线程,而进入任务队列的任务,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程执行。
具体来说,异步的执行机制如下,

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,存在一个任务队列,只要异步任务有了运行结果,就在任务队列中放置一个事件。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件,对应哪些异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的三个步骤
    任务队列就是一个事件的队列,也可以理解为消息的队列,IO设备完成一项任务,就在任务队列中添加一个事件,表示相关的异步任务可以进入执行栈了,主线程读取任务队列,就是读取其中的事件,任务队列中的事件,除了IO设备事件意外,还包括一些用户产生的时间,比如鼠标点击,页面滚动等等,相对耗时,只要制定过这些事件的回调函数,在这些事件发生时,就会进入任务队列,等待主线程读取。

Js宏任务与微任务与事件循环:
js中的任务分为宏任务和微任务,在挂起任务时,js引擎会将所有任务按照这个类别分别分到这两个队列中,首先执行主线程上的任务,执行完毕之后,会执行微任务队列中的所有任务,然后再取出宏任务队列中的第一个宏任务,按照主线程任务,所有微任务队列任务的顺序执行,完毕后取出微任务队列中的所有任务顺序执行,之后再取宏任务队列中的一个宏任务,执行主线程任务,并执行其所有微任务,如此循环,直到所有任务队列为空。
浏览器环境下:
宏任务一般有:setTimeout,setInterval,requestAnimationFrame
微任务一般有:process.nextTick,Promise.then catch finally
Js中的事件循环:
先执行一个宏任务,执行结束后,如果有可执行的微任务,则执行所有的微任务,并开始下一个宏任务,如果没有,则直接执行下一个宏任务,知道所有任务执行完毕。
例子:

b09a3634f13ec70a912cf4a8f871ce13.png

3c8a0d6bd35d8bd7dd264e75f0b183f8.png

首先浏览器执行js进入第一个宏任务进入主线程, 直接打印console.log('1')
遇到 setTimeout 分发到宏任务Event Queue中
遇到 process.nextTick 丢到微任务Event Queue中
遇到 Promise,new Promise 直接执行 输出 console.log('7');
执行then 被分发到微任务Event Queue中
第一轮宏任务执行结束,开始执行微任务 打印 6,8
第一轮微任务执行完毕,执行第二轮宏事件,执行setTimeout
先执行主线程宏任务,在执行微任务,打印'2,4,3,5'
整段代码,共进行了两次事件循环,完整的输出为1,7,6,8,2,4,3,5,
浏览器环境下和node.js环境下的宏任务和微任务的执行顺序是不一样的
Js中的事件委托:
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
事件委托的原理:事件委托是利用事件的冒泡来实现的,冒泡就是事件从最深的节点开始,然后逐步向上传播事件。举个例子,页面上有这么一个结点树div>ul>li>a,比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div添加点击事件,那么里面的ul,li,a触发点击事件的时候,都会冒泡到最外成的div上,所以都会触发,这就是事件委托,委托他们父级代为执行事件。
事件委托的应用:
点击不同的li显示其内容
传统方法与事件委托方法

951bf6f0636c17ac76a295518781a93d.png

097f090f38e6c19f0d30e4970a53f55e.png

传统方式的DOM操作较多,每次点击li都要遍历所有li的事件
而事件委托则可以只操作当前点击的dom
浏览器事件机制
事件传播的三个阶段:捕获,目标对象,冒泡。

  1. 其中捕获(Capture)是 事件对象(event object) 从 window 派发到 目标对象父级的过程。
  2. 目标(Target)阶段是 事件对象派发到目标元素时的阶段,如果事件类型指示其不冒泡,那事件传播将在此阶段终止。
  3. 冒泡(Bubbling)阶段 和捕获相反,是以目标对象父级到 window 的过程。 在任一阶段调用 stopPropagation 都将终止本次事件的传播。

绑定在被点击元素的事件是按照代码的顺序发生的,其他非绑定的元素则是通过冒泡或者捕获的触发。按照W3C的标准,先发生捕获事件,后发生冒泡事件。所以事件的整体顺序是:非目标元素捕获 -> 目标元素代码顺序 -> 非目标元素冒泡。
Js的事件绑定方式:

  1. 夹杂在html标签内

711663b8a8e50fce070679e9783307d7.png
  1. on+"事件"

83d8214d6a64e58302238cc1c0f2150d.png

aada2ac2a5fcea96f1e95b0545bf769b.png

但如果想单击一次执行多个函数时,这种绑定方式就无法完成了:因为同一个事件,在执行多个函数时会发生覆盖

  1. element.addEventListener(事件名,函数,冒泡/捕获)

09aee7d63e1c867fd008227c7eb9e4bd.png

上面两个函数fA(),fB()都会执行,不会发生覆盖现象。
第一个是事件类型,不需要on前缀,但事件名需加 " " ;
第二个参数是发生这个事件的时候要调用的函数;
第三个是布尔值的true或false.(默认值是false)
false代码是以冒泡型事件流进行处理,一般都选用false.
true意味着代码以捕获型事件流进行处理,不是必须不推荐使用。

Js执行栈Js执行上下文: 执行上下文就是JavaScript 在被解析和运行时环境的抽象概念,JavaScript 运行任何代码都是在执行上下文环境中运行的,执行上下文包括三个周期:创建——运行——销毁,重点说一下创建环节。
创建环节(函数被调用,但未未被执行)会执行三件事情

  1. 创建变量对象,首先初始化函数的arguments对象,提升函数声明和变量声明,从近到远查找函数运行所需要的变量。
  2. 创建作用域链,作用域就是一个独立的地盘,让变量不会相互干扰,当前作用域没有定义的变量,这成为 自由变量。自由变量会向上一直寻找,要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,如果最终没有就为undefined。这种层层之间的调用关系就构成了作用域链。
  3. 确定this指向,this、apply、call的指向Js执行栈 JavaScript 引擎创建了执行栈来管理执行上下文,可以把执行栈认为成一个储存函数调用的栈结构,遵循先进后出的原则。
    Js中有三种执行上下文
  • 全局执行上下文,默认的,在浏览器中是window对象,并且this在非严格模式下指向它。
  • 函数执行上下文,JS的函数每当被调用时会创建一个上下文。
  • Eval执行上下文,eval函数会产生自己的上下文,这里不讨论。 Js执行栈工作过程
    1 JavaScript引擎是单线程执行,所有代码都是排队执行。
  1. 一开始执行的是全局代码,首先创建全局的执行上下文,然后将该执行上下文压入执行栈中。
  2. 每当执行一个函数,就会创建该函数的执行上下文,然后将其压入执行栈的顶部,函数执行完成后,执行上下文从底部退出,等待垃圾回收。
  3. 浏览器js总是访问执行栈顶层的执行上下文。
  4. 全局上下文只有唯一的一个,它在浏览器关闭时出栈

进程和线程的区别:

  • 进程是资源分配的最小单位,线程是CPU调度的最小单位
  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上) 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

Js判断变量为数组的方法:

  1. Instanceof object instanceof constructor

alert(a instanceof Array); // true, 复制代码
变量 instanceof 类型 返回的是布尔值
其中instanceof运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上
instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。

b17ec738666925141dc6da9218a11cf3.png

2. arr.__proto__===Array.Prototype 其中__proto__为对象实例的属性,指向该对象的原型
Prototype为构造函数的属性,指向该构造函数的原型
3. arr.constructor===Array 其中constructor为实例原型的属性,指向该实例的构造函数
4. Object.prototype.toString().call(arr) 其中返回值为字符串’[Object Array]’
5. Array.isArray(arr) ES6中的新方法,返回值为true或false
Js数组去重方法:

  1. Array.filter() + indexOf

adedcc693384cb8e03cf7c7824626b2b.png
  1. 使用 for...of + includes()

1c10f3afa90bc391fea13d6c38a29279.png
  1. 双重 for 循环
  2. Array.sort(),然后比较相邻元素是否相等,从而排除重复项。
  3. for...of + Object 利用对象的属性不能相同的特点进行去重

85fd433c4f1b367193d922765b2f3098.png
  1. ES6的new Set()

4eefddc2d86d754bcb558ed27abd8f0d.png

数组去重要么使用for...of + Object方式,要么使用ES6的 new Set()方式。从执行结果看for...of + Object的效率应该是最高的(只在当前量级的计算结果来看)。

Js防抖和节流:
Js防抖的基本思路:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

3684d511dce7693583916e16c068aa44.png

JS中节流的基本思路是:规定一个期限时间,在该时间内,触发事件的回调函数只能执行一次,如果期限时间内回调函数被多次触发,则只有一次能生效。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值