JS基础总结

WebAPI

获取元素

	document.getElementById()
	document.getElementsByTagName()
	// H5
	document.getElementsByClassName()
	document.querySelector()
	document.querySelectorAll()

	// 获取html
	document.documentElement
	// 获取body
	document.body

事件

	// 鼠标事件
	window.onclick = () => {} // 点击事件
	window.onblur = () => {} // 脱标事件
	window.onfocus = () => {} // 聚焦事件
	window.onmousedown = () => {} // 鼠标按下
	window.onmousemove = () => {} // 鼠标移动
	window.onmouseup = () => {} // 鼠标松开
	window.onmouseover = () => {} // 鼠标进入
	window.onmouseout = () => {} // 鼠标离开
	// 不支持冒泡,与mouseover一样
	window.onmouseenter = () => {} // 鼠标进入,但不支持冒泡
	window.onmouseleave = () => {} // 鼠标离开,但不支持冒泡
	// 键盘事件
	window.onkeypress = () => {} // 键盘按下 已废弃
	window.onkeydown = () => {} // 键盘按下
	window.onkeyup = () => {} // 键盘松开

事件源的位置

  • clientX、clientY:
    相对于浏览器窗口可视区域的X,Y坐标(窗口坐标)、
    可视区域不包括工具栏和滚动条、IE事件和标准事件都定义了这2个属性。
  • pageX、pageY:
    类似于event.clientX、event.clientY,但它们使用的是文档坐标而非窗口坐标。
    这2个属性不是标准属性,但得到了广泛支持。IE事件中没有这2个属性。
  • offsetX、offsetY:
    相对于事件源元素(target或srcElement)的X,Y坐标,
    只有IE事件有这2个属性,标准事件没有对应的属性。
  • screenX、screenY:
    相对于用户显示器屏幕左上角的X,Y坐标。标准事件和IE事件都定义了这2个属性

操作元素

	// 示例:操作html元素
	// 操作元素文本
	document.documentElement.innerHTML = ''
	document.documentElement.innerText = ''
	// 操作元素样式
	document.documentElement.style
	// 操作元素类名
	document.documentElement.className
	// 操作元素属性
	document.documentElement.getAttribute()
	document.documentElement.setAttribute()
	document.documentElement.removeAttribute()

元素节点

	// 示例:body元素
	// 父节点
	document.body.parentNode
	// 子节点(包含空格)
	document.body.childNodes
	// 子元素
	document.body.children
	// 第一个,最后一个节点(包含元素节点和文本节点)
	document.body.firstChild
	document.body.lastChild
	// 第一个,最后一个节点(包含元素节点,不包含文本节点)
	document.body.firstElementChild
	document.body.lastElementChild
	// 下一个,上一个节点(包含元素节点和文本节点)
	document.body.nextSibling
	document.body.previousSibling
	// 下一个,上一个节点(包含元素节点,不包含文本节点)
	document.body.nextElementSibling
	document.body.previousElementSibling

	// 节点增删改
	document.createElement()
	document.body.appendChild()
	document.body.insertBefore()
	document.body.removeChild()
	// 克隆元素
	document.body.cloneNode()

元素属性

	// 示例:body元素
	// 偏移量 offset 距离带有定位父元素的位置
	document.body.offsetLeft // 元素左侧距离带有定位父元素的值
	document.body.offsetTop // 元素顶部距离带有定位父元素的值
	document.body.offsetHeight // 自身元素高度,包含padding + 边框
	document.body.offsetWidth // 自身元素宽度,包含padding + 边框

在这里插入图片描述

	// 示例:body元素
	// 自身元素大小 client
	document.body.clientHeight // 不包含padding + 边框
	document.body.clientWidth // 不包含padding + 边框
	document.body.clientLeft // 边框大小
	document.body.clientTop // 边框大小

在这里插入图片描述

	// 示例:body元素
	// 滚动元素 scroll
	document.body.scrollHeight // 自身实际高度,不包含padding + 边框
	document.body.scrollWidth // 自身实际宽度,不包含padding + 边框
	document.body.scrollLeft // 左边滚动距离
	document.body.scrollTop // 上边滚动距离

在这里插入图片描述

	// 示例:body元素
	const { x, y, left, top, bottom, right } = document.body.getBoundingClientRect()

在这里插入图片描述

BOM对象

	// 所有元素加载完成
	window.onload = () => {}
	// 计时器
	setTimeout(() => {}, 0)
	setInterval(() => {}, 0)
	// location
	const {
		hash,
		host,
		href,
		pathname,
		port,
		protocol,
		search,
		assign,
		reload,
		replace,
	} = location

	// localstorage
	localStorage.setItem('localStorage', 1)
	localStorage.getItem('localStorage')
	localStorage.removeItem('localStorage')
	localStorage.clear()
	// sessionstorage
	sessionStorage.setItem('sessionStorage', 1)
	sessionStorage.getItem('sessionStorage')
	sessionStorage.removeItem('sessionStorage')
	sessionStorage.clear()

操作元素综合示例(键盘移动活动表格)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    table {
      font-size: 14px;
      border-collapse: collapse;
      width: 100%;
      table-layout: fixed;
    }

    table td {
      border: 1px solid #e1e1e1;
      padding: 0;
      height: 30px;
      text-align: center;
    }

    table td.current {
      background: #1890ff;
    }
  </style>
</head>

<body>
  <div>
    <table>
      <tbody>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td class="current"></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
      </tbody>
    </table>
  </div>
  <script>

    document.onkeydown = event => {
      if (!event) return
      var code = event.keyCode || ''
      if (!{ '37': 1, '38': 1, '39': 1, '40': 1 }[code]) return
      var tbody = document.querySelector('tbody')
      var current = document.querySelector('.current')
      current.className = ''
      if (code == 37) {
        if (current.cellIndex == 0) {
          current = current.parentNode.children[8]
          current.className = 'current'
        } else {
          current.previousElementSibling.className = 'current'
        }
      } else if (code == 38) {
        if (current.parentNode.rowIndex == 0) {
          current = tbody.children[8].children[current.cellIndex]
          current.className = 'current'
        } else {
          current = current.parentNode.previousElementSibling.children[current.cellIndex]
          current.className = 'current'
        }
      } else if (code == 39) {
        if (current.cellIndex == 8) {
          current = current.parentNode.children[0]
          current.className = 'current'
        } else {
          current.nextElementSibling.className = 'current'
        }
      } else if (code == 40) {
        if (current.parentNode.rowIndex == 8) {
          current = tbody.children[0].children[current.cellIndex]
          current.className = 'current'
        } else {
          current = current.parentNode.nextElementSibling.children[current.cellIndex]
          current.className = 'current'
        }
      }
    }
  </script>
</body>

</html>

执行上下文和执行栈

执行上下文

1、JS代码都是在执行上下文中执行的
2、执行上下文: 指当前执行环境中的变量、函数声明、作用域链、this等信息
3、执行上下文分为全局、函数、Eval执行上下文
	1)全局执行上下文(浏览器环境下,为全局的 window 对象)
	2)函数执行上下文,每当一个函数被调用时, 都会为该函数创建一个新的上下文
	3)Eval 函数执行上下文,如eval("1 + 2")
4、对于每个执行上下文,都有三个重要属性:变量对象、作用域链(Scope chain)、this

执行上下文的特点

1、单线程,只在主线程上运行
2、同步执行,从上向下按顺序执行
3、全局上下文只有一个,也就是window对象
4、函数每调用一次就会产生一个新的执行上下文环境

执行上下文的生命周期

1、创建阶段:生成变量对象、建立作用域链、确定this指向
2、执行阶段: 变量赋值、函数引用、执行其他代码
      1.创建变量对象:
          1) 变量
          2) 函数及函数的参数
          3) 全局: window
          4) 局部: 局部变量
      2.确认this的指向
          1) 全局: this -- -> window
          2) 局部: this -- -> 调用其的对象
      3.创建作用域链
          父级作用域链 + 当前的变量对象
  	  4.扩展:
          ECobj = {
              变量对象:{变量,函数,函数的形参}
              scopeChain:父级作用域链+当前的变量对象,
              this: {window | 调用其的对象}
          }

执行栈

执行栈是一种先进后出的数据结构,用来存储代码运行的所有执行上下文
	1)当 JS 引擎第一次遇到js脚本时,会创建一个全局的执行上下文并且压入当前执行栈
	2)每当JS 引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部
	3)当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文
	4)一旦所有代码执行完毕,JS 引擎从当前栈中移除全局执行上下文

作用域

作用域:可访问变量的集合,最大的作用就是隔离变量,不同的作用域下同名变量不会有冲突
作用域类型:全局作用域、函数作用域、块级作用域(ES6)
	全局作用域:全局上下文的变量
	函数作用域:是指声明在函数内部的变量,函数的作用域在函数定义的时候就决定了
	块级作用域:块作用域由{ }包括,if和for语句里面的{ }也属于块作用域,在块级作用域中,可通过let和const声明变量,该变量在指定块的作用域外无法被访问

var let const的区别

1、var定义的变量,没有块的概念,可以跨块访问, 可以变量提升
2、let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明
3、const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明

作用域链

