JS基础总结

news/2024/12/21 20:52:58/

JS基础总结

  • WebAPI
      • 获取元素
      • 事件
      • 事件源的位置
      • 操作元素
      • 元素节点
      • 元素属性
      • BOM对象
      • 操作元素综合示例(键盘移动活动表格)
  • 执行上下文和执行栈
      • 执行上下文
      • 执行上下文的特点
      • 执行上下文的生命周期
      • 执行栈
  • 作用域
      • var let const的区别
      • 作用域链
      • 作用域和值类型引用类型的值传递
      • 变量提升
  • this的指向问题
      • this的绑定方式
      • 手写 new
      • 手写call、apply、bind
      • 相关题目
  • 闭包
      • 防抖
      • 节流
      • 相关题目
  • 原型和原型链
      • 原型和原型链
      • proto、prototype、constructor属性介绍
      • 原型链完整关系
      • 手写instanceof
      • 继承
      • ES6 类
  • Promise
      • 手写promise
      • async await generator
      • generatorToAsync
  • 深拷贝
  • 事件轮询
      • 事件轮询机制 Event loop
      • event loop中的Update the rendering(更新渲染)
      • event loop和浏览器渲染时机
      • event loop 和 vue nextTick
      • node事件轮询
      • 相关题目
  • 数组操作
      • for...in和for...of的区别
      • 手写Reduce
      • 手写Map
      • 手写filter
      • 手写some
      • 手写Every
      • 手写Flat
  • 类型判断
      • 基础数据类型
      • isNaN 和 Number.isNaN
      • 转换到字符串
      • 转换到数字
      • 转换到boolean
      • 类型转换
      • 手写Typeof
      • 手写instanceof
      • || && 的返回值
      • map 和 Object 的区别
      • map 和 WeakMap
  • 函数操作
      • compose
      • 函数柯里化
  • BOM
      • 定时器
      • setTimeout模拟setInterval
      • setInterval模拟setTimeout
      • web worker

WebAPI

获取元素

	document.getElementById()document.getElementsByTagName()// H5document.getElementsByClassName()document.querySelector()document.querySelectorAll()// 获取htmldocument.documentElement// 获取bodydocument.body

事件

javascript">	// 鼠标事件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个属性

操作元素

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

元素节点

javascript">	// 示例:body元素// 父节点document.body.parentNode// 子节点(包含空格)document.body.childNodes// 子元素document.body.children// 第一个,最后一个节点(包含元素节点和文本节点)document.body.firstChilddocument.body.lastChild// 第一个,最后一个节点(包含元素节点,不包含文本节点)document.body.firstElementChilddocument.body.lastElementChild// 下一个,上一个节点(包含元素节点和文本节点)document.body.nextSiblingdocument.body.previousSibling// 下一个,上一个节点(包含元素节点,不包含文本节点)document.body.nextElementSiblingdocument.body.previousElementSibling// 节点增删改document.createElement()document.body.appendChild()document.body.insertBefore()document.body.removeChild()// 克隆元素document.body.cloneNode()

元素属性

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

BOM对象

javascript">	// 所有元素加载完成window.onload = () => {}// 计时器setTimeout(() => {}, 0)setInterval(() => {}, 0)// locationconst {hash,host,href,pathname,port,protocol,search,assign,reload,replace,} = location// localstoragelocalStorage.setItem('localStorage', 1)localStorage.getItem('localStorage')localStorage.removeItem('localStorage')localStorage.clear()// sessionstoragesessionStorage.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>javascript">document.onkeydown = event => {if (!event) returnvar code = event.keyCode || ''if (!{ '37': 1, '38': 1, '39': 1, '40': 1 }[code]) returnvar 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) 全局: window4) 局部:局部变量2.确认this的指向1) 全局: this -- -> window2) 局部: 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用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明

作用域链

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

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

