面试总结 JS(上)

198 阅读34分钟

js部分面试题

js的数据类型(关于数据类型相关的)

js有8种数据类型 基本数据类型

ES5的5种:Null,undefined,Boolean,Number,String

ES6新增:Symbol表示独一无二的值

ES10新增:BigInt 表示任意大的整数

一种引用数据类型:(本质上是由一组无序的键值对组成)

引用数据类型: Object。包含Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。

检测数据类型的方式

typeof  其中数组、对象、null都会被判断为Object,其他判断都正确

instanceof 只能判断引用数据类型,不能判断基本数据类型

constructor 它有2个作用 一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。需要注意的事情是如果创建一个对象来改变它的原型,constructor就不能来判断数据类型了

Object.prototype.toString.call()

1、null 和 undefined 的区别?

相同:

在 if 语句中 nullundefined 都会转为false两者用相等运算符比较也是相等

首先 UndefinedNull 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefinednull。

不同:

undefined 代表的含义是未定义,

定义了形参,没有传实参,显示undefined

一般变量声明了但还没有定义的时候会返回 undefined

对象属性名不存在时,显示undefined

函数没有写返回值,即没有写return,拿到的是undefined

null 代表的含义是空对象。也作为对象原型链的终点

null 主要用于赋值给一些可能会返回对象的变量,作为初始化。

存储方式以及堆栈内存是什么

基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。 引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。

引用数据:类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址

基本数据类型和引用数据类型

基本类型值:指的是保存在栈内存中的简单数据段;number string 布尔

引用类型值:指的是那些保存在堆内存中的对象,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组 对象

引用类型存储把值存储在堆内存中,堆内存是从下往上存储。生成唯一内存地址。然后在栈内存中把地址赋值给变量。栈内存是从上往下存储的。之所以如此划分内存主要考虑到特别大的对象进行值传递时的效率问题

堆和栈有什么区别存储机制

栈 是一种连续储存的数据结构,具有先进后出的性质。

通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。

堆 是一种非连续的树形储存数据结构,每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。

js数据类型转换

在JavaScript中类型转换有三种情况:

转换为数字(调用Number(),parseInt(),parseFloat()方法)

转换为字符串(调用.toString()或String()方法)

转换为布尔值(调用Boolean()方法) 还有隐式转换 注意:null、undefined没有.toString方法

数据类型相比较objected .is ==和===

=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回false如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,除非两者存储的内存地址相同才相等,反之false

== 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会进行强制类型转化后再进行比较,使用Number()转换成Number类型在进行判断。例外规则,null==undefined,null/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值,进行条件判断。Boolean(null)===Boolean(undefined)>false===false 结果为true

Object.is()在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等

typeof null 的结果是什么,为什么?

typeof null 的结果是Object。

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:

000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。

如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。

有两种特殊数据类型:

undefined的值是 (-2)30(一个超出整数范围的数字);

null 的值是机器码 NULL 指针(null 指针的值全是 0)

那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。

事件的故事

什么是事件?

事件是文档和浏览器窗口中发生的特定的交互瞬间,事件就发生了。一是直接在标签内直接添加执行语句,二是定义执行函数。 DOM事件分为两种类型

事件类型分两种:事件捕获、事件冒泡。

事件捕获就是:事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素。

事件冒泡:IE提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。 什么是事件流?

事件流

页面接受事件的先后顺序就形成了事件流。

自定义事件

自定义事件,就是自己定义事件类型,自己定义事件处理函数。

事件委托

事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了

阻止事件冒泡

event.stopPropagation() .stop修饰符

addEventListener(‘click',函数名,true/false) .captrue 事件委托

好处:提高性能,减少了事件绑定,从而减少内存占用

应用场景 在vue中事件委托:

我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现

封装事件绑定

我们在封装这个函数的时候可以用addEventListener(事件监听)来实现 ,封装的函数有三个参数,第一个是要绑定事件的元素,第二个是要绑定的事件类型,第三个是事件的执行函数。 调用这个函数 就可以实现给某个元素绑定一个事件了。

Javascript 的作用域和作用域链

作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。

作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域

全局作用域就是Js中最外层的作用域,在哪里都可以访问

函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套

Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)

防抖节流

防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。

鼠标事件 mouseenter与mouseover区别

mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。

mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,之后只要是在被绑定元素节点区域内移动,都会被触发。

mouseenter 不支持事件冒泡 mouseover 会冒泡

引用数据类型 object

object的方法

Object.is()

是一种判断两个值是否相同的方法。

语法:Object.is(value1, value2);

参数:value1:要比较的第一个值。value2:要比较的第二个值。

返回值:一个布尔表达式,指示两个参数是否具有相同的值。

Object.assign()

方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。 语法:Object.assign(target, ...sources)