当查找变量的时候,首先会先从当前上下文的变量对象(作用域)中查找,
如果没有找到,就会从父级的执行上下文的变量对象中查找,
如果还没有找到,一直找到全局上下文的变量对象,也就是全局对象。
这样由多个执行上下文的变量对象构成的链表就叫做作用域链

作用域和值类型引用类型的值传递

	// 全局作用域下有 num1 : 55 ,num2: 66,俩个变量
	var num1 = 55
	var num2 = 66
	function f1(num, num1) {
		// 根据传入的参数,变量的提升,预编译
		// 函数作用域下有 num:55 ,num1:66
		// var num = 55
		// var num1 = 66

		// 此时变量num和num1被改为了100
		num = 100
		num1 = 100

		// 根据作用域链,函数内部无num2变量,往上一级作用域寻找,变量提升,全局变量的num2被改为了100
		num2 = 100
		console.log(num) //100
		console.log(num1) //100
		console.log(num2) //100
	}
	f1(num1, num2)
	console.log(num1) //55
	console.log(num2) //100,全局变量num2在函数种被改为了100
	console.log(num); //not define 报错 全局作用域下无num属性
	function Person(name, age, salary) {
		// 函数作用域下有 name age salary 三个变量
		this.name = name
		this.age = age
		this.salary = salary
	}
	function f2(person) {
		// f2的作用域下 person: p
		//var person = p
		// 此时person和p指向堆内存中的同一个Person对象
		person.name = 'ls' //改变了堆内存的Person对象的name值
		// person和p的作用域下:name: aa, age: 18, salary: 10
		person = new Person('aa', 18, 10) //将person指向新的Person对象
	}
	// p的作用域下 name: zs, age: 18, salary: 1000
	var p = new Person('zs', 18, 1000)
	console.log(p.name) //'zs'
	f2(p)
	console.log(p.name) //'ls' 此时打印的仍然是堆内存中第一个Person的name值

变量提升

 js引擎在代码正式执行之前会做一个预处理的工作:
 	 1.收集变量
     2.收集函数
  依据:
      var :将var后边的变量定义但是不赋值 var username = undefined;
      function(){} 提前定义该函数
  变量的提升是变量名的提升,函数提升是整体的提升
  变量与函数同名,提升以函数为准
	console.log(username) //undefined

	var username = 'kobe'

	console.log(username) //kobe

	fun() //fun()

	function fun() {
		console.log('fun()')
	}
	function Foo() {
		getName = function () {
			console.log(1)
		}
		return this
	}
	Foo.getName = function () {
		console.log(2)
	}
	Foo.prototype.getName = function () {
		console.log(3)
	}
	// 在变量提升之后,此时5的函数会被4给替换,因为前面的getName在变量提升之后是5函数
	var getName = function () {
		console.log(4)
	}
	function getName() {
		console.log(5)
	}

	// 请写出以下的输出结果
	Foo.getName() // 2
	getName() //4
	
	// 这里的执行顺序是先执行Foo函数,也就是(Foo()).getName(),
	// Foo执行后,函数中的getName没有变量修饰符,也就是会在全局变量中找,那么此时全局变量中的getName被1函数赋值了
	// Foo返回了一个this值,this指向window,最后的变成window.getName(),此时getName是全局函数,因此会执行,输出1
	Foo().getName() //1
	
	// 此时getName已经被修改了
	getName() //1
	
	// new (Foo.getName)() ==> new (function(){console.log(2);})() 会执行该函数并产生一个实例对象
	new Foo.getName() //2 
	
	// new Foo()是一个实例对象,此时类的原型对象上有一个getName函数,输出
	new Foo().getName() //3
	
	// new ((new Foo()).getName)() ==> new (function(){console.log(3);})() 执行该函数
	new new Foo().getName() //3 
	function A() {
		console.log(1)
	}
	function Fn() {
		A = function () {
			console.log(2)
		}
		return this
	}
	Fn.A = A
	Fn.prototype = {
		A: () => {
			console.log(3)
		},
	}
	A() //1
	Fn.A() //1
	Fn().A() //2
	new Fn.A() //1
	new Fn().A() //3
	new new Fn().A() //报错,箭头函数不能new
}

this的指向问题

this的绑定方式

  1)默认绑定(非严格模式下this指向全局对象,严格模式下函数内的this指向undefined)
  2)隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj,谁调用指向谁)
  3)显示绑定(通过call或者apply方法直接指定this的绑定对象, 如foo.call(obj))
  4)new构造函数绑定,this指向新生成的对象
  5)箭头函数,this指向的是定义该函数时,外层环境中的this,箭头函数的this在定义时就决定了,不能改变

手写 new

	function New (fn, ...args) {
		// 创建一个空的对象并链接到构造函数的原型,使它能访问原型中的属性
		const instance = object.create(fn.prototype)
		// 使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
		const res = fn.apply(instance, args)
		// 优先返回构造函数返回的对象
		return typeof res === 'object' || typeof res === 'function' ? res : instance
	}
	
	function Person(name) {
		this.name = name
	}
	Person.prototype.eat = function () {
		console.log('Eatting')
	}

	var lindaidai = New(Person, 'LinDaiDai')
	console.log(lindaidai, 'New') // Person{ name: 'LinDaiDai' }
	lindaidai.eat() // 'Eatting'

手写call、apply、bind

	Function.prototype.Call = function(context, ...args) {
		if(!context) context = window
		const f = Symbol()
		context[f] = this
		const res = context[f](...args)
		delete context[f]
		return res
	}
	
	Function.prototype.Apply = function(context, ...args) {
		if(!context) context = window
		const f = Symbol()
		context[f] = this
		const res = context[f](args)
		delete context[f]
		return res
	}
	
	Function.prototype.Bind= function(context, ...args) {
		if(!context) context = window
		const f = Symbol()
		context[f] = this
		return function(...args1) {
			const res = context[f](...args, ...agrs1)
			delete context[f]
			return res
		}
	}
	
	var obj = {
		name: 'objName',
	}
	var name = 'globalName'

	function consoleInfo(sex, weight) {
		console.log(this.name, sex, weight, 'this指向 call apply bind')
	}
	consoleInfo('man', 100) // 'globalName' 'man' 100

	consoleInfo.Call(obj, 'man', 100) // 'objName' 'man' 100
	consoleInfo.Call(obj, 'woman', 120) // 'objName' 'woman' 120

	consoleInfo.Apply(obj, ['man', 100]) // 'objName' 'man' 100
	consoleInfo.Apply(obj, ['woman', 120]) // 'objName' 'woman' 120

	consoleInfo.Bind(obj, 'man', 100)() // 'objName' 'man' 100
	consoleInfo.Bind(obj, 'woman', 120)() // 'objName' 'woman' 120