javascript">	// 全局作用域下有 num1 : 55 ,num2: 66,俩个变量var num1 = 55var num2 = 66function f1(num, num1) {// 根据传入的参数,变量的提升,预编译// 函数作用域下有 num:55 ,num1:66// var num = 55// var num1 = 66// 此时变量num和num1被改为了100num = 100num1 = 100// 根据作用域链,函数内部无num2变量,往上一级作用域寻找,变量提升,全局变量的num2被改为了100num2 = 100console.log(num) //100console.log(num1) //100console.log(num2) //100}f1(num1, num2)console.log(num1) //55console.log(num2) //100,全局变量num2在函数种被改为了100console.log(num); //not define 报错 全局作用域下无num属性
javascript">	function Person(name, age, salary) {// 函数作用域下有 name age salary 三个变量this.name = namethis.age = agethis.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: 10person = new Person('aa', 18, 10) //将person指向新的Person对象}// p的作用域下 name: zs, age: 18, salary: 1000var 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(){} 提前定义该函数变量的提升是变量名的提升,函数提升是整体的提升变量与函数同名,提升以函数为准
javascript">	console.log(username) //undefinedvar username = 'kobe'console.log(username) //kobefun() //fun()function fun() {console.log('fun()')}
javascript">	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() // 2getName() //4// 这里的执行顺序是先执行Foo函数,也就是(Foo()).getName(),// Foo执行后,函数中的getName没有变量修饰符,也就是会在全局变量中找,那么此时全局变量中的getName被1函数赋值了// Foo返回了一个this值,this指向window,最后的变成window.getName(),此时getName是全局函数,因此会执行,输出1Foo().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 
javascript">	function A() {console.log(1)}function Fn() {A = function () {console.log(2)}return this}Fn.A = AFn.prototype = {A: () => {console.log(3)},}A() //1Fn.A() //1Fn().A() //2new Fn.A() //1new Fn().A() //3new 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

javascript">	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

javascript">	Function.prototype.Call = function(context, ...args) {if(!context) context = windowconst f = Symbol()context[f] = thisconst res = context[f](...args)delete context[f]return res}Function.prototype.Apply = function(context, ...args) {if(!context) context = windowconst f = Symbol()context[f] = thisconst res = context[f](args)delete context[f]return res}Function.prototype.Bind= function(context, ...args) {if(!context) context = windowconst f = Symbol()context[f] = thisreturn 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' 100consoleInfo.Call(obj, 'man', 100) // 'objName' 'man' 100consoleInfo.Call(obj, 'woman', 120) // 'objName' 'woman' 120consoleInfo.Apply(obj, ['man', 100]) // 'objName' 'man' 100consoleInfo.Apply(obj, ['woman', 120]) // 'objName' 'woman' 120consoleInfo.Bind(obj, 'man', 100)() // 'objName' 'man' 100consoleInfo.Bind(obj, 'woman', 120)() // 'objName' 'woman' 120

相关题目

