JavaScript面试题

1.var,let和const之间的区别

  • 变量提升
    - var声明的变量存在变量提升
    - let和const不存在变量提升
  • 块级作用域
    - var不存在块级作用域
    - let和const存在块级作用域
  • 暂时性死区
    - var不存在暂时性死区
    - let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
  • 重复声明
    - var允许重复声明变量
    - let和const在同一作用域下不允许重复声明变量
  • 修改声明的变量
    - var和let可以修改声明的变量
    - const声明一个只读的变量

2.ES6中数组新增了哪些扩展

  • 扩展运算符…
  • Array.from()
    将类数组转为真正的数组,还可以接受第二个参数,用来对每个元素进行处理,将处理后的值放入返回的数组
    类数组:类数组这个对象必须包含length属性;类数组中key是以数字或者数字的字符串组成。
  • Array.of()
    用于将一组值,转换为数组。注: 没有参数的时候,返回一个空数组;当参数只有一个的时候,实际上是指定数组的长度;参数个数不少于2个时,Array()才会返回由参数组成的新数组
  • copyWithin()
    将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,可以接受三个参数
    - target(必需):复制到指定目标索引位置。
    - start(可选):从该位置开始读取数据,默认为0
    - end(可选):到该位置前停止读取数据,默认等于数组长度
  • find()
    找出第一个符合条件的数组成员,参数是一个回调函数,函数接受三个参数:依次是当前的值,当前的位置和原数组。如果所有成员都不符合条件,则返回undefined
  • findIndex()
    返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
  • fill()
    使用给定值,填充一个数组,还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
  • entries(),keys(),values()
    entries():对键值对进行遍历
    keys:对键名进行遍历
    values:对键值进行遍历
  • includes()
    用于判断数组是否包含给定的值
  • flat()
    将数组扁平化处理,返回一个新数组,对原数组没有影响。flat()默认只会拉平一层,如果想要拉平多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1
  • sort()
    数组排序

3.ES6中对象新增了哪些扩展

  • 解构赋值…
  • 扩展运算符
  • 属性遍历
    - for…in…:循环遍历对象自身的和继承的可枚举的属性
    - Object.keys():返回一个数组,可枚举属性的键名
  • Object.is
    严格判断两个值是否相等,与严格比较运算符(===)的行为一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身
  • Object.assign()
    用于对象合并,第一个参数是目标对象,后面的参数都是源对象
  • Object.keys()
    返回自身所有可遍历属性的键名的数组
  • Object.values()
    返回自身所有可遍历属性的键值的数组
  • Object.entries()
    返回自身所有可遍历属性的键值对的数组
  • Object.fromEntries()
    用于将一个键值对数组转为对象

4.ES6中函数新增了哪些扩展

  • 允许为函数的参数设置默认值
    function ( x, y = ‘a’ ) {}
  • 箭头函数

5.ES6中的Set,Map,WeakSet和WeakMap

  • Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
    - 集合:是由一堆无序的,相关联的,且不重复的元素组成的集合
    - 字典:是由一些元素的集合,每个元素有一个称作key的域,不同元素的key各不相同

  • Set的实例关于增删改查的方法:
    - add(): s.add(1) 返回Set结构本身
    - delete(): s.delete(1) 返回一个布尔值,表示是否删除成功
    - has(): s.has(1) 返回一个布尔值,判断该值是否为Set的成员
    - clear(): 清除所有成员,返回布尔值

  • Set实例遍历的方法:
    - keys():返回键名的遍历器
    - values(): 返回键值的遍历器
    - entries(): 返回键值对的遍历器
    - forEach(): 使用回调函数遍历每个成员

  • Map结构的实例针对增删改查有以下属性和操作方法:
    - size属性:返回Map结构的成员总数
    - set():m.set( ‘a’, 1)设置value,返回当前Map对象
    - get(): m.get(a) 获取key对应的键值,如果找不到key,返回undefined
    - has(): 返回一个布尔值,表示某个值是否在当前Map对象之中
    - delete(): 删除某个键,返回true,如果删除失败,返回false
    - clear(): 清除所有成员,没有返回值

  • WeakSet
    - WeakSet和Set的区别

    • 没有遍历操作的API
    • 没有size属性
    • WeakSet只能是引用类型,而不能是其他类型的值
let ws = new WeakSet()
// 不是引用类型
let weakSet = new WeakSet([2,3])
console.log(weakSet)  // 报错
// 成员为引用类型
let obj1 = { name: 1 }
let obj2 = { name: 1 }
let ws = new WeakSet([obj1,obj2])
console.log(ws)
WeakSet里面的引用只要在外部消失,它在WeakSet里面的引用就会自动消失
  • WeakMap

    • WeakMap和Map的区别
      - 没有遍历操作的API
      - 没有clear清除方法
      - WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
      - WeakMap的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不再手动删除引用

6.Promise

  1. Promise是异步编程的一种解决方案,解决了回调地狱

    • Promise对象有三种状态:
      • pedding(进行中)
      • fulfilled(已成功)
      • rejected(已失败)
    • 从pedding变为fufilled和从pedding变为rejected,就不会再变,任何时候都可以得到这个结果。
      Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和rejected
      • resolve函数的作用是,将Promise对象的状态从未完成变成成功
      • rejected函数的作用是,将Promise对象的状态从未完成变为失败
  2. Promise.all() : 只有数组里面的promise全部成功才返回成功,要不就失败。

    • Promise.race():哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
    • Promise.allSettled(): 只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

7.ES6中Generator

Generator函数是ES6提供的一种异步编程解决方案; Generator函数是一个普通函数,但是有两个特征:
- function关键字与函数名之间有一个星号
- 函数体内部使用yield表达式,定义不同的内部状态

function* helloWorldGenerator() {
		yield 'hello';
		yield 'world';
		return 'ending';
}
var hw = helloWorldGenerator()
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

8.ES6中Proxy

用户定义基本操作的自定义行为

  • 用法:
    var proxy = new Proxy(target, handler)
    target: 表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理)
    handler:通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理P的行为

  • 使用场景:
    1. 拦截和监视外部对对象的访问
    2. 降低函数或类的复杂度
    3. 在复杂操作前对操作进行校验或多所需资源进行管理

  • get:

    • get(target, property, receiver)方法用于拦截某个属性的读取操作,可以接受三个参数,分别为目标对象、属性名和 proxy 实例本身
  • set:

    • set(target, property, value, receiver)方法用来拦截某个属性的赋值操作,四个参数依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

9.ES6中Decorator

Decorator,即装饰器
装饰对象为下面两种:
- 类的装饰
- 类属性的装饰

类的装饰器:
@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

类属性的装饰:
当对类属性进行装饰的时候,能够接受三个参数
- 类的原型对象
- 需要装饰的属性名
- 装饰属性名的描述对象

	class Person {
        // readonly {Person.prototype, 'name', descriptor}
        // 类似于
        // Object.defineProperty(Person.prototype, 'name', descriptor)
        @readonly
        name() { return `${this.first} ${this.last}`}
    }
	// 第一个参数类的原型对象,第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象
	function readonly(target,name,descriptor) {
        // descriptor对象原来的值如下
        // {
        // 	  value: specifiedFunction,
        //    enumerable: false,
        //    configurable: true
        //    writable: true
        // }
        
        descriptor.writable = false;
        return descriptor
	}