相关题目

	var a = 10
	function foo() {
		// 默认模式下 函数的this执行window
		console.log(this.a) // 10
	}
	foo()
	'use strict'
	var a = 10
	function foo() {
		// 严格模式下 函数的this 指向 undefined
		console.log('this1', this) // undefined
		console.log(window.a) // 10
		console.log(this.a) // 报错,undefined上没a
	}
	console.log(window.foo) // f foo(){...}
	console.log('this2', this) // windiow
	foo()
	// let const 声明的变量不存在变量提升 window下无 a,b 变量
	let a = 10
	const b = 20

	function foo() {
		console.log(this.a) // undefined
		console.log(this.b) // undefined
	}
	foo()
	console.log(window.a) // undefined
	// let const 声明的变量不存在变量提升 window下无 a,b 变量
	let a = 10
	const b = 20

	function foo() {
		console.log(this.a) // undefined
		console.log(this.b) // undefined
	}
	foo()
	console.log(window.a) // undefined
	var a = 1
	function foo() {
		var a = 2
		console.log(this) // window
		console.log(this.a) // 1
	}
	foo()
	var a = 1
	function foo() {
		var a = 2
		function inner() {
			// 默认模式下,函数的this指向widow
			console.log(this.a) // 1
		}
		inner()
	}
	foo()
	function foo() {
		console.log(this.a)
	}
	var obj = { a: 1, foo }
	var a = 2
	foo() // 2 显示绑定,window调用,函数this默认指向window
	obj.foo() // 1 隐式绑定,由obj调用foo,obj中存在a变量为1,谁调用指向谁
	function foo() {
		console.log(this.a)
	}
	var obj = { a: 1, foo }
	var a = 2
	var foo2 = obj.foo

	obj.foo() //1  隐式绑定,由obj调用foo,obj中存在a变量为1,谁调用指向谁
	foo2() // 2 显示绑定,window调用,函数this默认指向window
	function foo() {
		console.log(this.a)
	}
	var obj = { a: 1, foo }
	var a = 2
	var foo2 = obj.foo
	var obj2 = { a: 3, foo2: obj.foo }

	obj.foo() // 1
	foo2() // 2
	obj2.foo2() // 3
	function foo() {
		console.log(this.a)
	}
	function doFoo(fn) {
		// doFoo 作用域下 fn = foo
		console.log(this)
		fn()
	}
	var obj = { a: 1, foo }
	var a = 2
	doFoo(obj.foo) // window 2 显式绑定
	function foo() {
		console.log(this.a)
	}
	function doFoo(fn) {
		// doFoo 作用域下 fn = foo
		console.log(this)
		fn()
	}
	var obj = { a: 1, foo }
	var a = 2
	var obj2 = { a: 3, doFoo }

	obj2.doFoo(obj.foo) // obj2 2 隐式绑定
	'use strict'
	function foo() {
		console.log(this.a)
	}
	function doFoo(fn) {
		console.log(this)
		fn()
	}
	var obj = { a: 1, foo }
	var a = 2
	var obj2 = { a: 3, doFoo }

	obj2.doFoo(obj.foo) // obj2 undefined 没有 a,严格模式下this指向undefined
	function foo() {
		console.log(this.a)
	}
	var obj = { a: 1 }
	var a = 2

	foo() // 2
	foo.call(obj) // 1
	foo.apply(obj) // 1
	foo.bind(obj)() // 1
	var obj1 = {
		a: 1,
	}
	var obj2 = {
		a: 2,
		foo1: function () {
			console.log(this.a)
		},
		foo2: function () {
			setTimeout(function () {
				console.log(this)
				console.log(this.a)
			}, 0)
		},
	}
	var a = 3

	obj2.foo1() // 2
	obj2.foo2() // window 3
	var obj1 = {
		a: 1,
	}
	var obj2 = {
		a: 2,
		foo1: function () {
			console.log(this.a)
		},
		foo2: function () {
			setTimeout(
				function () {
					console.log(this)
					console.log(this.a)
				}.call(obj1),
				0
			)
		},
	}
	var a = 3
	obj2.foo1() // 2
	obj2.foo2() // obj1 1
	var obj1 = {
		a: 1,
	}
	var obj2 = {
		a: 2,
		foo1: function () {
			console.log(this.a)
		},
		foo2: function () {
			function inner() {
				console.log(this)
				console.log(this.a)
			}
			inner()
		},
	}
	var a = 3
	obj2.foo1() // 2
	obj2.foo2() // window 3
	function foo() {
		console.log(this.a)
	}
	var obj = { a: 1 }
	var a = 2

	foo() // 2
	foo.call(obj) // 1
	foo().call(obj) // 2,  Cannot read property 'call' of undefined
	function foo() {
		console.log(this.a)
		return function () {
			console.log(this.a)
		}
	}
	var obj = { a: 1 }
	var a = 2

	foo() // 2
	foo.call(obj) // 1
	foo().call(obj) // 2 1
	function foo() {
		console.log(this.a)
		return function () {
			console.log(this.a)
		}
	}
	var obj = { a: 1 }
	var a = 2

	foo() // 2
	foo.bind(obj)
	foo().bind(obj) // 2
	function foo() {
		console.log(this.a)
		return function () {
			console.log(this.a)
		}
	}
	var obj = { a: 1 }
	var a = 2
	foo.call(obj)() // 1 2
	var obj = {
		a: 'obj',
		foo: function () {
			console.log('foo:', this.a)
			return function () {
				console.log('inner:', this.a)
			}
		},
	}
	var a = 'window'
	var obj2 = { a: 'obj2' }

	obj.foo()() // 'foo:obj' 'inner:window'
	obj.foo.call(obj2)() // 'foo:obj2' 'inner:window'
	obj.foo().call(obj2) // foo:'obj' 'inner:obj2'
	var obj = {
		a: 1,
		foo: function (b) {
			b = b || this.a
			return function (c) {
				console.log(this.a + b + c)
			}
		},
	}
	var a = 2
	var obj2 = { a: 3 }

	obj.foo(a).call(obj2, 1) // 6 a = 3 b = 2 c = 1
	obj.foo.call(obj2)(1) // 6 a = 2 b = 3 c = 1
	function foo1() {
		console.log(this.a)
	}
	var a = 1
	var obj = {
		a: 2,
	}

	var foo2 = function () {
		foo1.call(obj)
	}

	foo2() // 2
	foo2.call(window) // 2
	function foo1(b) {
		console.log(`${this.a} + ${b}`)
		return this.a + b
	}
	var a = 1
	var obj = {
		a: 2,
	}

	var foo2 = function () {
		return foo1.call(obj, ...arguments)
	}

	var num = foo2(3) // 2 + 3
	console.log(num) // 5
	function foo(item) {
		console.log(item, this.a)
	}
	var obj = {
		a: 'obj',
	}
	var a = 'window'
	var arr = [1, 2, 3]

	arr.forEach(foo, obj) // 1 "obj", 2 'obj' 3 'obj'
	arr.map(foo, obj) // 1 'obj' 2 'obj' 3 'obj'
	arr.filter(function (i) {
		console.log(i, this.a) // 1 "obj" 2 "obj" 3 "obj"
		return i > 2
	}, obj)
	function Person(name) {
		this.name = name
	}
	var name = 'window'
	var person1 = new Person('LinDaiDai')
	console.log(person1.name) // 'LinDaiDai'
	function Person(name) {
		this.name = name
		this.foo1 = function () {
			console.log(this.name)
		}
		this.foo2 = function () {
			return function () {
				console.log(this.name)
			}
		}
	}
	var person1 = new Person('person1')
	person1.foo1() // person1
	person1.foo2()() // undefined
var name = 'window'
	function Person(name) {
		this.name = name
		this.foo = function () {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		}
	}
	var person2 = {
		name: 'person2',
		foo: function () {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		},
	}

	var person1 = new Person('person1')
	person1.foo()() // 'person1' window
	person2.foo()() // 'person2' window
	var name = 'window'
	function Person(name) {
		this.name = name
		this.foo = function () {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		}
	}
	var person1 = new Person('person1')
	var person2 = new Person('person2')

	person1.foo.call(person2)() // 'person2' window
	person1.foo().call(person2) // 'person1' 'person2'
	var obj = {
		name: 'obj',
		foo1: () => {
			console.log(this.name)
		},
		foo2: function () {
			console.log(this.name)
			return () => {
				console.log(this.name)
			}
		},
	}
	var name = 'window'
	obj.foo1() // window
	obj.foo2()() // obj obj
	var name = 'window'
	var obj1 = {
		name: 'obj1',
		foo: function () {
			console.log(this.name)
		},
	}

	var obj2 = {
		name: 'obj2',
		foo: () => {
			console.log(this.name)
		},
	}

	obj1.foo() // obj1
	obj2.foo() // window
	var name = 'window'
	var obj1 = {
		name: 'obj1',
		foo: function () {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		},
	}
	var obj2 = {
		name: 'obj2',
		foo: function () {
			console.log(this.name)
			return () => {
				console.log(this.name)
			}
		},
	}
	var obj3 = {
		name: 'obj3',
		foo: () => {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		},
	}
	var obj4 = {
		name: 'obj4',
		foo: () => {
			console.log(this.name)
			return () => {
				console.log(this.name)
			}
		},
	}

	obj1.foo()() // obj1 window
	obj2.foo()() // obj2 obj2
	obj3.foo()() // window window
	obj4.foo()() // window window
	var name = 'window'
	function Person(name) {
		this.name = name
		this.foo1 = function () {
			console.log(this.name)
		}
		this.foo2 = () => {
			console.log(this.name)
		}
	}
	var person2 = {
		name: 'person2',
		foo2: () => {
			console.log(this.name)
		},
	}
	var person1 = new Person('person1')
	person1.foo1() // person1
	person1.foo2() // person1
	person2.foo2() // window
	var name = 'window'
	function Person(name) {
		this.name = name
		this.foo1 = function () {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		}
		this.foo2 = function () {
			console.log(this.name)
			return () => {
				console.log(this.name)
			}
		}
		this.foo3 = () => {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		}
		this.foo4 = () => {
			console.log(this.name)
			return () => {
				console.log(this.name)
			}
		}
	}
	var person1 = new Person('person1')
	person1.foo1()() // person1 window
	person1.foo2()() // person1 person1
	person1.foo3()() // person1 window
	person1.foo4()() // person1 person1
	var name = 'window'
	var obj1 = {
		name: 'obj1',
		foo1: function () {
			console.log(this.name)
			return () => {
				console.log(this.name)
			}
		},
		foo2: () => {
			console.log(this.name)
			return function () {
				console.log(this.name)
			}
		},
	}
	var obj2 = {
		name: 'obj2',
	}
	obj1.foo1.call(obj2)() // obj2 obj2
	obj1.foo1().call(obj2) // obj1 obj1
	obj1.foo2.call(obj2)() // window window
	obj1.foo2().call(obj2) // window obj2
	var name = 'window'
	var person1 = {
		name: 'person1',
		foo1: function () {
			console.log(this.name)
		},
		foo2: () => console.log(this.name),
		foo3: function () {
			return function () {
				console.log(this.name)
			}
		},
		foo4: function () {
			return () => {
				console.log(this.name)
			}
		},
	}
	var person2 = { name: 'person2' }

	person1.foo1() // person1
	person1.foo1.call(person2) // preson2

	person1.foo2() // window
	person1.foo2.call(person2) // window

	person1.foo3()() // window
	person1.foo3.call(person2)() // window
	person1.foo3().call(person2) // person2

	person1.foo4()() // person1
	person1.foo4.call(person2)() // person2
	person1.foo4().call(person2) // person1
	var name = 'window'
	function Person(name) {
		this.name = name
		this.foo1 = function () {
			console.log(this.name)
		}
		this.foo2 = () => console.log(this.name)
		this.foo3 = function () {
			return function () {
				console.log(this.name)
			}
		}
		this.foo4 = function () {
			return () => {
				console.log(this.name)
			}
		}
	}
	var person1 = new Person('person1')
	var person2 = new Person('person2')

	person1.foo1() // person1
	person1.foo1.call(person2) // person2

	person1.foo2() // person1
	person1.foo2.call(person2) // person1

	person1.foo3()() // winodw
	person1.foo3.call(person2)() // window
	person1.foo3().call(person2) // person2

	person1.foo4()() // person1
	person1.foo4.call(person2)() // person2
	person1.foo4().call(person2) // person1
	var name = 'window'
	function Person(name) {
		this.name = name
		this.obj = {
			name: 'obj',
			foo1: function () {
				return function () {
					console.log(this.name)
				}
			},
			foo2: function () {
				return () => {
					console.log(this.name)
				}
			},
		}
	}
	var person1 = new Person('person1')
	var person2 = new Person('person2')

	person1.obj.foo1()() // window
	person1.obj.foo1.call(person2)() // window
	person1.obj.foo1().call(person2) // person2

	person1.obj.foo2()() // obj
	person1.obj.foo2.call(person2)() // person2
	person1.obj.foo2().call(person2) //  obj
	function foo() {
		console.log(this.a) // 2
	}
	var a = 2
	;(function () {
		'use strict'
		foo() // 2, window调用
	})()