javascript">	var a = 10function foo() {// 默认模式下 函数的this执行windowconsole.log(this.a) // 10}foo()
javascript">	'use strict'var a = 10function foo() {// 严格模式下 函数的this 指向 undefinedconsole.log('this1', this) // undefinedconsole.log(window.a) // 10console.log(this.a) // 报错,undefined上没a}console.log(window.foo) // f foo(){...}console.log('this2', this) // windiowfoo()
javascript">	// let const 声明的变量不存在变量提升 window下无 a,b 变量let a = 10const b = 20function foo() {console.log(this.a) // undefinedconsole.log(this.b) // undefined}foo()console.log(window.a) // undefined
javascript">	// let const 声明的变量不存在变量提升 window下无 a,b 变量let a = 10const b = 20function foo() {console.log(this.a) // undefinedconsole.log(this.b) // undefined}foo()console.log(window.a) // undefined
javascript">	var a = 1function foo() {var a = 2console.log(this) // windowconsole.log(this.a) // 1}foo()
javascript">	var a = 1function foo() {var a = 2function inner() {// 默认模式下,函数的this指向widowconsole.log(this.a) // 1}inner()}foo()
javascript">	function foo() {console.log(this.a)}var obj = { a: 1, foo }var a = 2foo() // 2 显示绑定,window调用,函数this默认指向windowobj.foo() // 1 隐式绑定,由obj调用foo,obj中存在a变量为1,谁调用指向谁
javascript">	function foo() {console.log(this.a)}var obj = { a: 1, foo }var a = 2var foo2 = obj.fooobj.foo() //1  隐式绑定,由obj调用foo,obj中存在a变量为1,谁调用指向谁foo2() // 2 显示绑定,window调用,函数this默认指向window
javascript">	function foo() {console.log(this.a)}var obj = { a: 1, foo }var a = 2var foo2 = obj.foovar obj2 = { a: 3, foo2: obj.foo }obj.foo() // 1foo2() // 2obj2.foo2() // 3
javascript">	function foo() {console.log(this.a)}function doFoo(fn) {// doFoo 作用域下 fn = fooconsole.log(this)fn()}var obj = { a: 1, foo }var a = 2doFoo(obj.foo) // window 2 显式绑定
javascript">	function foo() {console.log(this.a)}function doFoo(fn) {// doFoo 作用域下 fn = fooconsole.log(this)fn()}var obj = { a: 1, foo }var a = 2var obj2 = { a: 3, doFoo }obj2.doFoo(obj.foo) // obj2 2 隐式绑定
javascript">	'use strict'function foo() {console.log(this.a)}function doFoo(fn) {console.log(this)fn()}var obj = { a: 1, foo }var a = 2var obj2 = { a: 3, doFoo }obj2.doFoo(obj.foo) // obj2 undefined 没有 a,严格模式下this指向undefined
javascript">	function foo() {console.log(this.a)}var obj = { a: 1 }var a = 2foo() // 2foo.call(obj) // 1foo.apply(obj) // 1foo.bind(obj)() // 1
javascript">	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 = 3obj2.foo1() // 2obj2.foo2() // window 3
javascript">	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 = 3obj2.foo1() // 2obj2.foo2() // obj1 1
javascript">	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 = 3obj2.foo1() // 2obj2.foo2() // window 3
javascript">	function foo() {console.log(this.a)}var obj = { a: 1 }var a = 2foo() // 2foo.call(obj) // 1foo().call(obj) // 2,  Cannot read property 'call' of undefined
javascript">	function foo() {console.log(this.a)return function () {console.log(this.a)}}var obj = { a: 1 }var a = 2foo() // 2foo.call(obj) // 1foo().call(obj) // 2 1
javascript">	function foo() {console.log(this.a)return function () {console.log(this.a)}}var obj = { a: 1 }var a = 2foo() // 2foo.bind(obj)foo().bind(obj) // 2
javascript">	function foo() {console.log(this.a)return function () {console.log(this.a)}}var obj = { a: 1 }var a = 2foo.call(obj)() // 1 2
javascript">	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'
javascript">	var obj = {a: 1,foo: function (b) {b = b || this.areturn function (c) {console.log(this.a + b + c)}},}var a = 2var obj2 = { a: 3 }obj.foo(a).call(obj2, 1) // 6 a = 3 b = 2 c = 1obj.foo.call(obj2)(1) // 6 a = 2 b = 3 c = 1
javascript">	function foo1() {console.log(this.a)}var a = 1var obj = {a: 2,}var foo2 = function () {foo1.call(obj)}foo2() // 2foo2.call(window) // 2
javascript">	function foo1(b) {console.log(`${this.a} + ${b}`)return this.a + b}var a = 1var obj = {a: 2,}var foo2 = function () {return foo1.call(obj, ...arguments)}var num = foo2(3) // 2 + 3console.log(num) // 5
javascript">	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)
javascript">	function Person(name) {this.name = name}var name = 'window'var person1 = new Person('LinDaiDai')console.log(person1.name) // 'LinDaiDai'
javascript">	function Person(name) {this.name = namethis.foo1 = function () {console.log(this.name)}this.foo2 = function () {return function () {console.log(this.name)}}}var person1 = new Person('person1')person1.foo1() // person1person1.foo2()() // undefined
javascript">var name = 'window'function Person(name) {this.name = namethis.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' windowperson2.foo()() // 'person2' window
javascript">	var name = 'window'function Person(name) {this.name = namethis.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' windowperson1.foo().call(person2) // 'person1' 'person2'
javascript">	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() // windowobj.foo2()() // obj obj
javascript">	var name = 'window'var obj1 = {name: 'obj1',foo: function () {console.log(this.name)},}var obj2 = {name: 'obj2',foo: () => {console.log(this.name)},}obj1.foo() // obj1obj2.foo() // window
javascript">	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 windowobj2.foo()() // obj2 obj2obj3.foo()() // window windowobj4.foo()() // window window
javascript">	var name = 'window'function Person(name) {this.name = namethis.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() // person1person1.foo2() // person1person2.foo2() // window
javascript">	var name = 'window'function Person(name) {this.name = namethis.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 windowperson1.foo2()() // person1 person1person1.foo3()() // person1 windowperson1.foo4()() // person1 person1
javascript">	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 obj2obj1.foo1().call(obj2) // obj1 obj1obj1.foo2.call(obj2)() // window windowobj1.foo2().call(obj2) // window obj2
javascript">	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() // person1person1.foo1.call(person2) // preson2person1.foo2() // windowperson1.foo2.call(person2) // windowperson1.foo3()() // windowperson1.foo3.call(person2)() // windowperson1.foo3().call(person2) // person2person1.foo4()() // person1person1.foo4.call(person2)() // person2person1.foo4().call(person2) // person1
javascript">	var name = 'window'function Person(name) {this.name = namethis.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() // person1person1.foo1.call(person2) // person2person1.foo2() // person1person1.foo2.call(person2) // person1person1.foo3()() // winodwperson1.foo3.call(person2)() // windowperson1.foo3().call(person2) // person2person1.foo4()() // person1person1.foo4.call(person2)() // person2person1.foo4().call(person2) // person1
javascript">	var name = 'window'function Person(name) {this.name = namethis.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()() // windowperson1.obj.foo1.call(person2)() // windowperson1.obj.foo1().call(person2) // person2person1.obj.foo2()() // objperson1.obj.foo2.call(person2)() // person2person1.obj.foo2().call(person2) //  obj
javascript">	function foo() {console.log(this.a) // 2}var a = 2;(function () {'use strict'foo() // 2, window调用})()