如果一个方法有多个装饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行

装饰器不能用于装饰函数,因为函数存在变量提升情况

10.深拷贝和浅拷贝及如何实现

浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存;修改新对象影响原对象
深拷贝:会创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象
区别:浅拷贝只复制对象的第一层属性,深拷贝可以对对象的属性进行递归复制

  • 深拷贝实现方法:
    - JSON方法:缺点,不能深拷贝函数,underfined,symbol
    - 函数库lodash的_.cloneDeep方法
    - 递归
    浅拷贝实现方法:
    - object.assign(目标对象,源对象),源对象的所有可枚举属性都复制到目标对象上
    - 扩展运算符
    - concat方法

浅拷贝:

function shallowClone(obj){
	const newObj = {}
	for(let prop in obj) {
		if(obj.hasOwnProperty(prop)){
			newObj[prop] =  obj[prop]
		}
	}
	return newObj
}

深拷贝:

function deepClone(obj) {
	if( obj === null ) return obj;
	if( obj instanceof Date ) return new Date(obj);
	if( obj instanceof RegExp ) return new RegExp(obj)
	if( typeof obj !== 'object' ) return obj
	let cloneObj = new obj.constructor()
	// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
	for( let key in obj) {
		if( obj.hasOwnProperty(key)) {
			cloneObj[key] = deepClone(obj[key])
		}
	}
	return cloneObj
}

11.数组常用方法

  • 增:
    push() 添加到数组末尾,返回数组的最新长度
    unshift() 在数组开头添加任意多个值,返回新的数组长度
    splice() 传入三个参数,分别是开始位置,0(要删除的元素数量),插入的元素,返回空数组,改变原数组
    concat() 返回拼接好的新数组,不会影响原始数组

  • 删:
    pop() 删除数组的最后一项,同时减少数组的length值,返回被删除的项
    shift() 删除数组的第一项,返回被删除的项
    splice() 传入两个参数,分别是开始位置,删除元素的数量,返回删除元素的数组
    slice(),两个参数,开始的位置,结束的位置

  • 改:
    splice()

  • 查:
    indexOf() 返回要查找的元素在数组的位置,如果没找到则返回-1
    includes() 返回要查找的元素是否在数组中,找到返回true,否则fasle
    find() 返回第一个匹配的元素

  • 排序:
    reverse() 反转
    sort() 排序

  • 转换:
    join()

12.字符串常用方法

  • 增:
    concat()

  • 删:
    slice() 两个参数,开始的位置,结束的位置,返回删除的元素,不包含结束
    substr() 两个参数,开始位置,截取的长度
    substring() 两个参数,开始位置,结束位置

  • 改:
    trim(),trimLeft(),trimRight()
    repeat() 传入整数,表示复制几次
    padStart(),padEnd() 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
    toLowerCase(),toUpperCase()

  • 查:
    chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定
    indexOf() 从宏观字符串开头去搜索传入的字符串,并返回位置
    startWith()
    includes()

  • 转换:
    split()

  • 模板匹配方法:
    match(): 接受一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组
    search(): 接受一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回-1
    replace(): 接受两个参数,第一个参数为匹配的内容,第二个参数为替换的元素

13.JavaScript类型转换机制

  • 显示转换:
    - Number()
    - parseInt()
    - String()
    - Boolean()
  • 隐式转换:
    - 比较运算符
    - 算术运算符

14.闭包及其使用场景

一个函数和对其周围状态的引用捆绑在一起
闭包可以让你在一个内部函数中访问到外层函数的作用域

  • 使用场景:
    - 延长变量的生命周期
    - 函数柯里化

15.作用域链

作用域: 即变量和函数生效的区域或集合,换句话说,作用域决定了代码区块中变量和其他资源的可见性。

  • 作用域分为:
    - 全局作用域
    - 函数作用域
    - 块级作用域

  • 作用域链:当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域

16.原型和原型链

  • 每个构造函数都有一个原型属性 protoType 保存了所有用构造函数实例化出来的对象和方法,用构造函数创建一个对象,这个对象上会有一个属性__proto__指向构造函数的原型属性。
  • 当我们使用一个对象的属性或者方法的时候,首先在自身内存查找,找到就用,找不到就去原型上找。原型的本质就是对象,对象又有自己原型,原型如果没有这个方法,就去原型的原型中查找,这个查找的链条就叫做原型链,最后找到Object.protoType,如果Object.protoType没有这个属性,直接返回undefined,因为Object.protoType的原型是null

17.如何实现继承

  1. ES6
class Parent{
		constructor(){
		  this.age = 18;
		}
}class Child extends Parent{
		constructor(){
		  super();
		  this.name = '张三';
		}
}
let o1 = new Child();
console.log( o1,o1.name,o1.age );
  1. 原型链继承
function Parent(){
	this.age = 20;
}
function Child(){
	this.name = '张三'
}
Child.prototype = new Parent();
let o2 = new Child();
console.log( o2,o2.name,o2.age );

原型链继承的缺点:每个实例对引用类型属性的修改都会被其他的实例共享

  1. 借用构造函数继承
function Parent(){
	this.age = 22;
}
function Child(){
	this.name = '张三'
	Parent.call(this);
}
let o3 = new Child();
console.log( o3,o3.name,o3.age );

构造函数继承的优点:解决了每个实例对引用类型属性的修改都会被其他的实例所共享的问题。
缺点:无法复用父类的公共函数,每次子类构造实例都得执行一次父类函数

  1. 组合式继承
function Parent(){
	this.age = 100;
}
function Child(){
	Parent.call(this);
	this.name = '张三'
}
Child.prototype = new Parent();
let o4 = new Child();
console.log( o4,o4.name,o4.age );
  1. 寄生组合式继承
function inheritPrototype(Parent, Child){
           Child.prototype = Object.create(Parent.prototype) //创建父类原型的一个副本,把副本赋值给子类原型
           Child.prototype.constructor = Child;
 }
function Parent(name) {
    this.name = name
}
Parent.prototype.getName = function () {
       console.log(this.name)
}
function Child(color) {
     Parent.call(this, 'arzh')
     this.color = color
}
inheritPrototype(Parent, Child)
var arzhChild = new Child('red')
console.log(arzhChild.name) // 'arzh'
  

18.谈谈对this的理解

this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

19.JavaScript的事件模型

  • 事件流都会经历三个阶段:
    - 事件捕获阶段
    - 处于目标阶段
    - 事件冒泡阶段

20.typeof 和 instanceof 区别

  • typeof 操作符返回一个字符串,表示未经计算的操作数的类型,返回一个变量的基本类型(返回值有number,string,boolean,function,undefined,object)
  • instanceOf运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上,返回一个布尔值

21.new操作符具体干了什么

  • 创建一个新的对象
  • 将对象与构造函数通过原型链链接起来
  • 将构造函数中的this绑定到新建的对象上

22.ajax的原理

ajax:通过xmlHttpRequest对象来向服务器发送异步请求,从服务端获取数据(onreadystatechange)

  • 如果使用GET请求发送数据的时候,需要注意如下:
    - 将请求数据添加到open()方法中的url地址中
    - 发送请求数据中的send()方法中参数设置为null