闭包

  什么是闭包?
    1.密闭的容器.类似于set,map
    2.闭包是一个对象.存放数据的格式:key:value
  形成的条件:
    1.函数嵌套
    2.内部函数引用外部函数的局部变量
  闭包的优点:
    1.延长外部函数局部变量的生命周期
  闭包的缺点:
    容易造成内存泄漏
  注意点:
    1.合理使用闭包
    2.用完闭包要及时销毁

防抖

	// 就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
	function debounce(fn, wait) {
		let timer = null
		return function() {
			const _this = this
			const args = arguments
			if(timer) clearTimeout(timer)
			timer = setTimeout(function() {
				fn.apply(_this, args)
			}, wait) 
		}
	}

节流

	function throttle(fn, delay) {
		let curTime = Date.now()
		return function() {
			let nowTime = Date.now()
			if(nowTime - curTime >= delay) {
				curTime = Date.now()
				fn.apply(this, arguments)
			}
		}
	}
	
	function throttleSetTimeout(fn, delay) {
		let timer = null
		return function() {
			const _this = this
			const args = arguments
			if(!timer) {
				timer = setTimeout(function() {
					timer = null
					fn.apply(_this, args)
				}, delay)
			}
		}
	}

相关题目

	function fun() {
		var count = 1
		// 此时已经形成闭包了
		function fun2() {
			console.log(count)
		}
		// 在fun2执行之后,闭包会被立即销毁
		fun2()
	}
	fun() // 1
	function fun() {
		var count = 1
		return function () {
			count++
			console.log(count)
		}
	}

	var fun2 = fun()
	// 此函数执行完闭包还未销毁
	fun2() //2
	// 此函数执行完后,闭包会销毁
	fun2() //3
	function fun(n, o) {
		// var n ,o
		// fun(0)时 n = 0,o = undefined
		console.log(o)
		return {
			fun: function (m) {
				return fun(m, n)
			},
		}
	}
	var a = fun(0) //undefined
	/* 
  此时fun是一个函数,返回的是全局函数执行的结果,此时m是传入的1,而n则是第一次执行时修改后的0,
  所以此时fun的结果为0,并且此时n是外部函数的局部变量,这里形成了闭包,此时传入的值也只改变了
  返回的函数的值,没有改变外部函数n的值

*/
	a.fun(1) //0
	a.fun(2) //0
	a.fun(3) //0
	/**
	 * 这里需要拆开来看,
	 * 首先是fun(0) 输出的结果肯定是undefinded
	 * 然后是fun(0).fun(1)  此时fun(0)已经给n赋值了,因此输出的是0
	 * 然后是(fun(0).fun(1)).fun(2) 此时(fun(0).fun(1))返回的闭包与fun(0)返回的是新的对象,执行新的函数,
	 *                             形成新的闭包, 因此,此时相当于n的值应该是1
	 * 然后是(fun(0).fun(1).fun(2)).fun(3) 与上面同理
	 */
	var b = fun(0).fun(1).fun(2).fun(3) //undefined, 0 , 1 , 2
	/**
	 * 上面两种情况的混合
	 * 首先是 fun(0) 输出的结果是undefined
	 * 然后是fun(0).fun(1),输出的结果是 0,并将返回对象赋给c
	 * 然后是c.fun(2),相当于(fun(0).fun(1)).fun(2),此时的n已经改为1了,输出的是1
	 * 然后是c.fun(3),相当于(fun(0).fun(1)).fun(3),此时的n仍然是1,输出1
	 */
	var c = fun(0).fun(1)
	c.fun(2)
	c.fun(3) //undefined, 0 ,1 , 1
	var test = (function (i) {
		return function () {
			alert((i *= 2))
		}
	})(2)
	test(5)

	// 弹出的是字符串 '4'
	/**
	 * 这里是一个自调用闭包
	 * 外面的函数执行时:
	 * 创建一个局部变量i并且赋值为2,然后将一个函数返回出去,将函数赋给test
	 * test(5)执行时,实际执行的是第一个函数返回出去的函数,不会执行第一个
	 * 函数,第一个函数只会执行一次,由于返回出去的函数不接收变量,因此,传入的5不起作用
	 * 并且alert中需要的变量是i,自身函数并没有对应的变量,根据作用域链会在第一个函数中
	 * 找到对应的i,i = 2*2 = 4,并且这里形成了闭包,因为返回函数中对外面函数的变量
	 * 还有引用,所以外面函数中的变量i在函数执行完之后并不会被销毁
	 */
	var a = 0,
		b = 0
	function A(a) {
		A = function (b) {
			alert(a + b++)
		}
		alert(a++)
	}
	A(1) // '1'
	A(2) // '4'

	/**
	 * 函数执行前进行变量提升,定义 a ,b 未赋值,定义并且对函数赋值
	 * 函数执行,全局变量 a 被赋值为 0 ,全局 b被赋值为0
	 * A(1)执行,函数的局部变量 a 被赋值为 1 ,此时函数A被重新赋值,弹出执行,弹出 '1' ,并且将局部变量a改为 2
	 * A(2)执行,此时函数已经被更改了,执行的是里面的那个函数,因为还用到变量a,自身没有a,根据作用域链
	 * 找到外面函数的局部变量a,此时形成了闭包,此时 a = 2 ,b 的值由传入的值决定 ,因此弹出 '4'
	 */
	var x = 2
	var y = {
		x: 3,
		z: (function (x) {
			this.x *= x
			x += 2
			return function (n) {
				this.x *= n
				x += 3
				console.log(x)
			}
		})(x),
	}
	/*
  m(4)此时调用函数的是m,第一个函数是window执行的,this指向的是window,第二个函数是m执行的,m是普通的变量
  this依旧指向window
  自调用函数执行,在函数中会定义var x = 2,局部变量由全局变量赋值,
  因此全局的 x = 2*2 = 6,函数中的x改为 x= 2 + 2=4
  返回一个函数,这个函数由m调用,this指向window,也就是说,全局的 x = 4 * 4 = 16
  返回的函数没有x,会根据作用域链找到第一个函数的 x = 4,此时形成闭包, x = 4 + 3 = 7
  因此打印x=7
*/
	var m = y.z
	m(4)

	/*
  y.z(5)调用的函数的是y中的z,第一个函数的this指向是window,返回的函数this指向的是y,因为此时执行的函数的是z
  上面的函数执行完之后,形成了闭包,此时全局的x为16,函数内部的x为7
  执行返回的函数:此时this指向的y,this.x = 3 * 5 =15,第一个函数的 x = 7 + 3 =10
  因此打印的x为10
*/

	y.z(5)
	// 经过两次执行,全局的x已经被改为了16,y中的x被改为了15
	console.log(x, y.x)