闭包

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

防抖

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

节流

javascript">	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 = nullreturn function() {const _this = thisconst args = argumentsif(!timer) {timer = setTimeout(function() {timer = nullfn.apply(_this, args)}, delay)}}}

相关题目

javascript">	function fun() {var count = 1// 此时已经形成闭包了function fun2() {console.log(count)}// 在fun2执行之后,闭包会被立即销毁fun2()}fun() // 1
javascript">	function fun() {var count = 1return function () {count++console.log(count)}}var fun2 = fun()// 此函数执行完闭包还未销毁fun2() //2// 此函数执行完后,闭包会销毁fun2() //3
javascript">	function fun(n, o) {// var n ,o// fun(0)时 n = 0,o = undefinedconsole.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) //0a.fun(2) //0a.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
javascript">	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在函数执行完之后并不会被销毁*/
javascript">	var a = 0,b = 0function 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'*/
javascript">	var x = 2var y = {x: 3,z: (function (x) {this.x *= xx += 2return function (n) {this.x *= nx += 3console.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.zm(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被改为了15console.log(x, y.x)
javascript">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 = isetTimeout(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 = [] // 这里存放异步操作的 Promiseconst 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))}// 异步操作完成之后,输出最后的 iPromise.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

原型链完整关系

在这里插入图片描述

javascript">function Foo () {}
const foo = new Foo()// 以下关系全部成立
/*foo.constructor === Foofoo.__proto__ === Foo.prototypeFoo.prototype.constructor === FooFoo.prototype.__proto__ === Object.prototypeObject.prototype.constructor === ObjectObject.prototype.__proto__ === nullFoo.contructor === FunctionFoo.__proto__ === Function.prototypeObject.contructor === ObjectObject.__proto__ === Function.prototypeFunction.prototype.constructor === FunctionFunction.prototype.__proto__ === Object.prototypeFunction.constructor === FunctionFunction.__proto__ === Function.prototype
*/

手写instanceof

javascript">	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,[]))

继承