23.bind,call和apply的区别

  • apply(): 接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入。改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
  • call(): call方法的第一个参数是this的指向,后面传入的是一个参数列表,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
  • bind(): 第一个参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)。改变this指向后不会立即执行,而是返回以恶搞永久改变this指向的函数

24.谈谈对事件循环的理解

JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then,MutationObserver,宏任务的话就是setImmediate setTimeout setInterval

  • 在javaScript中,所有的任务都可以分为:
    - 同步任务: 立即执行的任务,同步任务一般会直接进入到主线程中执行
    - 异步任务: 异步执行的任务,比如ajax网络请求,setTimeout定时函数

  • 异步任务执行顺序:事件队列其实是一个先进先出的数据结构,排在前面的事件会优先被主线程读取

    • 微任务:
      • Promise.then
      • MutaionObserver
      • Object.observe(已废弃,Proxy对象代替)
      • procress.nextTick
    • 宏任务:
      • script(可以理解为外层同步代码)
      • setTimeout/setInterval
      • UI rendering/UI事件
      • postMessage,MessageChannel
      • setImmediate,I/O
  • 微任务和宏任务执行机制:
    - 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
    - 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面所有微任务依次执行完

25.DOM常见操作创建节点:

  • createElemet: 创建新元素,接受一个参数,即要创建元素的标签名
    const divEl = document.createElement(‘div’)

    • createTextNode: 创建一个文本节点
      const textEl = document.createTextNode(‘content’)

    • createDocumentFragment: 用来创建一个文档碎片,它表示一个轻量级的文档,主要是用来存储临时节点,然后把文档碎片的内容一次性添加到DOM中
      const fragament = docment.createDocmentFragment()
      当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragement自身,而是它的所有子孙节点

  • 获取节点:
    - querySelect: 传入任何有效的css选择器
    - querySelectorAll:返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表

  • 更新节点:
    innerHTML
    innerText,textContent
    style: x.style.color = ‘#fff000’

  • 添加节点:
    appendChild: 把一个子节点添加到父节点的最后一个子节点
    insertBefore: 把子节点插入到指定的位置
    parentElement.insertBefore(newElment,referenceElement)
    子节点会插入到referenceElement之前

  • setAttriBute:在执行元素中添加一个属性节点,如果元素中已有该属性改变属性值
    div.setAttribute(‘class’,‘white’) // 第一个参数属性名,第二个参数属性值

  • 删除节点:
    removeChild

26.BOM常见操作

  1. window
  2. location
  3. navigator
  4. screen
  5. history

27.递归及其使用场景

// 计算x的n次方
function pow(x, n) {
	if( n == 1) {
		return x
	} else {
		return x * pow(x, n - 1)
	}
}
// 斐波那契数列
function factorial2 (n, start = 1, total = 1) {
	   if(n <= 2){
	       return total
	   }
	   return factorial2 (n -1, total, total + start)
}
// 数组扁平化
let a = [1,2,3, [1,2,3, [1,2,3]]]
// 变成
let a = [1,2,3,1,2,3,1,2,3]
// 具体实现
function flat(arr = [], result = []) {
	   arr.forEach(v => {
	       if(Array.isArray(v)) {
	           result = result.concat(flat(v, []))
	       }else {
	           result.push(v)
	       }
	   })
	   return result
}

28.内存泄漏

内存泄漏:由于疏忽或错误造成程序未能释放已经不再使用的内存

  • 常见内存泄漏情况:
    1. 意外的全局变量
    2. 定时器
    3. 闭包

29.cookie,localStroage和sessionStroage的区别

  1. 存储大小:cookie数据大小不能超过4k,sessionStroage和localStroage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  2. 有效时间:localStroage存储持久化,浏览器关闭后数据不丢失,sessionStroage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
  3. 数据与服务器之间的交互方式:cookie的数据会自动的传递到服务器,服务端也可以写cookie到客户端,sessionStroage和localStroage不会自动把数据发给服务器,仅在本地保持
  4. 作用域:sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;cookie、localstorage在所有同源窗口之间共享
  • 使用场景:
    - 标记用户与跟踪用户行为的情况,使用cookie
    - 适合长期保存在本地的数据,推荐使用localStroage
    - 敏感账号一次性登录,使用sessionStroage

30.函数式编程

  • 优点
    更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况
    更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响
    更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性
    隐性好处。减少代码量,提高维护性
  • 缺点:
    性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
    资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式
    递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作

31.如何是实现函数缓存

函数缓存,就是将函数运算过的结果进行缓存

  • 实现方式:
    1. 闭包
    2. 函数柯里化

32.防抖,节流

相同:在不影响客户体验的前提下,将频繁的回调函数,进行次数缩减,避免大量计算导致的页面卡顿
不同:防抖是将多次执行变为最后一次执行,就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行事件
节流:将多次执行变为在规定时间内只执行一次,就是指连续触发事件但在n秒内只执行一次函数(下拉加载更多)

  • 防抖:
function debounce(func,wait) {
	let timeout;
	return function () {
		let context = this; // 保存this指向
		let args = arguments; // 拿到event对象
		clearTimeout(timeout)
		timeout = settimeout(function(){
			func.apply(context,args)
		},wait)
	}
}
防抖如果需要立即执行,需要加入第三个参数用于判断
function debounce(func,wait,immediate){
	let timeout;
	return function() {
		let context = this;
		let args = arguments;
		if (timeout) clearTimeout(timeout)
		if(immdiate) {
			let callNow = !timeout;
			timeout = setTimeout(function(){
				timeout = setTimeout(function(){
					timeout = null
				})
			},wait)
			if(callNow) {
				func.apply(context,args)
			}
		} else{
			timeout = setTimeout(function(){
				func.apply(context,args)
			},wait)
		}
	}
}
  • 节流:
function throttled(fn,delay = 500) {
	let oldTime = Date.now()
	return function(...args) {
		let newTime = Date.now()
		if(newTime - oldTime >= delay) {
			fn.apply(null,args)
			oldTime = Date.now()
		}
	}
}
	
function throttled2(fn, delay = 500) {
	    let timer = null
	    return function (...args) {
	        if (!timer) {
	            timer = setTimeout(() => {
	                fn.apply(this, args)
	                timer = null
	            }, delay);
	        }
	    }
	}

33.web常见攻击

  • XSS攻击:
    攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息和Cookie,SessionID等,进而危害数据安全

    • 防范XSS攻击:
      httpOnly: 在cookie中设置httpOnly属性后,js脚本将无法读取到cookie信息
      输入过滤:1.检测按照规定的格式输入
      2.转义HTML,把引号,尖括号,斜杠进行转义
  • CSRF攻击:
    一种挟持用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法

    • 防御CSRF攻击:
      验证码:
      设置token

34.地址栏输入地址回车发生了什么