for (var i = 0; i < 5; i++) {
		setTimeout(function () {
			console.log(new Date(), i)
		}, 1000)
	}

	console.log(new Date(), i)
	// 5,5,5,5,5,5

	// 用箭头表示其前后的两次输出之间有 1 秒的时间间隔,
	// 而逗号表示其前后的两次输出之间的时间间隔可以忽略,代码实际运行的结果该如何描述?
	// 5 -> 5,5,5,5,5

	// 如果期望代码的输出变成:5 -> 0,1,2,3,4,该怎么改造代码?
	// 闭包
	for (var i = 0; i < 5; i++) {
		;(function (j) {
			// j = i
			setTimeout(function () {
				console.log(new Date(), j)
			}, 1000)
		})(i)
	}

	console.log(new Date(), i)

	// 增补
	for (var i = 0; i < 5; i++) {
		setTimeout(
			function (j) {
				console.log(new Date(), j)
			},
			1000,
			i
		)
	}

	console.log(new Date(), i)

	//  JS 中基本类型(Primitive Type)的参数传递是按值传递(Pass by Value)的特征
	// 利用了函数作用域
	var output = function (i) {
		setTimeout(function () {
			console.log(new Date(), i)
		}, 1000)
	}

	for (var i = 0; i < 5; i++) {
		output(i) // 这里传过去的 i 值被复制了
	}

	console.log(new Date(), i)

	/* 
  如果期望代码的输出变成 0 -> 1 -> 2 -> 3 -> 4 -> 5,
  并且要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?
  新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,
  循环结束后在大概第 5 秒的时候输出 5
  这里使用大概,是为了避免钻牛角尖的同学陷进去,因为 JS 中的定时器触发时机有可能是不确定的
  */

	/* const tasks = [];
  for (var i = 0; i < 5; i++) {   // 这里 i 的声明不能改成 let,如果要改该怎么做?
      ((j) => {
          tasks.push(new Promise((resolve) => {
              setTimeout(() => {
                  console.log(new Date, j);
                  resolve();  // 这里一定要 resolve,否则代码不会按预期 work
              }, 1000 * j);   // 定时器的超时时间逐步增加
          }));
      })(i);
  }
  
  Promise.all(tasks).then(() => {
      setTimeout(() => {
          console.log(new Date, i);
      }, 1000);   // 注意这里只需要把超时设置为 1 秒
  }); */

	const tasks = [] // 这里存放异步操作的 Promise
	const output = (i) =>
		new Promise((resolve) => {
			setTimeout(() => {
				console.log(new Date(), i)
				resolve()
			}, 1000 * i)
		})

	// 生成全部的异步操作
	for (var i = 0; i < 5; i++) {
		tasks.push(output(i))
	}

	// 异步操作完成之后,输出最后的 i
	Promise.all(tasks).then(() => {
		setTimeout(() => {
			console.log(new Date(), i)
		}, 1000)
	})

	// async await

	// 模拟其他语言中的 sleep,实际上可以是任何异步操作
	const sleep = (timeountMS) =>
		new Promise((resolve) => {
			setTimeout(resolve, timeountMS)
		})

	;(async () => {
		// 声明即执行的 async 函数表达式
		for (var i = 0; i < 5; i++) {
			if (i > 0) {
				await sleep(1000)
			}
			console.log(new Date(), i)
		}

		await sleep(1000)
		console.log(new Date(), i)
	})()

原型和原型链

原型和原型链

原型的作用:原型被定义为给其它对象提供共享属性的对象,函数的实例可以共享原型上的属性和方法
原型链:
	它的作用就是当你在访问一个对象上属性的时候,
	如果该对象内部不存在这个属性,那么就会去它__proto__属性所指向的对象(原型对象)上查找。
	如果原型对象依旧不存在这个属性,那么就会去其原型的__proto__属性所指向的原型对象上去查找。
	以此类推,直到找到null,而这个查找的线路,也就构成了我们常说的原型链
原型链和作用域的区别: 原型链是查找对象上的属性,作用域链是查找当前上下文中的变量

proto、prototype、constructor属性介绍

1)js中对象分为两种,普通对象和函数对象
2)__proto__和constructor是对象独有的。
	prototype属性是函数独有的,它的作用是包含可以给特定类型的所有实例提供共享的属性和方法;
	但是在 JS 中,函数也是对象,所以函数也拥有__proto__和 constructor属性
3)constructor属性是对象所独有的,它是一个对象指向一个函数,这个函数就是该对象的构造函数
	构造函数.prototype.constructor === 该构造函数本身
4)一个对象的__proto__指向其构造函数的prototype
	函数创建的对象.__proto__ === 该函数.prototype

原型链完整关系

在这里插入图片描述

function Foo () {}
const foo = new Foo()

// 以下关系全部成立
/*
	foo.constructor === Foo
	foo.__proto__ === Foo.prototype
	Foo.prototype.constructor === Foo
	Foo.prototype.__proto__ === Object.prototype
	Object.prototype.constructor === Object
	Object.prototype.__proto__ === null
	Foo.contructor === Function
	Foo.__proto__ === Function.prototype
	Object.contructor === Object
	Object.__proto__ === Function.prototype
	Function.prototype.constructor === Function
	Function.prototype.__proto__ === Object.prototype
	Function.constructor === Function
	Function.__proto__ === Function.prototype
*/

手写instanceof

	function Instanceof(fn, context) {
		const proto = context.__proto__
		if(proto) {
			if(proto === fn.prototype) {
				return true
			} else {
				return Instanceof(fn, proto)
			}
		} else {
			return false
		}
	}
	console.log(Instanceof(Array,[]))

继承

	// 原型链继承
	// 缺点:引用类型属性会被所有的实例对象共用
	function SuperClass() {
		this.name = 'Super'
		this.info = {
			child: 'Sub',
		}
	}
	function SubClass() {}

	SubClass.prototype = new SuperClass()

	const sub1 = new SubClass()
	const sub2 = new SubClass()

	sub1.name = 'sub1'
	console.log(sub1.name, sub2.name, '原型链继承 普通类型属性') // sub1, Super

	sub1.info.child = 'sub1'
	console.log(sub1.info.child, sub2.info.child, '原型链继承 引用类型属性') // sub1, sub1
	// 盗用构造函数继承
	// 缺点:无法访问父函数的原型对象
	function SuperClass() {
		this.name = 'Super'
		this.info = {
			child: 'Sub',
		}
	}
	function SubClass() {
		SuperClass.call(this)
	}

	SuperClass.prototype.super = 'prototype'

	const sub1 = new SubClass()
	const sub2 = new SubClass()

	sub1.name = 'sub1'
	console.log(sub1.name, sub2.name, '盗用构造函数继承 普通类型属性') // sub1, Super

	sub1.info.child = 'sub1'
	console.log(sub1.info.child, sub2.info.child, '盗用构造函数继承 引用类型属性') // sub1, Sub

	console.log(sub1.super, '盗用构造函数继承 访问父类的原型') // undefined
	// 组合式继承
	// 缺点:父函数会被执行两次
	function SuperClass() {
		this.name = 'Super'
		this.info = {
			child: 'Sub',
		}
		console.log('组合式继承 父类执行了')
	}
	function SubClass() {
		SuperClass.call(this)
	}
	SubClass.prototype = new SuperClass()
	SuperClass.prototype.super = 'prototype'

	const sub1 = new SubClass()
	const sub2 = new SubClass()

	sub1.name = 'sub1'
	console.log(sub1.name, sub2.name, '组合式继承 普通类型属性') // sub1, Super

	sub1.info.child = 'sub1'
	console.log(sub1.info.child, sub2.info.child, '组合式继承 引用类型属性') // sub1, Sub

	console.log(sub1.super, '组合式继承 访问父类的原型') // prototype
	// 原型式继承,原型链继承的封装,也是Object.crate()的实现
	// 未解决原型链继承的缺点
	function createObject(o) {
		function F() {}
		F.prototype = o
		return new F()
	}
	// 寄生式继承
	function SuperClass() {
		this.name = 'Super'
		this.info = {
			child: 'Sub',
		}
		console.log('寄生式继承 父类执行了')
	}
	SuperClass.prototype.super = 'prototype'
	const superClass = new SuperClass()

	function createObject(obj) {
		//通过原型方式创建新的对象
		let o = inheritObject(obj)
		// 在这可以添加自定义的属性和方法
		// 子类属性
		o.childName = 'childName'
		return o
	}

	const sub1 = createObject(superClass)
	const sub2 = createObject(superClass)

	console.log(sub1.info.child, '寄生式继承 sub1.info.child before') // Sub
	sub1.info.child = 'child'
	console.log(sub1.info.child, '寄生式继承 sub1.info.child after') // child
	console.log(sub2.info.child) // Sub

	console.log(sub1.super, '寄生式继承 sub1.super') // prototype
	console.log(sub1.childName, '寄生式继承 sub1 childName') // childName
	// 寄生组合式继承
	function SuperClass() {
		this.name = 'Super'
		this.info = {
			child: 'Sub',
		}
		console.log('寄生组合式继承 父类执行了')
	}
	function SubClass() {
		SuperClass.call(this)
	}
	SuperClass.prototype.super = 'prototype'

	function inheritProtype(SubClass, SuperClass) {
		let p = inheritObject(SuperClass.prototype)
		SubClass.prototype = p
		p.constructor = SubClass
	}

	inheritProtype(SubClass, SuperClass)
	const sub1 = new SubClass()
	const sub2 = new SubClass()

	console.log(sub1.info.child, '寄生组合式继承 sub1.info.child before') // Sub
	sub1.info.child = 'child'
	console.log(sub1.info.child, '寄生组合式继承 sub1.info.child after') // child
	console.log(sub2.info.child) // Sub

	console.log(sub1.super, '寄生组合式继承 sub1.super') // prototype

ES6 类

1) Class 类可以看作是构造函数的语法糖
2) Class 类中定义的方法,都是定义在该构造函数的原型上
3)使用static关键字,作为静态方法(静态方法,只能通过类调用,实例不能调用)
4)extents 关键字实际是寄生组合继承

了避免与访问器属性冲突,在构造函数中使用了一个带有下划线前缀的私有属性_myProperty。这是一种常见的命名约定,用于表示该属性应该被视为私有的,以防止直接访问

	function Foo() {
		getName = function () {
			console.log(1)
		}
		return this
	}
	// 静态方法
	Foo.getName = function () {
		console.log(2)
	}
	// 成员方法
	Foo.prototype.getName = function () {
		console.log(3)
	}

	// 函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被同名变量赋值后覆盖
	var getName = function () {
		console.log(4)
	}
	function getName() {
		console.log(5)
	}

	//请写出以下输出结果:
	Foo.getName() // 2
	getName() // 4
	// Foo().getName(); // undefined is not a function
	getName() // 4
	new Foo.getName() // 2
	new Foo().getName() // 3
	new new Foo().getName() // 3