javascript">	// 原型链继承// 缺点:引用类型属性会被所有的实例对象共用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, Supersub1.info.child = 'sub1'console.log(sub1.info.child, sub2.info.child, '原型链继承 引用类型属性') // sub1, sub1
javascript">	// 盗用构造函数继承// 缺点:无法访问父函数的原型对象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, Supersub1.info.child = 'sub1'console.log(sub1.info.child, sub2.info.child, '盗用构造函数继承 引用类型属性') // sub1, Subconsole.log(sub1.super, '盗用构造函数继承 访问父类的原型') // undefined
javascript">	// 组合式继承// 缺点:父函数会被执行两次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, Supersub1.info.child = 'sub1'console.log(sub1.info.child, sub2.info.child, '组合式继承 引用类型属性') // sub1, Subconsole.log(sub1.super, '组合式继承 访问父类的原型') // prototype
javascript">	// 原型式继承,原型链继承的封装,也是Object.crate()的实现// 未解决原型链继承的缺点function createObject(o) {function F() {}F.prototype = oreturn new F()}
javascript">	// 寄生式继承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') // Subsub1.info.child = 'child'console.log(sub1.info.child, '寄生式继承 sub1.info.child after') // childconsole.log(sub2.info.child) // Subconsole.log(sub1.super, '寄生式继承 sub1.super') // prototypeconsole.log(sub1.childName, '寄生式继承 sub1 childName') // childName
javascript">	// 寄生组合式继承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 = pp.constructor = SubClass}inheritProtype(SubClass, SuperClass)const sub1 = new SubClass()const sub2 = new SubClass()console.log(sub1.info.child, '寄生组合式继承 sub1.info.child before') // Subsub1.info.child = 'child'console.log(sub1.info.child, '寄生组合式继承 sub1.info.child after') // childconsole.log(sub2.info.child) // Subconsole.log(sub1.super, '寄生组合式继承 sub1.super') // prototype

ES6 类

1) Class 类可以看作是构造函数的语法糖
2) Class 类中定义的方法,都是定义在该构造函数的原型上
3)使用static关键字,作为静态方法(静态方法,只能通过类调用,实例不能调用)
4)extents 关键字实际是寄生组合继承了避免与访问器属性冲突,在构造函数中使用了一个带有下划线前缀的私有属性_myProperty。这是一种常见的命名约定,用于表示该属性应该被视为私有的,以防止直接访问
javascript">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() // 2getName() // 4// Foo().getName(); // undefined is not a functiongetName() // 4new Foo.getName() // 2new Foo().getName() // 3new new Foo().getName() // 3

Promise

手写promise

javascript">	class MyPromise {constructor(execute) {this.state = 'pending'this.data = undefinedthis.error = undefinedthis.resolveTask = []this.rejectTask = []try{execute(this.resolve.bind(this), this.reject.bind(this))} catch(e) {this.reject(e)}}resolve = (value) => {if(this.state !== 'pending') returnthis.state = 'fulfilled'this.data = valuethis.resolveTask.forEach(cb => cb())}reject = (error) => {if(this.state !== 'pending') returnthis.state = 'rejected'this.error= errorthis.rejectTask .forEach(cb => cb())}then = (onResolve, onReject) => {onResolve = typeof onResolve === 'function' ? onResolve : value => valueonReject = typeof onReject === 'function' ? onReject : (error) => throw errorreturn 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 = 0return new MyPromise((resolve, reject) => {for(let i = 0; i < promises.length; i++) {MyPromise.resolve(promises[i]).then(value => {result[i] = valueindex++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、2new 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

javascript">	function generatorToAsync(generatorFunc) {return function(...args) {const gen = generatorFunc.apply(this, args)return new Promise((resolve, reject) => {function step(key, arg) {let generatorResulttry {generatorResult = gen[key](arg)}catch(e) {return reject(e)}const { value, done } = generatorResultif(done) {return resolve(value)} else {Promise.resolve(value).then(res => {step('next', res)}, err => {step('throw', err)})}}step('next')})}}// 1秒后打印data1 再过一秒打印data2 最后打印successconst getData = () =>new Promise((resolve) => setTimeout(() => resolve('data'), 1000))const test = generatorToAsync(function* testG() {// await被编译成了yieldconst data = yield getData()console.log('data1: ', data)const data2 = yield getData()console.log('data2: ', data2)return 'success'})test().then((res) => console.log(res))