1.解析URL:对url进行解析,判断所需要使用的传输协议和请求的资源的路径是否合法,如果合法,浏览器会检查url中可能存在的非法字符然后进行转义,进而进行下一个过程;不合法的话会将输入的内容传递给搜索引擎。
2.缓存判断:浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。
3.DNS解析:获取输入的URL中的域名的IP地址,首先会判断本地是否有该域名的IP地址的缓存,如果有则使用,如果没有则向本地DNS服务器发起请求。
4.获取MAC地址:数据传输还需要知道目的主机的MAC地址。从应用层一直下发到数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的MAC地址需要分情况处理。如果与请求主机在同一个子网里,可以使用APR协议获取到目的主机的MAC地址,否则转发给网关,由它代为转发。
5.TCP三次握手:如下
7.返回数据:服务端返回一个html文件作为响应,浏览器接收到响应后,开始对html文件进行解析,开始页面的渲染过程。
8.页面渲染:html文件生成DOM树,css文件生成CSSDOM树,如果遇到script标签,则判断是否含有defer或者async属性,要不然script的加载和执行会造成页面的渲染的阻塞。根据DOM树和CSSDOM树来工构建渲染树,进而进行布局渲染。

// 三次握手
// SYN(联机)ACK(确认)FIN(结束)

  1. 第一次握手:客户端采用TCP协议将带有SYN标志的数据包发送给服务器,等待服务器确认
  2. 第二次握手:服务端在接受到SYN数据包后,必须确认SYN,并发送ACK标志,同时,自己也将会向客户端发送一个SYN标志
  3. 第三次握手:客户端在接收到服务端的SYN+ACK,自己会向服务器发送ACK包。完成三次握手。此时客户端和服务器正式建立了连接,开始传输数据

// 四次挥手

  1. 当客户端的数据传输到尾部时,客户端向服务器发送带有FIN标志的数据包,使其明白自己准备断开通信了。
  2. 因为TCP的通信是使用全双工通信的,所以在断开连接的时候也应该是双向的;当服务端收到带有FIN标志的数据包时,其必不会直接发送FIN标志标志断开通信的请求,而是先发送一个带有ACK标志的应答信息,使客户端明白服务器还有数据要进行发送。
  3. 服务端的数据发送完成后,向客户端发送带有FIN标志的数据包,通知客户端断开连接
  4. 当客户端收到FIN后,担心某些不可控制的因素导致服务器不知道他要断开连接,会发送ACK进行确认,同时把自己设置成TIME_WAIT状态并启动定时器,在TCP的定时器到达后客户端并没有接收到请求,会重新发送;当服务器收到请求后就断开连接

35.遍历数组的方法及其返回值

遍历数组的方法有:some(),every(),forEach(),filter(),map()
- some(): 如果至少有1个元素返回true,则这个方法返回true
- every(): 如果所有元素都返回true,则这个方法返回true
- forEach(): 对数组每一项都运行传入的函数,没有返回值
- filter(): 对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回
- map() 返回每次函数调用的结果构成的数组

36.重绘,回流

  • 回流
    当元素的尺寸,布局,隐藏等改变而需要重新构建时,就称之为回流
  • 重绘
    当元素的外观,风格改变,不会影响布局的时候,就称之为重绘
    回流必将引起重绘,而重绘不一定会影响回流
  • 如何避免重绘和回流
    • 尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。
    • 避免设置多层内联样式,css选择符从右往左匹配查找,避免节点层级过多。
    • 使用动画时尽可能使用requestAnimationFrame。
    • 使用visibility替换display: none。因为前者只会引起重绘,后者会引发回流

37.强缓存和协商缓存

  • 缓存的优点:
    - 减少了不必要的数据传输,节省带宽
    - 减少服务器的负担,提升网站性能
    - 加快了客户端加载网页的速度
    - 用户体验友好

  • 缺点:
    资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕。

  • 强缓存:
    当浏览器去请求某个文件的时候,服务端就在respone header里面对该文件做了缓存配置。缓存的时间、缓存类型都由服务端控制,具体表现为:

    • respone header 的cache-control,常见的设置是max-age public private no-cache no-store等
    • max-age: 缓存的时间
    • public: 所有内容都将被缓存(客户端和代理服务器都可缓存)
    • private: 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
    • no-cache: 必须先与服务器确认返回的响应是否被更改
    • no-store: 所有内容都不会被缓存到缓存或 Internet 临时文件中
  • 协商缓存:
    强缓存就是给资源设置个过期时间,客户端每次请求资源时都会看是否过期;只有在过期才会去询问服务器。所以,强缓存就是为了给客户端自给自足用的。而当某天,客户端请求该资源时发现其过期了,这是就会去请求服务器了,而这时候去请求服务器的这过程就可以设置协商缓存。
    response header里面的设置
    etag:每个文件有一个,改动文件了就变了,就是个文件hash,每个文件唯一,就像用webpack打包的时候,每个资源都会有这个东西
    last-modified:文件的修改时间,精确到秒

38.垃圾回收机制

JavaScript具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中使用的内存

  • 原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放内存
    有以下两种实现方式:
    1. 标记清除
    2. 引用计数

  • 标记清除:当变量进入执行环境时,会标记一个状态,当离开执行环境时,会标记一个离开的状态。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存

  • 引用计数:如果一个值的引用次数时0,就表示这个值不再用到了,因此可以将这块内存释放

39.preload和prefetch的区别

preload 告诉浏览器立即加载资源;
prefetch 告诉浏览器在空闲时才开始加载资源;也就是预加载

40.commonjs,ES6模块化,AMD,CMD的区别

  1. commonJS是服务端的实现,moudle.exports或exports来暴露模块,通过require来加载模块。同步加载,也就是只有加载完成,才能执行后面的操作。代码都运行在模块作用域,不会造成全局污染
  2. AMD规格是异步加载,允许指定回调函数。通过define()方法定义模块:require([module],callback),通过require方法方法加载模块:define(id,[depends],callback),推崇依赖前置
  3. ES6一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。export 命令用于规定模块的对外接口。import 命令用于输入其他模块提供的功能。
  4. CMD依赖于seajs,是异步加载,延后加载,就近加载,用时加载

41.让异步请求执行为同步效果有什么办法

推荐使用async await写法
当然也可以用Promise.all 统一处理所有Promise, 并且按Promise数组内顺序执行then后的内容

42.async await和promise的区别

async await是基于Promise实现的, 可以说是改良版的Promise,他不能用于普通的回调函数。Promise的出现解决了传统callback函数导致的“地狱回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。

43.Promise.then与async await怎么捕获异常

  • Promise:
  1. promise(实例).then后面第二个参数可以捕获到异常。
let promise = new Promise((resolve, reject) => {
	      reject('错误');
});

promise.then(() => {}, (error) => {
       console.log(error); //'错误'
});
  1. promise(实例).catch也可以捕获到异常
let promise = new Promise((resolve, reject) => {
	   reject('错误');
});


promise.catch((error) => {
     console.log(error);
});
  • async await
  1. async函数返回的是一个Promise,所以我们可以在外面catch住错误。
async function test() {
	   await new Promise((resolve, reject) => reject('错误'));
};
test().catch((data) => console.log(data)); //错误
  1. try…catch
async function test() {
	  try {
	      await new Promise((resolve, reject) => reject('错误'));
      } catch (error) {
        console.log(error); //错误
	};
};
test();

44.跨域

跨域本质是浏览器基于同源策略的一种安全手段

  • 同源具有以下三个相同点:
    1.协议相同
    2.域名相同
    3.端口相同

  • 解决方案:
    1. JSONP
    - 去创建一个script标签
    - script的src属性设置接口地址
    - 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
    - 通过定义函数名去接受返回的数据

