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事件机制来实现的线程之间的通信
标签:总结,function,console,log,基础,JS,var,obj,name
From: https://blog.csdn.net/SuihideOmelet/article/details/142743891