Promise

手写promise

	class MyPromise {
		constructor(execute) {
			this.state = 'pending'
			this.data = undefined
			this.error = undefined
			this.resolveTask = []
			this.rejectTask = []
			
			try{
				execute(this.resolve.bind(this), this.reject.bind(this))
			} catch(e) {
				this.reject(e)
			}
		}
		resolve = (value) => {
			if(this.state !== 'pending') return
			this.state = 'fulfilled'
			this.data = value
			this.resolveTask.forEach(cb => cb())
		}
	
		reject = (error) => {
			if(this.state !== 'pending') return
			this.state = 'rejected'
			this.error= error
			this.rejectTask .forEach(cb => cb())
		}
		
		then = (onResolve, onReject) => {
			onResolve = typeof onResolve === 'function' ? onResolve : value => value
			onReject = typeof onReject === 'function' ? onReject : (error) => throw error
			return new MyPromise((resolve, reject) => {
				this.resolveTask.push(() => {
					const res = onResolve(this.data)
					if(res instanceof MyPromise) {
						res.then(resolve, reject)
					} else {
						resolve(res)
					}
				})
				this.rejectTask.push(() => {
					const res = onReject(this.error)
					if(res instanceof MyPromise) {
						res.then(resolve, reject)
					} else {
						reject(res)
					}
				})
			})
		}
		catch = (onReject) => {
			return this.then(undefined, onReject)
		}
	
		static resolve = (value) => {
			return new MyPromise((resolve, reject) => {
				if(value instanceof MyPromise) {
					value.then(resolve, reject)
				} else {
					resolve(value)
				}
			})
		}
	
		static reject  = (error) => {
			return new MyPromise((resolve, reject) => {
				if(value instanceof MyPromise) {
					error.then(resolve, reject)
				} else {
					reject(error)
				}
			})
		}
	
		static race = (promises) => {
			return new MyPromise((resolve, reject) => {
				for(let i = 0; i < promises.length; i++) {
					MyPromise.resolve(promises[i])
						.then(value => {
							resolve(value)
						}, error => {
							reject(error)
						})
				}
			})
		}
		static all = (promises) => {
			const result = []
			let index = 0
			return new MyPromise((resolve, reject) => {
				for(let i = 0; i < promises.length; i++) {
					MyPromise.resolve(promises[i])
						.then(value => {
							result[i] = value
							index++
							if(index === promises.length - 1) {
								resolve(resolve(value))
							}
						}, error => {
							reject(error)
						})
				}
			})
		}
	
		static retry(fn, delay, times) {
			return new MyPromise((resolve, reject) => {
				function func() {
					MyPromise.resolve(fn())
						.then((res) => {
							resolve(res)
						})
						.catch((err) => {
							// 接口失败后,判断剩余次数不为0时,继续重发
							if (times !== 0) {
								setTimeout(func, delay)
								times--
							} else {
								reject(err)
							}
						})
				}
				func()
			})
		}
	}

	// 打印结果:依次打印1、2
	new MyPromise((resolve, reject) => {
		setTimeout(() => {
			resolve(1)
		}, 500)
	})
		.then((res) => {
			console.log(res)
			return new MyPromise((resolve) => {
				setTimeout(() => {
					resolve(2)
				}, 1000)
			})
		})
		.then((data) => {
			console.log(data)
		})

async await generator

	async 是 generator 的语法糖,返回一个Promise对象
	await 只能写在 async 函数中,作用就是获取Promise中返回的reslove或者reject值
	generator函数跟普通函数在写法上的区别就是,多了一个星号*
	只有在generator函数中才能使用yield,相当于generator函数执行的中途暂停点
	generator函数是不会自动执行的,每一次调用它的next方法,会停留在下一个yield的位置

generatorToAsync

	function generatorToAsync(generatorFunc) {
		return function(...args) {
			const gen = generatorFunc.apply(this, args)
			return new Promise((resolve, reject) => {
				function step(key, arg) {
					let generatorResult
					try {
						generatorResult = gen[key](arg)
					}catch(e) {
						return reject(e)
					}
					const { value, done } = generatorResult
					if(done) {
						return resolve(value)
					} else {
						Promise.resolve(value)
							.then(res => {
								step('next', res)
							}, err => {
								step('throw', err)
							})
					}
				}
				step('next')
			})
		}
	}

	// 1秒后打印data1 再过一秒打印data2 最后打印success
	const getData = () =>
		new Promise((resolve) => setTimeout(() => resolve('data'), 1000))
	const test = generatorToAsync(function* testG() {
		// await被编译成了yield
		const data = yield getData()
		console.log('data1: ', data)
		const data2 = yield getData()
		console.log('data2: ', data2)
		return 'success'
	})

	test().then((res) => console.log(res))

深拷贝

	// 深拷贝第一种方法(开发中比较常用,但是有局限性)
	// JSON.parse(JSON.stringify(obj))不能对函数、正则、时间对象、数字对象的时候会不好用
	// 手写深拷贝
	let obj = {
		a: 100,
		b: [10, 20, 30],
		c: {
			x: 10,
		},
		d: /^\d+$/,
	}
	function deepClone(obj) {
		if(obj === null) return obj
		if(typeof obj !== 'object') return obj
		if(obj instanceof Function) return  obj
		if(obj instanceof RegExp) {
			return new RegExp(obj)
		}
		if(obj instanceof Date) {
			return new Date(obj)
		}
		
		const newObj = new obj.constructor()
		for(let key in obj) {
			if(obj.hasOwnProperty(key)) {
				newObj[key] = deepClone(obj[key])
			}
		}
		return newObj
	}

	let obj1 = deepClone(obj)
	obj1.a = 200
	console.log(obj, obj1)

	function deepCloneWeakMap(target, hash = new WeakMap()) {
		const isObject = (obj) => typeof obj === 'obj' && obj !== null
		if(!isObject(target)) return target
		if(hash.get(target)) return hash.get(target)
		const newObj = Array.isArray(target) ? [] : {}
		hash.set(target, newObj)
		for(let key in target) {
			if(target.hasOwnProperty(key)) {
				if(isObject(target[key])) {
					newObj[key] = deepCloneWeakMap(target[key], hash)
				} else {
					newObj[key] = target[key]
				}
			}
		}
		return newObj
	}
	let obj1 = deepCloneWeakMap(obj)
	obj1.a = 200
	console.log(obj, obj1)

事件轮询

事件轮询机制 Event loop

  JS的一大特点是单线程,所有任务都得排队,前一个任务结束,后一个任务才会执行,如果前一个任务执行时间过长,后一个任务就不得不等着
  这里的任务分为两种: 宏任务 和 微任务
  当宏任务执行完成后,会判断微任务队列中是否有任务,如果有,则把微任务放到主线程中并执行,如果没有,执行下一个宏任务
 	宏任务:在主线程上排队执行的任务,前一个任务执行完毕,才能执行下一个任务
      分类:script全部代码(注意同步代码也属于宏任务)、setTimeout、setInterval、setImmediate、requestAnimationFrame (task 任务源)
      1.宏任务所处的队列就是宏任务队列
      2.第一个宏任务队列中只有一个任务:执行主线程的js代码
      3.宏任务队列可以有多个
      4.当宏任务队列的中的任务压部执行完以后会查看是否有微任务队列如果有先执行微任务队列中的所有任务,
          最后再执行宏任务队列中的函数
    微任务:不进入主线程,进入微任务队列的任务
        分类:new Promise( ).then(回调) process.nextTick、MutationObserver
        1.微任务所处的队列就是微任务队列
        2.只有一个微任务队列
        3.在上一个宏任务队列执行完毕后如果有微任务队列就会执行微任务队列中的所有任务
  事件轮询机制的执行过程
	1、代码执行过程中,宏任务和微任务分别放在不同的队列中
	2、当某个宏任务执行完成后,会查看微任务队列是否任务,如果有,执行微任务队列中的所有微任务
	3、微任务执行完成后,读取宏任务队列中排在第一个的宏任务(注意宏任务是一个一个读取),执行该宏任务,执行过程中遇到微任务,依次加入到微任务队列
	4、宏任务执行完成,再次读取微任务队列中的微任务,并执行,以此类推
	举个简单的例子,假设一个script标签的代码如下:
	Promise.resolve().then(function promise1 () {
	  console.log('promise1');
	})
    setTimeout(function setTimeout1 (){
	  console.log('setTimeout1')
	  Promise.resolve().then(function  promise2 () {
		console.log('promise2');
	  })
	}, 0)
    setTimeout(function setTimeout2 (){
	  console.log('setTimeout2')
	}, 0)
 script里的代码被列为一个task,放入task队列。
	循环1:
		【task队列:script ;microtask队列:】
			从task队列中取出script任务,推入栈中执行。
			promise1列为microtask,setTimeout1列为task,setTimeout2列为task。
		【task队列:setTimeout1 setTimeout2;microtask队列:promise1】
			 script任务执行完毕,执行microtask checkpoint,取出microtask队列的promise1执行。
	循环2:
		【task队列:setTimeout1 setTimeout2;microtask队列:】
			从task队列中取出setTimeout1,推入栈中执行,将promise2列为microtask。
		【task队列:setTimeout2;microtask队列:promise2】
			执行microtask checkpoint,取出microtask队列的promise2执行。
	循环3:
		【task队列:setTimeout2;microtask队列:】
			从task队列中取出setTimeout2,推入栈中执行。
			setTimeout2任务执行完毕,执行microtask checkpoint。
		【task队列:;microtask队列:】