//动态创建 script
var script = document.createElement('script');

// 设置回调函数
function getData(data) {
       console.log(data);
}

//设置 script 的 src 属性,并设置请求地址
script.src = 'http://localhost:3000/?callback=getData';

// 让 script 生效
document.body.appendChild(script);
JSONP的缺点: JSONP只支持get,因为script标签只能使用get请求;
  1. CORS
    服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求
  2. Proxy代理

45.get和post和区别

  1. get传递参数在url中,长度有限制,post传递的数据在请求体中,长度相对大一些
  2. get请求可以加入收藏夹进行缓存,post不可以
  3. post请求主要用来传递大量数据做新增操作,get请求主要用来获取数据
  4. get请求刷新服务器或者回退没有影响,post请求回退时会重新提交数据请求。
  5. get请求可以被缓存,post请求不会被缓存

46.http和https的区别

1.https需要证书
2.http是超文本传输协议,是明文传输,https则是具有安全性的ssl的加密传输协议
3.http和https使用的端口不同,前者是80,后者是443
4.http的连接很简单,无状态;https是由ssl+http构建的可进行加密传输,身份认证的网络协议,比http协议安全

47.https传输过程

  1. 在服务器端存在一个公钥及私钥
  2. 客户端从服务器取到这个公钥
  3. 客户端产生一个随机的密钥
  4. 客户端通过公钥对密钥进行加密(非对称加密)
  5. 客户端发送到服务器端
  6. 服务端接受这个密钥并且以后的服务器端和客户端的数据全部通过这个密钥加密

48.script标签里defer和async的区别

defer和async虽然都是异步加载js,但是async是加载完js后立马执行,而defer要等待之前渲染完再去进行执行。

浏览器遇到script标签时,会立即加载并执行指定的脚本,执行完毕之后,才继续解析后面的标签;有async属性,浏览器遇到该script标签时,加载脚本的过程与解析后面标签的过程同时进行,加载完毕后,立即执行该脚本;有defer属性,浏览器遇到该script标签时,加载脚本的过程与解析后面标签的过程同时进行。但是执行脚本的过程,在解析完所有元素之后才执行。

49.TCP和UDP的区别

  1. TCP是面向链接的,而UDP是面向无连接的
  2. TCP仅支持单播传输,UDP提供了单播,多播,广播的功能
  3. TCP的三次握手保证了连接的可靠性;UDP是无连接的,不可靠的一种数据传输协议
  4. UDP的头部开销比TCP的更小,数据传输速率更高,实时性更好

50.304过程

a. 浏览器请求资源时首先命中资源的Expires 和 Cache-Control,Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效,可以通过Cache-control: max-age指定最大生命周期,状态仍然返回200,但不会请求数据,在浏览器中能明显看到from cache字样。
b. 强缓存失效,进入协商缓存阶段,首先验证ETagETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据客户端上送的If-None-Match值来判断是否命中缓存。
c. 协商缓存Last-Modify/If-Modify-Since阶段,客户端第一次请求资源时,服务服返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间。再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。

  • 强制缓存
    强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是 Expires 和 Cache-Control,其中Cache-Control优先级比Expires高。
    强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:

    • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
    • 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存。
    • 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果
  • 协商缓存
    协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since 和 Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。协商缓存主要有以下两种情况:

    • 协商缓存生效,返回304
    • 协商缓存失效,返回200和请求结果结果

51.forEach怎么停止

运用抛出异常(try catch)可以终止foreach循环

try {
    [1,2,3,4,5,6].forEach(function(item, index){
	        console.log(item);
	        if(item === 3){
	            throw new Error('阻止');
	        }
	    });
	} catch (error) {
	    console.log('error_阻止成功', error);
}

52.webSocket 和 ajax 的区别

  1. 本质不一样:ajax是异步javascript和xml,是一种建立交互的开发技术。webSocket而是浏览器和服务器的全双工通信。
  2. 生命周期不一样:websocket创建的是长链接,在一个会话中一直保持链接;而ajax是短链接,发送和接收完成数据后就会断开连接。
  3. 适用方位不一样:websocket适用于实时数据交互,而ajax适用于非实时数据交互
  4. 发起人不一样:ajax是客户端发起请求的,而websocket服务器和客户端能够相互推送消息。
  5. 用法不同

53.!!和??

!!是将一个值强制转换为Boolean值
??作用是当一个表达式是null或者undefined时为变量设置一个默认值

54.diff算法

diff算法是调和的具体表现,将虚拟dom树转换成真实dom最少的操作过程称之为调和。
策略一:tree diff 分层求异

  • 同一层级进行比较,如果发现不同,直接删除,创建
  • 如果跨层级的操作,并不是移动,而是重新渲染
  • 所以react官方建议不要进行DOM节点跨层级操作

策略二:component diff 相同类生成类似树形结构,不同类生成不同树形结构

  • 组件之间进行比较,如果是同一类型的组件,按照策略一进行比较
  • 反之,则将该组件判断为dirty component(脏组件),从而替换整个组件下的所有子节点。

策略三:element diff 设置唯一key

  • 允许开发者对同一层级的同组子节点,添加唯一key进行区分

55.super()和super(props)

super(props)将传入的props,赋值给组件实例props属性中,如果只调用了super(),那么this.props在super()和构造函数结束之间仍是undefined

super这个关键字,即可以当作函数使用,也可以当做对象使用。
	1.当做函数使用:
		class A {}
		class B extends A {
            constructor() {
                super()
			}
        }
	在constructor中必须调用super方法,因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,而super就代表了父类的构造函数。super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于 A.prototype.constructor.call(this, props)
	2.当作对象使用
    	在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

63.promise内部是如何实现的,generator函数内部是如何实现的,如何通过generator实现一个async await方法

地址

64.基本数据类型和引用数据类型有什么区别

  1. 存储方式:基本数据类型存储在栈里,而引用数据类型存储在堆
  2. 使用方式:引用数据类型使用时,使用的时指向这个数据的指针,而基本数据类型使用的时数据本身

64.多层if判断怎么优化

使用newMap,例如:
	const gitObj = (type) => {
    if (type === '张三') {
        return '张三的Obj';
    }
    if (type === '李四') {
        return '李四的Obj';
    }
    if (type === 233) {
        return 'testObj';
    }
    if (type === 'testUser') {
        return 'testUser';
    }
    return '--';
};
优化如下:
	const gitObj = (type) => {
    const newMap = {
        '张三': '张三的Obj',
        '李四': '李四的Obj',
        233: 'testObj',
        'testUser': 'testUser哈哈哈',
    };
    return newMap[type] || '--';
};

65.如何定义一个对象没有原型

1.Object.create(proto,[propertiesObject])
	proto: 新创建对象的原型对象
	propertiesObject: 可选
例如:
	let b = Object.creat(null)
	b.name = '张三'
	b.say = () => {
		console.log('11')
	}

66.怎么判断是否时promise

const isPromise = (val) => {
	return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}

67.如何判断是否是一个数组,对象