参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。

返回值:修改后的目标对象。

Object.entries()

方法返回给定对象自己的可枚举字符串键属性 [key, value] 对的数组。

它类似于使用 for...in 循环进行迭代,除了 for...in 循环还会枚举原型链中的属性。

属性的顺序与通过手动循环对象的属性值给出的顺序相同。

语法:Object.entries(obj)

参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:

给定对象自己的可枚举字符串键属性 [key, value] 对的数组。

Object.values()

方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。

语法:Object.values(obj)

参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。

Object.prototype.hasOwnProperty()

hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。

如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。

语法:hasOwnProperty(prop)

参数:prop:要测试的属性的字符串名称或符号

返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。

Object.keys()

Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。

语法:Object.keys(obj)

参数:obj:要返回可枚举自身属性的对象。

返回值:表示给定对象的所有可枚举属性的字符串数组。

Object.prototype.toString()

toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。

语法:toString()

返回值:表示对象的字符串。 ​

Object.freeze()

Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。

语法:Object.freeze(obj)

参数:obj:要冻结的对象。返回值:传递给函数的对象。

对象和面向对象

对象:属性和方法的集合叫做对象(万物皆对象)。 面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。 面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。 创建对象的方式(4种):new Object、字面量、构造函数、原型。

怎么判断两个对象是否相等?

① 首先比较两个对象的长度,如果长度不相等使flag为false,为不相等; ② 如果长度相等那就遍历对象1(对象2也可以),利用hasOwnProperty() 【哈斯欧尼PROpt】方法查看对象1中是否包含对象2中的属性或者方法,如果不包含则使flag为false,为不想等。 hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
③ 接下来判断两对象的内存地址是否相同,不同则为true

什么是深拷贝,浅拷贝,浅拷贝 赋值的区别,如何实现

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

1.浅拷贝:

将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存

如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,

2.深拷贝:

创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象

3、赋值:

当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象

如何判断JS对象中是否存在循环引用

我个人的理解是,如果一个对象的值等于父级(祖父级,曾祖父级....),则说明是循环引用了

定义一个空数组吗,且对于目标对象进行递归,每次都判断递归项是否为对象,是的话就放到数组里,而且每次判断属性值是否存在,在的话说明环引用了

数组

数组的方法

1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组

2、splice( ):向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据

3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。

4、push( ):向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。

5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数 组。

6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。

7、reverse( ): 原数组倒序 它的返回值是倒序之后的原数组

8、concat( ):数组合并。

9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。

10、join( ):讲数组进行分割成为字符串 这能分割一层在套一层就分隔不了了

11、toString( ):数组转字符串;

12、toLocaleString( ):将数组转换为本地数组。

13、forEach( ):数组进行遍历;

14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。

15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。

16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。

17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。

18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个

19.isArray() 判断是否是数组

  1. indexOf 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1

  2. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1

  3. Array.of() 填充单个值

  4. Array.from() 来源是类数组

24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉 , 第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个

find 查找这一组数 符合条件的第一个数 给他返回出来

findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来 没有返回 -1

keys 属性名 values属性值 entries属性和属性值

forEach 循环便利 有3个参数 无法使用 break continue , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素

改变数组本身的api

  1. pop() 尾部弹出一个元素

  2. push() 尾部插入一个元素

  3. shift() 头部弹出一个元素

  4. unshift() 头部插入一个元素

  5. sort([func]) 对数组进行排序,func有2各参数,其返回值小于0,那么参数1被排列到参数2之前,反之参数2排在参数1之前

  6. reverse() 原位反转数组中的元素

  7. splice(pos,deleteCount,...item) 返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入items

  8. copyWithin(pos[, start[, end]]) 复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝

  9. arr.fill(value[, start[, end]]) 从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组 其他数组api不改变原数组

map 映射关系的数组 map 主要就是有返回值可以return 数组判断的会返回boolean

1、map()方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。

2、map()方法按照原始数组元素顺序依次处理元素。

注意:

map()不会对空数组进行检测。

map()不会改变原始数组。

map() 函数的作用是对数组中的每一个元素进行处理,返回新的元素。

filter 满足条件的都能返回 是一个数组

some返回boolean 循环数组 只要有一个成员通过了就会返回 true 反而 false

every返回boolean 循环数组 只有全部成员通过了就会返回 true 反而 false

reduce() 累加器 把上一次计算的值,给下一次计算进行相加

set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用

delete [1] delete 可以删除数组中的一向