event loop中的Update the rendering(更新渲染)

    渲染的基本流程:
      1、处理 HTML 标记并构建 DOM 树。
      2、处理 CSS 标记并构建 CSSOM 树, 将 DOM 与 CSSOM 合并成一个渲染树。
      3、根据渲染树来布局,以计算每个节点的几何信息。
      4、将各个节点绘制到屏幕上。
    可以看到渲染树的一个重要组成部分是CSSOM树,绘制会等待css样式全部加载完成才进行,所以css样式加载的快慢是首屏呈现快慢的关键点。

event loop和浏览器渲染时机

    浏览器更新渲染会在event loop中的 宏任务 和 微任务 完成后进行,即宏任务 → 微任务 → 渲染更新(先宏任务 再微任务,然后再渲染更新
    宏任务队列中,如果有大量任务等待执行时,将dom的变动作为微任务,能更快的将变化呈现给用户,这样就可以在这一次的事件轮询中更新dom

event loop 和 vue nextTick

vue nextTick为什么要优先使用微任务实现?
  1、vue nextTick的源码实现,优先级判断,总结就是Promise > MutationObserver > setImmediate > setTimeout
  2、优先使用Promise,因为根据event loop与浏览器更新渲染时机,使用微任务,本次event loop轮询就可以获取到更新的dom
  3、如果使用宏任务,要到下一次event loop中,才能获取到更新的dom

node事件轮询

  process.nextTick 是 Node.js 自身定义实现的一种机制,有自己的 nextTickQueue
  process.nextTick执行顺序早于微任务
   process.nextTick()
   setTimeout()
   setImmediate()

  nodejs的事件轮询机制 :借助libnv库实现的
   概括事件轮询机制,分为六个阶段
    1. timers定时器阶段
      计时和执行到点的定时器回调函数
    2. pending callbacks
      某些系统操作(例如TCP错误类型)的回调函数
    3. idle, prepare
      准备工作
    4. poll轮询阶段(轮询队列)
      如果轮询队列不为空,依次同步取出轮询队列中第一个回调函数执行,直到轮询队列为空或者达到系统最大的限制
      如果轮询队列为空
        如果之前设置过setImmediate函数
            直接进入下一个check阶段
        如果之前没有设置过setImmediate函数
          在当前poll阶段等待直到轮询队列添加回调函数,就去第一个情况执行
        如果定时器到点了,也会去下一个阶段
    5. check查阶段
      执行setImmediate设置的回调函数
    6. close callbacks
      关闭阶段执行close事件回调函数
  process.nextTick能在任意阶段优先执行

相关题目

	console.log('-------start------')

	setTimeout(() => {
		console.log('setTimeout()')
	}, 0)

	new Promise((resolve, reject) => {
		for (var i = 0; i < 5; i++) {
			console.log(i)
		}
		resolve()
	}).then(() => {
		console.log('Promise()')
	})

	console.log('----end----')

	// 执行结果: start 0 1 2 3 4 end Promise() setTimeout()
Promise.resolve()
		.then(function () {
			console.log('promise0')
		})
		.then(function () {
			console.log('promise5')
		})
	setTimeout(() => {
		console.log('timer1')
		Promise.resolve().then(function () {
			console.log('promise2')
		})
		Promise.resolve().then(function () {
			console.log('promise4')
		})
	}, 0)
	setTimeout(() => {
		console.log('timer2')
		Promise.resolve().then(function () {
			console.log('promise3')
		})
	}, 0)
	Promise.resolve().then(function () {
		console.log('promise1')
	})
	console.log('start')

	// 打印结果:start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3
	console.log('script start')
	async function async1() {
		await async2() // await 隐式返回promise
		console.log('async1 end') // 这里的执行时机:在执行微任务时执行
	}
	async function async2() {
		console.log('async2 end') // 这里是同步代码
	}
	async1()
	setTimeout(function () {
		console.log('setTimeout')
	}, 0)
	new Promise((resolve) => {
		console.log('Promise') // 这里是同步代码
		resolve()
	})
		.then(function () {
			console.log('promise1')
		})
		.then(function () {
			console.log('promise2')
		})
	console.log('script end')

	/**
	 * 首先同步任务先执行:script start 、遇到定时器,放到宏任务队列中,async1 start,遇到await,放入微任务队列中,执行async2,等待返回值async2
	 * 后面的代码将在同步任务执行完之后再执行,继续执行promise,第一个函数仍然是同步代码,执行promise1,后面的函数放入微任务队列
	 * 执行script end 同步任务执行完,执行异步微任务 async1 end、 promise2,这两者的顺序没有定论,看浏览器,最后执行宏任务setTimeout
	 */
	// 结果:script start -->  async2 end --> Promise -->
	// script end --> async1 end --> promise1 --> promise2 --> setTimeout

数组操作

for…in和for…of的区别

	for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
	for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
	对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

手写Reduce

	Array.prototype.Reduce = function(fn, initValue) {
		if(typeof fn !== 'function') {
			throw new TypeError(`${fn} is not a function`)
		}
		let pre, index
		const arr = this.slice()
		// 如果没有提供initValue,找到数组中的第一个存在的值作为pre,下一个元素的下标作为index
		if(initValue === undefined) {
			for(let i = 0; i < arr.length; i++) {
				if(!arr.hasOwnProperty(i)) continue
				pre = arr[i]
				index = i + 1
				break
			}
		} else {
			// 如果提供了initValue时,则作为pre的初始值,index从0开始
			pre = initValue
			index = 0
		}
		for(let i = index; i < arr.length; i++) {
			if(arr.hasOwnProperty(i)) {
				// 函数接收4个参数 pre, cur, index, array
				pre = fn.call(undefined, pre, arr[i], i, this)
			}
		}
		return pre
	}
	console.log([, , , 1, 2, 3, 4].Reduce((pre, cur) => pre + cur)) // 10

手写Map

	Array.prototype.Map = function(fn, context) {
		if(typeof fn !== 'function') {
			throw new TypeError(`${fn} is not a function`)
		}
		const arr = this.slice()
		const list = new Array(arr.length)
		for(let i = 0; i < arr.length; i++) {
			if(arr.hasOwnProperty(i)) {
				list[i] = fn.call(context, arr[i], i, this)
			}
		}
		return list
	}
	console.log([1, 2, 3].Map((item) => item * 2)) // [2, 4, 6]

手写filter

	Array.prototype.Filter = function(fn, context) {
		if(typeof fn !== 'function') {
			throw new TypeError(`${fn} is not a function`)
		}
		const list = []
		for(let i = 0; i < this.length; i++) {
			if(fn.call(context, this[i], i, this)) {
				list.push(this[i])
			}
		}
		return list
	}
	console.log([1, 2, 3, 4].Filter((item) => item > 2)) // [3, 4]

手写some

	Array.prototype.Some = function(fn) {
		if(typeof fn !== 'function') {
			throw new TypeError(`${fn} is not a function`)
		}
		let result = false
		for(let i = 0; i < this.length; i++) {
			if(fn(this[i], i)) {
				result = true
				break
			}
		}
		return result
	}
	console.log([1, 2, 3, 4].Some((item) => item > 6)) // false
	console.log([1, 2, 3, 4].Some((item) => item > 2)) // true

手写Every

	Array.prototype.Every= function(fn) {
		if(typeof fn !== 'function') {
			throw new TypeError(`${fn} is not a function`)
		}
		let result = false
		let index = 0
		for(let i = 0; i < this.length; i++) {
			if(fn(this[i], i)) {
				index++
				if(index === this.length - 1) {
					reslut = true
				}
			}
		}
		return result
	}
	console.log([1, 2, 3, 4].Every((item) => item > 4)) // false
	console.log([1, 2, 3, 4].Every((item) => item > 0)) // true

手写Flat

	Array.prototype.Flat = function(deep) {
		const arr = this.slice()
		if(deep === 0) return arr
		arr.reduce((pre, cur) => {
			if(Array.isArray(cur)) {
				return [..pre, ... cur.Flat(deep - 1)]
			} else {
				return [...pre, cur]
			}
		}, [])
	}
	const arr1 = [0, 1, [2, [3, [4, 5]]]]
	console.log(arr1.flat())
	console.log(arr1.flat(2))
	console.log(arr1.flat(Infinity))

类型判断

基础数据类型

Undefined Null Number String Boolean Object Symbol BigInt (后面两个ES6新增)
基础数据类型(存放在栈中):Undefined Null Number String Boolean Symbol BigInt 
引用数据类型(存放在堆中):Object (对象、数组、函数)

isNaN 和 Number.isNaN

	NaN 是一个特殊的警戒值,它表示非数字,并且它不等于自身 
		NaN !== NaN (true)
		typeof NaN === 'number' (true)
	isNaN 会将传入的值进行数字转换,任何不能进行数字转换的值都返回true
	Number.isNaN 会判断传入的值是否是数字,判断为数字再判断是不是NaN,不进行数据转换

转换到字符串

undefined -> 'undefined' null -> 'null' true -> 'true' false -> 'false'
数字正常转换(极大极小值使用指数形式)
Symbol直接转换(只允许显式强制转换,隐式强制转换会报错)
引用类型转换会调用toString()方法(toSting可以自定义)返回内部 [[class]] 的值

转换到数字

undefined -> NaN null -> 0 true -> 1 false -> 0
'' -> 0 含有非数字的字符串 -> NaN
Symbol 不能转数字

转换到boolean

undfeined null +0 -0 '' NaN false 都为false 其余的逻辑上都为true

类型转换

// 当a等于什么的时候能使下面的条件成立
 var a = ?
if (a == 1 && a == 2 && a == 3) {
   console.log(1);
}

/**
 *  == 的转换规则
 * 
 *  对象==字符串 对象.toString

    null==undefined 相等 但是和其他值不相等

    NaN!=NaN

    剩下的都转换成数字
 */
// 对象==字符串 对象.toString
// 利用这个思想,将a写为一个对象,并且重写其toSrting方法,在第一次执行的时候返回1
// 在第二次执行的时候返回2,第三次执行的时候返回3,使条件成立
var a = {
  i:1,
  toString() {
    if (i = 1) {
      return this.i++
    } else if (i = 2) {
      return this.i++
    } else {
      return this.i
    }
  }
}

// 利用Object.defineProperty进行数据劫持
var i = 0
Object.defineProperty(window, 'a', {
  get() {
     return ++i
  }
})

// 数组弹出
var a = [1, 2, 3]
a.toString = a.shift

if (a == 1 && a == 2 && a == 3) {
	console.log('成立')
}

手写Typeof

	function Typeof(context) {
		return Object.prototype.toString.call(context).slice(8, -1).toLowerCase()
	}
	const foo = () => {}
	const str = '1'
	const boo = false
	const n = null
	const u = undefined
	const s = Symbol()
	const b = BigInt(9007199254740991)
	console.log(Typeof(foo))
	console.log(Typeof(str))
	console.log(Typeof(boo))
	console.log(Typeof(n))
	console.log(Typeof(u))
	console.log(Typeof(s))
	console.log(Typeof(b))

手写instanceof

	function Instanceof(context, fn) {
		const proto = context.__proto__
		if (proto) {
			if (proto === fn.prototype) {
				return true
			} else {
				return Instanceof(proto, fn)
			}
		} else {
			return false
		}
	}

	const foo = () => {}
	const o = new Object()
	const a = new Array()
	const m = new Map()
	const w = new WeakMap()
	const s = new Set()

	console.log(Instanceof(foo, Function))
	console.log(Instanceof(o, Object))
	console.log(Instanceof(a, Array))
	console.log(Instanceof(m, Map))
	console.log(Instanceof(w, WeakMap))
	console.log(Instanceof(s, Set))

|| && 的返回值

|| -> true: 返回第一个操作数的值(不是条件结果) false: 返回第二个操作数的值(不是条件结果)
&& -> true: 返回第二个操作数的值(不是条件结果) false: 返回第一个操作数的值(不是条件结果)

map 和 Object 的区别

	意外的键:
		map不存在任何额外的键,只包含显示插入
		object存在原型对象,可能会跟原型上的键名重复
	键的类型
		map的键值可以是任何类型
		object的键值只能是string 或者 Symbol
	键的顺序
		map的键是有序的
		object的键是无序的
	大小
		map 可以通过size轻松获取
		object 只能手动计算
	迭代
		map可以直接迭代
		object 需要获取键后才能迭代
	性能
		频繁删减下map优于object

map 和 WeakMap

Map 数据结构。它类似于对象,也是键值对的集合,
	但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。
	但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。
	而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制

函数操作

compose

  在函数式编程当中有一个很重要的概念就是函数组合
  将一系列函数,通过compose函数组合起来,像管道一样连接起来,比如函数结合[f, g, h ],通过compose最终达到这样的效果: f(g(h()))
  compose函数要求:可执行同步方法,也可执行异步方法,两者都可以兼容
	function compose(list) {
		const init  = list.shift()
		return function(...args) {
			return list.reduce((pre, cur) => {
				pre.then(res => {
					return cur.call(undefined, res)	
				})
			}, Promise.resolve(init.apply(undefined, args)))
		}
	}

	// 同步方法案例
	let sync1 = (data) => {
		console.log('sync1')
		return data
	}
	let sync2 = (data) => {
		console.log('sync2')
		return data + 1
	}
	let sync3 = (data) => {
		console.log('sync3')
		return data + 2
	}
	let syncFn = compose([sync1, sync2, sync3])
	syncFn(0).then((res) => {
		console.log(res)
	})
	// 依次打印 sync1 → sync2 → sync3 → 3

	// 异步方法案例
	let async1 = (data) => {
		return new Promise((resolve) => {
			setTimeout(() => {
				console.log('async1')
				resolve(data)
			}, 1000)
		})
	}
	let async2 = (data) => {
		return new Promise((resolve) => {
			setTimeout(() => {
				console.log('async2')
				resolve(data + 1)
			}, 1000)
		})
	}
	let async3 = (data) => {
		return new Promise((resolve) => {
			setTimeout(() => {
				console.log('async3')
				resolve(data + 2)
			}, 1000)
		})
	}
	let composeFn = compose([async1, async2, async3])
	composeFn(0).then((res) => {
		console.log(res)
	})
	// 依次打印 async1 → async2 → async3 → 3

函数柯里化

  函数柯里化: 将使用多个参数的一个函数,转换成一系列使用一个参数的函数
  函数柯里化的原理: 用闭包把参数保存起来,当参数的长度等于原函数时,就开始执行原函数
	function curry(fn) {
		// fn.length 表示函数中参数的长度
		// 函数的length属性,表示形参的个数,不包含剩余参数,仅包括第一个有默认值之前的参数个数(不包含有默认值的参数)
		if(fn.length < 1) return fn()
		const generator = (...args) => {
			if(fn.length === args.length) {
				return fn(...args)
			} else {
				return (...args1) => {
					return generator(...args, ...args1)
				}
			}
		}
		return generator
	}
	function fn(a, b, c, d) {
		return a + b + c + d
	}
	let fn1 = curry(fn)
	console.log(fn1(1)(2)(3)(4)) // 10

BOM

定时器

  setTimeout固定时长后执行
  setInterval间隔固定时间重复执行
  setTimeout、setInterval最短时长为4ms

  定时器不准的原因
    setTimeout/setInterval执行的时间并不是确定的,由于 setTimeout/setInterval 是宏任务,
    根据事件轮询,如果上一个宏任务阻塞延迟了,代码执行时间超过了定时器的时间就会出现定时器不准的情况
  动画卡顿
    不同屏幕的刷新频率不同,定时器只能设置固定的时间间隔,这个时间间隔可能跟屏幕的刷新间隔不同

  requestAnimationFrame
    requestAnimationFrame 是浏览器专门为动画提供的API
    requestAnimationFrame刷新频率与显示器的刷新频率保持一致,使用该api可以避免使用setTimeout/setInterval造成动画卡顿的情况
    requestAnimationFrame:告诉浏览器在下次重绘之前执行传入的回调函数(通常是操纵dom,更新动画的函数)

  setTimeout、setInterval、requestAnimationFrame 三者的区别
    1)引擎层面
      setTimeout属于 JS引擎 ,存在事件轮询
      requestAnimationFrame 属于 GUI引擎
      JS引擎与GUI引擎是互斥的,也就是说 GUI引擎在渲染时会阻塞JS引擎的计算
      这样设计的原因,如果在GUI渲染的时候,JS同时又改变了dom,那么就会造成页面渲染不同步
    2)性能层面
      当页面被隐藏或最小化时,定时器 setTimeout仍会在后台执行动画任务
      当页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停,requestAnimationFrame也会停止