数组:
	1.instanceof
		a instanceof Array // 检测Array.prototype是否出现在a的原型链上
	2.constrtuctor
		a.constrtutor === Array
	3.Object.prototype.toString.call()
		Object.prototype.toString.call(a) === '[Object Array]'
	4.Array.isArray()
		Array.isArray(a)

67.函数重载

函数重载:存在两个或多个重名的方法,js的重载只能通过形参的个数的不同来区分
作用:减少函数名的数量,避免了名字空间的污染,减少了代码行数,提高代码的可读性。

68.promise怎么让其返回结果有顺序

假设a,b,c是三个Promise异步任务
让其实现返回结果有顺序,如下:
方法一:
	// reduce实现
	let promiseArr = [a, b, c]
	promiseArr.reduce((prev,cur) => {
		return prev.then(()=>{
			return cur.then(res=>{
				console.log(res)
			})
		})
	},Promise.resolve())
方法二:
	let arr = [a, b, c]
	async function fun(arr) {
		for(const item of arr) {
			const res = await item().then()
			console.log(res)
		}
	}

69.怎么判断两个引用类型的值是否相等

1.对象
        // 对比两个对象的值是否完全相等 返回值 true/false
    let  isObjectValueEqual = (a, b)=>{   
      //取对象a和b的属性名
      var aProps = Object.getOwnPropertyNames(a);//返回指定对象所有自身属性名
      var bProps = Object.getOwnPropertyNames(b);
      //判断属性名的length是否一致
      if (aProps.length != bProps.length) {
          return false;
      }
      //循环取出属性名,再判断属性值是否一致
      for (var i = 0; i < aProps.length; i++) {
        var propName = aProps[i];
        if (a[propName] !== b[propName]) {
            return false;
        }
      }
      return true;
    }
    
2.数组
	把数组转换为字符串,然后在判断:JSON.stringify(),toString(),join(‘’)
	循环:
        function isEquar(a , b){
            if(a.length != b.length)   return false;
            else{
                for(let i = 0 ; i < a.length ; i++){
                    if(a[i] !== b[i]) return false;
                }
                return true
            }
        }
        let a = [1,2,3,4,5];
        let b = [1,2,'3',4,5];
        console.log(isEquar(a , b));

70.class的静态属性怎么继承

静态方法是挂载到类型上面的,所以静态方法里面的this不会指向某一个实例对象, 而是当前的类型
直接通过类型本身去调用

71.for of为什么不能遍历对象

forof是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用forof遍历是会报错的。

1.遍历类数组对象时
	使用Array.from
		var obj = {
			0: 'one',
			1: 'two',
			length: 2
		}
		obj = Array.from(obj)
		for(var k of obj) {
			console.log(k)
		}
2.遍历普通对象时
	2.1:给对象添加一个[Symbol.iterator]属性,并指向一个迭代器
		var obj = {
			a: 1,
			b: 2,
			c: 3
		}
		obj[Symbol.iterator] = function() {
			let keys = Object.keys(this)
			let counts = 0
			return {
				next() {
					if(counts < keys.length) {
						return { value: obj[keys[counts++]], done: false }
					} else {
						return { value: undefined, done: true}
					}
				}
			}
		}
	2.2: 使用Generator函数生成迭代器
		var obj = {
			a: 1,
			b: 2,
			c: 3
		}
		obj[Symbol.iterator] = function()* {
			let keys = Object.keys(this)
			for( var k of keys) {
				return [k,obj[k]]
			}
		}

72.js怎么监听对象的变化

//  object.defineproperty
function observeKey(obj, key) {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    get() {
      console.log("读取属性", value);
      return value;
    },
    set(newValue) {
      console.log("设置属性", newValue);
      value = newValue;
    }
  });
}
let obj = { a: 1 };
observeKey(obj, "a");
// 读取a,触发get函数
console.log(obj.a);
// 设置a,触发set函数
obj.a = 1;



// new Proxy

// 比如,obj就是我
let obj = { name: "颜酱", age: 18, phone: 18362780848 };
// 给我加个秘书
let proxy = new Proxy(obj, {
  // 想要知道我的其他信息,先问问秘书,秘书心情好就说
  get(target, key) {
    // target就是obj,key就是访问的属性
    const isHappy = Math.random() > 0.5;
    return isHappy ? target[key] : "不太清楚";
  },
  // 想更新我的信息,同样看我秘书心情
  set(target, key, value) {
    // target就是obj,key就是访问的属性,value是别人想要的属性值
    const isHappy = Math.random() > 0.5;
    // 心情好就更新,不好就不改了
    isHappy ? (target[key] = value) : console.log("修改失败");
  }
});
// 这里注意,只能使用秘书,只有秘书才会说或者不说,如果直接obj.name跟秘书就没关系了
console.log(proxy.name);
// 同理,这里不能 obj.phone = 28
proxy.phone = 18362790909;
// 一旦修改成功,obj.phone就会被更新,当然不成功肯定不会被更新
console.log(proxy, obj);


73.http1和http2

HTTP1.0:

  • 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接

HTTP1.1:

  • 引入了持久连接,即TCP连接默认不关闭,可以被多个请求复用

  • 在同一个TCP连接里面,客户端可以同时发送多个请求

  • 虽然允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才 会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着

  • 新增了一些请求方法

  • 新增了一些请求头和响应头

  • 加入缓存处理(新字段cache-control)

    • 缺点
      • 高延迟–队头阻塞
      • 无状态特性–阻塞交互
      • 明文传输–不安全
      • 不支持服务端推送

HTTP2.0:

  • 采用二进制格式而非文本格式
  • 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行
  • 使用报头压缩,降低开销
  • 服务器推送

74.知识点

1.
	for(var i = 0, i < 2, i++) {
		setTimeout(()=>{
			console.log(i)
		})
	}
	settimeout是异步执行,10ms后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,当主线执行完成后,i是4,所以此时再去执行任务队列里的任务时,i全部是4了。对于打印4次是:

	每一次for循环的时候,settimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行,for循环了4次,就放了4次,当主线程执行完成后,才进入任务队列里面执行。
	因为for循环头部的let不仅将i绑定到for循环块中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的
2.
	setTimeout(()=>{
		console.log(1)
		Promise.resolve().then(()=>{
			console.log(2)
		})
	})
	Promoise.resolve().then(()=>{
		setTimeout(()=>{
			console.log(3)
		})
		console.log(4)
	})

75. JS CSS会阻塞DOM么

defer和async虽然都是异步加载js,但是async是加载完js后立马执行,而defer要等待之前渲染完再去进行执行。

概况:

  1. CSS不会阻塞DOM解析,但是会阻塞DOM渲染,严谨一点则是CSS会阻塞render tree的生成,进而会阻塞DOM的渲染
  2. JS会阻塞DOM的解析
  3. CSS会阻塞JS的执行
  4. JS中:既没有defer也没有async,则浏览器遇到script标签时,会立即加载并执行指定的脚本,执行完毕之后,才继续解析后面的标签;有async属性,浏览器遇到该script标签时,加载脚本的过程与解析后面标签的过程同时进行,加载完毕后,立即执行该脚本;有defer属性,浏览器遇到该script标签时,加载脚本的过程与解析后面标签的过程同时进行。但是执行脚本的过程,在解析完所有元素之后才执行。