Array.isArray() 用于确定传递的值是否是一个 [Array] (developer.mozilla.org/zh-CN/docs/…

flat 扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层

ES6数组的常用方法:

1、Array.from( ):将对象或字符串转成数组,注意得有length。

2、Array.of( ): 将一组值转换为数组。

3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换

target:从该位置开始替换数据;
start:从该位置开始读取数据,默认为0end:到该位置停止数据的读取,默认为数组的长度

4、find( ):用于找出第一个符合条件的数组成员。

5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

6、fill(value,start,end):使用给定值,填充一个数组。

value:填充的值;
start:开始填充的位置;
end:填充结束的位置。

7、keys( ):对键名的遍历。

8、values( ):对键值的遍历。

9、entries( ):对键值对的遍历。

10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。

11、flat( ):用于数组扁平,数组去除未定义。

12、flatMap( ):对原数组的每个成员执行一个函数。

13、Map( ):是一组键值对的结构,具有极快的查找速度。

14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

数组去重

  1. 使用ES6中的 new Set是最简单的去重方法: es6快速实现的过程,定义一个箭头函数,使用new Set 合并数组,再用sort方法去排序,最后Array.from生成新的数组

2、定义一个空数组,通过forEach循环,indexOf判断值是否是-1,如果是push到新的数组中

数组扁平

reduce h和 concat 实现 遍历数组每一项,若值为数组则递归遍历,否则concat。

数组排序

sort排序最简单的

字符串

字符串的方法

1、chartAt( ):返回在指定位置的字符;

2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并

3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1

4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引

5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符

6、substr( ):从起始索引号提取字符串中指定数目的字符;

7、substring( ):提取字符串中两个指定的索引号之间的字符;

8、toLowerCase( ):字符串转小写;

9、toUpperCase( ):字符串转大写;

10、valueOf( ):返回某个字符串对象的原始值;

11、trim( ):删除字符串两边的空格;

12、trimeState 取出开始的空格

13、trimeEnd 去除末尾空格

14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0

15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;

16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配

17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式

18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart

19、replace() 把指定的字符串替换成为别的字符

超长字符串存储到栈内存中

大家都知道,字符串属于基础类型,所以大家会觉得字符串是存在栈内存中的,但是大家要知道,V8默认栈内存是984Kib,那如果一个超长字符串 > 984Kib能装的进栈内存吗?这也就是一个比较经典的问题——大象装箱问题,试问:一头大象能装进一个小箱子里吗?

字符串的内容存于堆内存中,指针存于栈内存中,且相同的字符串指向同一个堆内存地址

新增或者修改字符串后,如果是一个之前不存在的字符串,则新开辟内存空间,如果是已有的,则直接使用已有的内存空间

当我们新建一个字符串时,V8会从内存中查找一下是否已经有存在的一样的字符串,找到的话直接复用。如果找不到的话,则开辟一块新的内存空间来存这个字符串,并把地址赋给变量。

大家有没有想过,为什么字符串不能通过下标索引来进行修改呢?因为字符串的修改本质上只能是通过整个的修改,而不能局部修改。

如何快速让字符串变成已千为精度的数字

var str = "10000000000";

第一种:把数字转换成字符串后,打散为数组,再从末尾开始,逐个把数组中的元素插入到新数组(result)的开头。 每插入一个元素,counter就计一次数(加1), 当counter为3的倍数时,就插入一个逗号,但是要注意开头(i为0时)不需要逗号。最后通过调用新数组的join方法得出结果。

第二种:通过正则表达式循环匹配末尾的三个数字,每匹配一次,就把逗号和匹配到的内容插入到结果字符串的开头,
然后把匹配目标(num)赋值为还没匹配的内(RegExp.leftContext)。如果数字的位数是3的倍数时,最后一次匹配到的内容肯定是三个数字, 但是最前面的三个数字前不需要加逗号;如果数字的位数不是3的倍数,那num变量最后肯定会剩下1到2个数字,循环过后,要把剩余的数字插入到结果字符串的开头。

javascript函数

声明函数的几种方式

函数声明

function 函数名(参数1,参数2,...){ //要执行的语句 }

函数表达式

var func2=function(b){}//函数表达式

var func3=function func4(c){}//命名式函数表达式

var func5=(function(n1,n2){})();//立即执行的函数表达式

return function(){ };//作为返回值的函数表达式

Function构造器

var 变量名 = new Function("参数1","参数2",...,"参数n","函数体");

立即执行函数

var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()

函数声明与函数表达式的区别

函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。

函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。

函数调用的几种方式

1.直接调用 函数名加上括号 ()

2.函数表达式 变量名()

函数的长度

函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

立即执行函数和使用场景

立即执行函数:( function( ){ })( ) 返回值可以为基本数据类型,也能返会任何类型的值。

写法原因:因为在 javascript 里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(), 然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。

作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。

使用场景: ①代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。 ②所有的这些工作只需要执行一次,比如只需要显示一个时间。 ③需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中, 不会让任何变量泄露成全局变量。

arguments 的对象是什么?

arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有length属性,可以arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。取之可以用 展开运算符来 数组和类数组类数组: ①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments ②不具有数组所具有的方法; ③类数组是一个普通对象,而真实的数组是Array类型。

常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($("div"));

this指向的问题(高频)

在全局的环境下this是指向window 的

普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window

在对象里调用的this,指向调用函数的那个对象,

在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象

方法中的this谁调用就指向谁。

箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this

函数式编程含义:

函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。 目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。

闭包

1、闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。

3、闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理

4、闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

5、为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染

闭包原理

函数执行分成两个阶段(预编译阶段和执行阶段)。

1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。

2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量

利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁, 但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

call、apply、bind封装与区别

都是来改变this指向和函数的调⽤,实际上call与apply的功能是相同的,只是两者的传参方式不一样,

call⽅法跟的是⼀个参数列表,

apply跟⼀个 数组作为参数,call⽅法和apply使⽤后就直接调⽤

bind 传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调⽤。

call 的性能要比apply好一点(尤其是当函数传递参数超过3个的时候)后期开发 call 多多一点

call 用扩展运算符就可以吧 apply 来代替了

bind 返回的函数可以作为构造函数吗?

不可以,会报错的哦, ERROR > Uncaught TypeError: s is not a constructor

具体操作:blog.csdn.net/qq_54753561…

函数柯里化(卡瑞化、加里化)?

概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。

特点: ①接收单一参数,将更多的参数通过回调函数来搞定; ②返回一个新函数,用于处理所有的想要传入的参数; ③需要利用call/apply与arguments对象收集参数; ④返回的这个函数正是用来处理收集起来的参数。

作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。

用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。

柯里化函数例子

柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。

我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是js的柯里化函数

es6和es5的继承(继承不用搞那么麻烦,项目中还是用 class)

原型链继承

父类的实例作为子类的原型,易于实现, 只能继承 原型上的方法,不能继承构造函数里的属性和方法

 原型链继承
 实现:
     父类的实例作为子类的原型
 优点:
     简单,易实现 不限制调用方式
 缺点:
 	   只能继承 原型上的方法 不能继承构造函数里的属性和方法

function Person() {
    this.a = true
}
Person.prototype.fn = function () {
    console.log('我是父类')
}

function Son() {
    this.b = false
}
 这里是关键 创建 Person 的实例 然后赋值给 Son 的原型
Son.prototype = new Person()
let xiaoming = new Son()
xiaoming.fn()
console.log(xiaoming.a)

构造函数继承

不能继承原型属性/方法,可以实现多继承,可以传参,无法复用,

构造函数继承
实现:
 Father.call(this),创建子类实例时调用Father构造函数,
 于是SubType的每个实例都会将Father中的属性复制一份。
优点:
  解决了子类构造函数向父类构造函数中传递参数
缺点:
无法实现复用,不能继承原型属性/方法,只能继承父类的实例属性和方法

function Father(age, name) {
    this.a = '22'
}
function Children() {
    Father.call(this)
}
let Class = new Children()
console.log(Class.a);

组合继承

通过call 对实例属性的继承,原型链对原型方法的继承, 调用多次

组合继承 实现: 组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造 函数技术来实现实例属性的继承。 优点: 既能调用父类实例属性,又能调用父类原型属性 调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用 可以继承属性和方法,并且可以继承原型的属性和方法 缺点: 缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。

function Father(name) { // 这里的name 就是 Son 传过来的
    this.name = name
    this.colors = [1, 2]
}

Father.prototype.sayName = function () {
    alert(this.name)
}

function Son(name, age) {
    //继承实例属性,第一次调用Father()
    Father.call(this, name) // 这里通过 call 传过去 name  继承实例属性
    this.age = age
}
Son.prototype = new Father() // 继承父类方法,第二次调用 Father
Son.prototype.ff = function () { // 子类的方法
    console.log('666');
}
var aa = new Son('小明', 5)
aa.colors.push(3)
console.log(aa); // 打印了 父类的属性 和方法   以及子类的方法
var bb = new Son('小红', 50)
aa.colors.push(999999)
console.log(bb); // 打印了 父类的属性 和方法   以及子类的方法

Es6有class继承:

首先利用class构造一个父类,然后利用extends与super实现子类继承

ES6类继承extends extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。 class Rectangle { // constructor constructor(height, width) { this.height = height; this.width = width; }

// Getter
get area() {
    return this.calcArea()
}

// Method
calcArea() {
    return this.height * this.width;
}

}

const rectangle = new Rectangle(10, 20); console.log(rectangle.area); // 输出 200


// 继承 class Square extends Rectangle {

constructor(length) { super(length, length);

// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';

}

get area() { return this.height * this.width; } }

const square = new Square(10); console.log(square.area); // 输出 100