深拷贝

javascript">	// 深拷贝第一种方法(开发中比较常用,但是有局限性)// JSON.parse(JSON.stringify(obj))不能对函数、正则、时间对象、数字对象的时候会不好用
javascript">	// 手写深拷贝let obj = {a: 100,b: [10, 20, 30],c: {x: 10,},d: /^\d+$/,}function deepClone(obj) {if(obj === null) return objif(typeof obj !== 'object') return objif(obj instanceof Function) return  objif(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 = 200console.log(obj, obj1)function deepCloneWeakMap(target, hash = new WeakMap()) {const isObject = (obj) => typeof obj === 'obj' && obj !== nullif(!isObject(target)) return targetif(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 = 200console.log(obj, obj1)

事件轮询

事件轮询机制 Event loop

  JS的一大特点是单线程,所有任务都得排队,前一个任务结束,后一个任务才会执行,如果前一个任务执行时间过长,后一个任务就不得不等着这里的任务分为两种: 宏任务 和 微任务当宏任务执行完成后,会判断微任务队列中是否有任务,如果有,则把微任务放到主线程中并执行,如果没有,执行下一个宏任务宏任务:在主线程上排队执行的任务,前一个任务执行完毕,才能执行下一个任务分类:script全部代码(注意同步代码也属于宏任务)、setTimeout、setInterval、setImmediate、requestAnimationFrame (task 任务源)1.宏任务所处的队列就是宏任务队列2.第一个宏任务队列中只有一个任务:执行主线程的js代码3.宏任务队列可以有多个4.当宏任务队列的中的任务压部执行完以后会查看是否有微任务队列如果有先执行微任务队列中的所有任务,最后再执行宏任务队列中的函数微任务:不进入主线程,进入微任务队列的任务分类:new Promise( ).then(回调) process.nextTick、MutationObserver1.微任务所处的队列就是微任务队列2.只有一个微任务队列3.在上一个宏任务队列执行完毕后如果有微任务队列就会执行微任务队列中的所有任务事件轮询机制的执行过程1、代码执行过程中,宏任务和微任务分别放在不同的队列中2、当某个宏任务执行完成后,会查看微任务队列是否任务,如果有,执行微任务队列中的所有微任务3、微任务执行完成后,读取宏任务队列中排在第一个的宏任务(注意宏任务是一个一个读取),执行该宏任务,执行过程中遇到微任务,依次加入到微任务队列4、宏任务执行完成,再次读取微任务队列中的微任务,并执行,以此类推举个简单的例子,假设一个script标签的代码如下:
javascript">	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 > setTimeout2、优先使用Promise,因为根据event loop与浏览器更新渲染时机,使用微任务,本次event loop轮询就可以获取到更新的dom3、如果使用宏任务,要到下一次event loop中,才能获取到更新的dom

node事件轮询

  process.nextTick 是 Node.js 自身定义实现的一种机制,有自己的 nextTickQueueprocess.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能在任意阶段优先执行

相关题目

javascript">	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()
javascript">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
javascript">	console.log('script start')async function async1() {await async2() // await 隐式返回promiseconsole.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

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

手写Map

javascript">	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

javascript">	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

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

手写Every

javascript">	Array.prototype.Every= function(fn) {if(typeof fn !== 'function') {throw new TypeError(`${fn} is not a function`)}let result = falselet index = 0for(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)) // falseconsole.log([1, 2, 3, 4].Every((item) => item > 0)) // true

手写Flat

javascript">	Array.prototype.Flat = function(deep) {const arr = this.slice()if(deep === 0) return arrarr.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 会将传入的值进行数字转换,任何不能进行数字转换的值都返回trueNumber.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

类型转换