76.cookie和session的作用和区别

  1. cookie:其实就是客户端存储的,什么时客户端,就是浏览器存储,单个cookie保存的数据不能超过4k
  2. session:服务端,也是放在服务器上(session的默认失效时间为30分钟),session是另一种记录客户状态的机制,基于cookie实现,客户端浏览器访问服务器的时候,服务器把客户信息以某种形式记录在服务器上,这就是session,客户端浏览器再次访问时只需要从session中查找该客户的状态就可以了

区别:

  1. cookie不是很安全,别人可以本地的cookie获取你的信息,考虑到安全性应当使用session
  2. session会在一定时间内保存到服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie

77.可以改变原数组的方法

pop()
push()
reverse()
shift()
unshift()
sort()
splice()

78.改变对象的原型

let children = {};
let parent = {};
// 把children的原型设置为parent
Object.setPrototypeOf(children,parent)
// 查看原型
Object.getPrototypeOf(children)

79.原型检测

instanceof

  1. 用于判断某个实例是否属于某构造函数
  2. 在继承关系中用来判断一个实例是否是属于它的父类型或者祖先类型的实例
    查找构造函数的原型对象是否在实例对象的原型链上,如果在返回true,如果不在返回false。说白了,只要右边变量的prototype在左边变量的原型链上即可
    [1,2,3] instance Array

isPrototypeOf
b.isPrototypeOf(a): 检查b的原型对象是否在实例对象a的原型链上

80.属性检测

in:

  • a in b: 检测a是否b对象上,并且检测是否在b的原型链上

hasOwnProperty

  • a.hasOwnProperty(b): 检测a上面是否有b,不检测原型链上

81.设置对象的原型

1.Object.create
// A: 被设置的对象,B: 设置成那个对象成为A的原型
A = Object.create(B)

2.__proto__
// A: 被设置的对象,B: 设置成那个对象成为A的原型

A.__proto__ = B

// 注:__proto__后面有值是设置,没值是获取

3.Object.setPrototypeOf
// A: 被设置的对象,B: 设置成那个对象成为A的原型
Object.setPrototypeOf(A,B)

82. __proto __原来是属性访问器

// __proto__ getter setter
我们知道__proto__只能设置为对象,而不能设置字符串或数字等,原因就是set的时候做了判断

如下:
	let lxp = {
		action: {},
		get proto() {
			return this.aciton
		},
		set proto(obj) {
			if(obj instanceof Object) {
				this.aciton = obj
			}
		}
	}
	
	lxp.proto = {viem: function() {}} // 可以
	lxp.proto = '123' // 不可以
	
如果我们想把proto设置为字符串或数字,可以把对象的原型设置为空,因为__proto__的属性访问器是存在它的原型链式
	let lxp = Object.create(null) 
	lxp.__proto = '123' //  这样就可以

83.class声明的方法为什么不能遍历

获取show方法的原型特征:Object.getOwnPropertyDescriptor(lxp.prototype,'show')
{
	writable: true,
	enumerable: true, // 是否可以遍历
	configurable: true
}

84.箭头函数和普通函数的区别

1.普通函数存在变量的提升,箭头函数没有
2.普通函数的this指向,谁调用指向谁,箭头函数是在哪定义就指向谁
3.普通函数可以当成构造函数,而箭头函数是不可以的。(箭头函数是有__proto__属性的,所以箭头函数本身是存在原型链的,它也是有自己的构造函数的,但是原型链到箭头函数这一环就停止了,因为它自己没有prototype属性,没办法让他的实例的__proto__属性指向,所以箭头函数就无法作为箭头函数)
4.箭头函数没有arguments,而接受所以的参数用...arguments

85.export 和export default 的区别

  1. export可以导出多个属性或者方法,需要用{}括起来,在用import接收的时候也得应用{}接收
  2. export defalut是以整体的方式抛出,接收的时候只接一个

86.网络层级协议

  1. 五层体系结构:应用层,运输层,网络层,数据链路层和物理层
    • 应用层:通过应用进程间的交互来完成特定网络应用
    • 运输层:负责向两台主机中进程之间的通信提供通用的数据传输服务。(TCP,UDP)
    • 网络层:负责为分组交换网上的不同主机提供通信服务;选择合适的路由分发网络。(数据报文)
    • 数据链路层:发送和接收数据;提供数据有效传输的端到端连接。(帧)
    • 物理层:透明地传送比特流。
  2. OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

87.BFC可以清除浮动吗?为什么

因为浮动元素也是BFC,两个BFC互不影响

88.v8垃圾回收

  • v8采用的是分代垃圾回收,分为新生代和老生代,新生代的对象是存活时间比较短的对象,而老生代为存活较长的对象。新生代的内存又会被分为两个区,一个FORM,一个TO,可以把FORM理解为使用区,TO为空闲区。每次垃圾回收时,把FORM区没引用的销毁,清理完成之后,FORM区和TO区角色互换,之前的TO区变成新的FORN区,之前的FORM区变成新的TO区,循环往复。

  • 当一个对象经历多次还未被清理掉,故此对象会被认定为生命周期较长的对象,会被从新生代移动到老生代,采用老生代的垃圾回收机制管理。但标记清除会造成内存不连续,所以会有标记整理取解决掉内存碎片。

  • 为什么TO区使用空间超过25%要晋升为老生代?

    • 为了不影响后续FORM空间的分配
    • 25%的红线要求是为了保证进行TO区和FORM区翻转时对于新的对象分配空间操作不会被影响。

89.js上下文执行栈

  • 栈(stack):

    • 栈的特点是:出口和入口是同一个:出口跟入口是同一个,遵循着先进后出,后进先出的原则。数据只能顺序的入栈,顺序的出栈。
  • 堆(heap):

    • 堆的特点是无序的key-value键值对存储方式,堆的存取方式跟顺序没有关系,不局限出入口。
  • 队列(queue):

    • 队列的特点是先进先出,数据存取时”从队尾插入,从队头取出“
  • 执行栈(JS stack):

    • 执行栈又称为执行上下文栈或调用栈。是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。程序执行进入一个执行环境时(比如整个script标签或进入一个函数内部),它的执行上下文就会被创建,执行上下文中包含了函数的参数和局部变量,并被推入执行栈中(入栈);程序执行完成后,它的执行上下文就会被销毁,并且从栈顶被推出(出栈),继续执行下一个执行上下文。

90.层叠上下文和层叠顺序

首先我们要知道以下几点:

  1. z-index属性值并不是在任何元素上都有效果。它仅在定位元素(定义了position属性,且属性值为非static值的元素)上有效果。
  2. 判断元素在z轴上的堆叠顺序,不仅仅是直接比较两个元素的z-index值的大小,这个堆叠顺序实际由元素层叠上下文,层叠等级共同决定的。
  • 层叠上下文:

    • 层叠上下文其实是一个三维的概念。每个盒模型的位置是三维的,分别是平面画布上的x轴,y轴以及表示层叠的z轴。如果元素一旦发生了层叠,这时我们能发现某个元素可能覆盖了另一个元素或者被另一个元素覆盖。
  • 层叠等级:

  1. 在同一个层叠上下文中,它描述定义的是该层叠上下文中的层叠上下文元素在z轴上的上下顺序
  2. 在其他普通元素中,它描述定义的是这些普通元素在z轴上的上下顺序。
  3. 普通元素的层叠等级优先由其所在的层叠上下文决定的。
  4. 层叠等级的比较只有在当前层叠上下文中才有意义。不同层叠上下文比较层叠等级是没有意义的。