setTimeout模拟setInterval

	function mySetInterval(fn, wait) {
		let timer
		function interval() {
			fn()
			timer = setTimeout(interval, wait)
		}
		interval()
		return {
			cancel() {
				clearTimeout(timer)
			}
		}
	}

	mySetInterval(() => {
		console.log(11)
	}, 1000)

setInterval模拟setTimeout

	function mySetTimeout(fn, wait) {
		let timer = setInterval(() => {
			fn()
			clearInterval(timer)
		}, wait)
	}

	mySetTimeout(() => {
		console.log(22)
	}, 1000)

web worker

  Web Worker
    专门处理复杂计算的,从此让前端拥有后端的计算能力
  页面大量计算,造成假死
    浏览器有GUI渲染线程与JS引擎线程,这两个线程是互斥的关系
    当js有大量计算时,会造成UI 阻塞,出现界面卡顿、掉帧等情况,严重时会出现页面卡死的情况,俗称假死

  计算时长超过多久适合用Web Worker
    原则:
      运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker
      但还要先考虑通信时长的问题,假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢
  最终标准:
    计算的运算时长 - 通信时长 > 50ms,推荐使用Web Worker

  Web Worker的限制
    1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象
    2、Worker中只能获取到部分浏览器提供的 API,如定时器、navigator、location、XMLHttpRequest等
    3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求
    4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值