javascript">// 当a等于什么的时候能使下面的条件成立var a = ?
if (a == 1 && a == 2 && a == 3) {console.log(1);
}/***  == 的转换规则* *  对象==字符串 对象.toStringnull==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.shiftif (a == 1 && a == 2 && a == 3) {console.log('成立')
}

手写Typeof

javascript">	function Typeof(context) {return Object.prototype.toString.call(context).slice(8, -1).toLowerCase()}const foo = () => {}const str = '1'const boo = falseconst n = nullconst u = undefinedconst 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

javascript">	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函数要求:可执行同步方法,也可执行异步方法,两者都可以兼容
javascript">	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

函数柯里化

  函数柯里化: 将使用多个参数的一个函数,转换成一系列使用一个参数的函数函数柯里化的原理: 用闭包把参数保存起来,当参数的长度等于原函数时,就开始执行原函数
javascript">	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 是宏任务,根据事件轮询,如果上一个宏任务阻塞延迟了,代码执行时间超过了定时器的时间就会出现定时器不准的情况动画卡顿不同屏幕的刷新频率不同,定时器只能设置固定的时间间隔,这个时间间隔可能跟屏幕的刷新间隔不同requestAnimationFramerequestAnimationFrame 是浏览器专门为动画提供的APIrequestAnimationFrame刷新频率与显示器的刷新频率保持一致,使用该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

javascript">	function mySetInterval(fn, wait) {let timerfunction interval() {fn()timer = setTimeout(interval, wait)}interval()return {cancel() {clearTimeout(timer)}}}mySetInterval(() => {console.log(11)}, 1000)

setInterval模拟setTimeout

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

http://www.ppmy.cn/news/1536393.html

相关文章

Prometheus之Pushgateway使用

Pushgateway属于整个架构图的这一部分 The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped. The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus. S…

【C++】入门基础介绍(上)C++的发展历史与命名空间

文章目录 1. 前言2. C发展历史2. 1 C版本更新特性一览2. 2 关于C23的一个小故事: 3. C的重要性3. 1 编程语言排行榜3. 2 C在工作领域中的应用 4. C学习建议和书籍推荐4. 1 C学习难度4. 2 学习书籍推荐 5. C的第一个程序6. 命名空间6. 1 namespace的价值6. 2 namespace的定义6. …

计算机复习10.3

1.下面哪种攻击属于被动攻击(D)。 A. 拒绝服务 B.端口扫描 C. 缓冲区溢出 D. 网络窃听 . 解析&#xff1a;网络攻击&#xff1a;主动攻击(从入侵者角度) 和 被动攻击 拒绝服务&#xff1a;是指攻击者通过向目标服务器或网络发送大量的请求&#xff0c;使得目标系统资源耗尽&a…

如何使用 Gradio 创建聊天机器人

如何使用 Gradio 创建聊天机器人 文章目录 如何使用 Gradio 创建聊天机器人一、介绍二、简单示例与实战1、定义聊天功能2、示例&#xff1a;回答“是”或“否”的聊天机器人3、另一个使用用户输入和历史记录的示例4、流式聊天机器人 三、定制化聊天机器人1、为您的机器人添加更…

笔记本电脑如何改ip地址:操作指南与注意事项

在信息时代的浪潮中&#xff0c;网络已成为我们日常生活与工作中不可或缺的一部分。对于笔记本电脑用户而言&#xff0c;IP地址作为设备在网络中的唯一标识&#xff0c;其重要性不言而喻。无论是出于网络安全、网络测试还是特殊网络环境等需求&#xff0c;了解如何修改笔记本电…

基于深度学习的编程错误自动修复

基于深度学习的编程错误自动修复&#xff08;Automated Code Repair Using Deep Learning&#xff09;是一种利用深度学习技术自动检测、定位并修复代码中的错误的技术。它旨在减少开发者手动调试和修复代码的时间&#xff0c;并提高代码的质量和可靠性。这一技术在大规模软件开…

树和二叉树知识点大全及相关题目练习【数据结构】

树和二叉树 要注意树和二叉树是两个完全不同的结构、概念&#xff0c;它们之间不存在包含之类的关系 树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集&#xff0c;它或为空树&#xff08;n 0&#xff09;&#xff1b;或为非空树&a…

美容院管理创新:SpringBoot系统设计与开发

摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让美容院信息的管理模式进行升级&#xff0c;也为了更好的维护美容院信息&#xff0c;美容院管理系统的开发运用就显得很有必要。并且通过开发…