层叠顺序:
表示元素发生层叠时按照特定的顺序规则在z轴上垂直显示。

91.浏览器不同标签页之间如何通信

1.localStroage: 缺点:跨域的共享不了
2.websocket: 缺点:需要服务端配合压力大、
3.postMessage
	iframe:
		发送信息:iframe.contentWindow.postMessage('信息',targetOrigin)
		接收信息:window.addEventListener('messge',(event)=>{
			console.log(event.data) // 接收的消息
		})
	
	window:
        // 接收消息
        window.addEventListener('message', (e) => {
             console.log(e.data)
        })
        // 发送消息
        const targetWindow = window.open('http://localhost:10001/user');
        setTimeout(()=>{
             targetWindow.postMessage('来自10002的消息', 'http://localhost:10001')
        }, 3000)

92.如何实现页面每次打开时清除本页缓存

  1. 用html标签设置http头信息
  2. 在需要打开的url后面增加一个随机的参数

93.浏览器缓存策略

  • 浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本地缓存。如果缓存有效,则使用本地缓存;否则,则向服务器发起请求并携带缓存标识。根据是否需向服务器发起HTTP请求,将缓存过程分为两个部分:强缓存和协商缓存,强缓存优先于协商缓存。
    • 强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
    • 协商缓存,让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,将缓存信息中的Etag和Last-Modified
      通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。

94.src和href的区别

  1. 作用不同:href用来在当前文档和引用资源之间确立联系;src用于替换当前内容
  2. 浏览器解析方式不同:遇到href,页面会并行加载后续内容;而src不同,浏览器需要加载完毕src的内容才会继续往下走。

95.Object.is与比较操作符 === ,==的区别

==会在比较时进行类型转换,===比较时不进行隐式类型转换,类型不同则会直接返回false,Object.is在 === 判等的基础上特别处理了NaN, -0和+0,处理之后:-0和+0等于false,NaN和NaN等于true

96.Object.assign和扩展运算符都是浅拷贝,两者的区别

  1. Object.assign() 方法接收的第一个参数作为目标对象, 后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象, 因此会触发 ES6 setter
  2. 扩展操作符 … 使用它时, 数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性, 但是它会复制 ES6 的 symbols 属性

97.箭头函数的this指向哪里

总是指向最近的外层作用域中的this所指对象

98. __proto __ 和prototype的区别

proto(隐式原型)与prototype(显式原型)
prototype是每个函数都会具备的一个属性,它是一个指针,指向一个对象,只有函数才有。而proto是主流浏览器上在除null以外的每个对象上都支持的一个属性,它能够指向该对象的原型。

99.requestAnimationFrame

  • requestAnimationFrame 请求动画帧

  • requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行

  • 优点:

    1. requestAnimationFrame的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
    2. 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
    3. requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
  • 跟setTimeout和setInterva的对比:

    1. setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行

    2. requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果

  • 应该场景:

  1. 箭头scroll函数
	页面滚动事件(scroll)的监听函数,就很适合用这个 api,推迟到下一次重新渲染。
        $(window).on('scroll', function () {
          window.requestAnimationFrame(scrollHandler)
        })
  1. 平滑滚动到页面顶部
        const scrollToTop = () => { 
          const c = document.documentElement.scrollTop || document.body.scrollTop 
          if (c > 0) {  
            window.requestAnimationFrame(scrollToTop) 
            window.scrollTo(0, c - c / 8) 
          }
        }

        scrollToTop()

3.大量数据渲染

//需要插入的容器
 let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
	if (curTotal <= 0) {
		return false
	}
	//每页多少条
	 let pageCount = Math.min(curTotal, once)
     window.requestAnimationFrame(function () {
		for (let i = 0; i < pageCount; i++) {
			let li = document.createElement('li')
				li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
					 ul.appendChild(li)
			}
			loop(curTotal - pageCount, curIndex + pageCount)
		})
	}
loop(total, index)

100.浏览器内核和引擎

内核:
    blink:chrome,Opera,360浏览器
    webkit:safai
    gecko:火狐
    trident: IE
JS引擎:
	V8: chrome,Opera

101.开启关闭全屏显示

requestFullscreen()  // 让元素开启全屏显示
cancleFullscreen() // 让元素关闭全屏显示


// 检测当前是否处于全屏状态
	document.fullScreen

102.input和textArea的区别

  • input:是单行输入框,不会换行。即使设置了宽高,也只是一行。可以通过size属性设置输入字符的长度,但是如果通过css设置了宽高,则size属性无效;
    通过value属性设置输入框的初始值,通过maxlength设置输入框可以输入的最大字符数。

  • textarea是多行输入框,可以换行。可以通过cols和rows属性设置宽高,也可以通过width和height属性设置宽高。通过maxlength设置输入框可以输入的最大字符数。

103.用一个div模拟textArea的实现

contenteditable属性

104.1像素边框问题

105.实现extend函数

106.jsonp原理,postMessage原理

107.静态资源或者接口等如何做缓存优化

108.页面DOM节点太多,会出现什么问题?如何优化?

  1. 定时分批渲染
  2. document.createDocumentFragment()
    • 用来创建一个虚拟的节点对象,节点对象不属于文档树
    • 当需要添加多个DOM元素时,可以先把DOM添加到这个虚拟节点中。然后再统一将虚拟节点添加到页面,这会减少页面渲染DOM的次数
    • window.requestAnimationFrame接受参数为函数,比起setTimeout和setInterval有以下优点:
      • 把每一帧中的所有DOM操作集中起来,在一次的重排/重绘中完成,每秒60帧
    • 在隐藏或者不可见的元素中,requestAnimationFrame将不会重绘/重排

109.break continue的区别

break 是结束整个循环体,continue是结束单次循环。

110.高度塌陷

高度塌陷简单说就是,父级元素包涵的子元素浮动了,当父级元素没有设置高度时,会因为没有子元素“撑开”,而变成一条线。

111. a标签写成大写A可以编译吗,href怎么配置点击不跳转,href跳转用js怎么拦截

设置 href="javascript:void(0);"来防止超链接跳转。
设置 href="javascript:;"来防止超链接跳转。
js拦截
    <a href="https://www.baidu.com" rel="external nofollow"  rel="external nofollow" >百度</a>
    <script>
        var link = document.querySelector("a");
        link.addEventListener('click',function(e){
            e.preventDefault();
            // return false
        })
    </script>

112. js中,var,let,const定义的变量可以挂载到window下么

  1. var 定义的变量会挂载到全局对象 window 上,let 和 const 定义的变量不会。

  2. 使用 var 声明的变量会自动成为全局变量,而使用 let 和 const 声明的变量则是块级作用域变量,只在声明的块内有效。

  3. 如果在非严格模式下,使用 let 或 const 声明的变量没有在任何块级作用域中声明,那么它们会被视为全局变量,并挂载到全局对象 window 上。

  4. 但是,在严格模式下,使用 let 或 const 声明的变量不会自动挂载到全局对象 window 上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值