this指向问题
this绑定规则四个规则
1.默认绑定
2.隐式绑定
3.显示绑定
4.new绑定
规则一 默认绑定
独立函数调用为window,严格模式下为underfunded
function test1(){
console.log(this);
test2()
}
function test2(){
console.log(this);
test3()
}
function test3(){
console.log(this);
}
test1()
// 打印三个windows
规则二 隐式绑定
通过某个对象调用进行绑定this,这时this指向这个对象
function foo(){
console.log(this);
}
const obj1 = {
foo
}
const obj2 = {
obj1
}
obj2.obj1.foo()
// 打印为obj1
规则三 显示绑定
通过call、bind、apply方法对this进行定向指定(如果希望同时调用函数用call和apply)。第一个参数传入null和undefined会被视为默认绑定,则this为window
apply:第一个参数作为this指向的对象,第二个参数为数组列表(改this指向同时会调用函数, 数组作为函数参数传入)
function fn2(name, age){
console.log(this, name, age);
}
const obj = {
o: 'asdasd'
}
fn2.apply(obj, ['asd', 'ads'])
// 打印对象obj和name为asd及age为ads
call:第一个参数作为this指向的对象,其余参数作为函数参数使用(改this指向同时会调用函数)
function fn2(name, age){
console.log(this, name, age);
}
const obj = {
o: 'asdasd'
}
fn2.call(obj, 'asd', 'ads')
// 打印对象obj和name为asd及age为ads
bind:第一个参数是拿来做this指向的对象,其余参数作为函数参数使用,返回一个新的函数通过这个函数进行调用(不会主动进行调用)
function fn1(name, age){
console.log(this, name, age);
}
const obj = { o: 123 }
const fn2 = fn1.bind( obj, '232', 123)
fn2()
// 打印对象obj和name为232和age为123
规则四 new绑定
function fn2(age){
console.log(this);
this.age = age
}
const obj = new fn2('asd')
// 打印fn2
规则优先级
默认绑定最低
隐式绑定比显示绑定低
new绑定高于隐式绑定
new绑定高于bind(不存在apply、call和new绑定一起使用的情况)
箭头函数中this
箭头函数没有this, 箭头函数中的this是与外层this一样
浏览器渲染
网页解析过程
- dns服务器将域名解析为IP地址
- 建立TCP连接:三次握手
- 发送HTTP请求,服务器处理请求报文并返回HTTP报文
- 浏览器接受服务器返回文件,进行解析渲染页面
- 断开连接:四次挥手
浏览器渲染页面过程
先下载html,解析html,遇到link下载css文件,解析css文件。形成html树跟css树后进行render 树形成,
然后根据布局统一形成最终render树,然后进行绘制,最后展示-display(注意css解析及成树不会阻塞DOM树的形成,但会阻塞render树的形成)
注意:render树与DOM树并不是一一对应的关系,比如元素设置为display:none这种元素不会出现render树上
layout和paint
回流和重绘
回流:第一次确定节点的大小和位置,叫做布局。之后改变位置和大小引起修改计算叫做回流。
回流的几种情况:
重绘:第一次渲染内容称之为绘制,之后重新渲染称为重绘(重绘不会引起回流,回流一定会引起重绘,所以回流比较消耗性能)
重绘的几种情况:
尽量避免引起回流:
script元素
浏览器解析html过程中,遇到script元素的话是不能继续构建dom树,它会先下载js文件,执行完js文件,执行完js后,再继续解析html,构建DOM树
为什么先解析js文件
因为js作用之一就是操作DOM,修改DOM。等到DOM渲染后再执行js,会造成严重的回流和重绘,影响页面的性能
解决js文件执行会造成html阻塞
脚本往往比html页面更重,处理时间需要更长,所以会造成页面短时间空白,用户体验不好。script元素提供两个属性defer和async解决这个问题。
defer属性
这个属性告诉浏览器不要等待脚本下载,继续解析HTML,构建DOM树。
defer会等到DOM树构建完成,在DOMContentLoad事件之前执行defer的代码,执行完defer里面的内容后,再执行DOMContentLoad的代码。
1.多个defer会按照先后顺序执行
2.defer能提高性能,推荐放到head中
3.defer仅适用外部脚本,script里面的内容会被忽略
async属性
async属性不会阻塞html解析,构建DOM树
async缺点:
1.不能保证DOMContentLoad事件之前之后执行
2.多个async不能按照先后顺序执行,只是根据下载解析速度来决定
函数
函数对象的属性
属性name:返回函数名称
属性length:返回函数参数个数
传统函数里面arguments
arguments是一种类似数组的对象数据,能通过索引进行访问,却不能使用数组的一些方法。(箭头函数里面不绑定arguments,所以不能箭头函数使用arguments)
arguments转数组
纯函数
- 输入的值保证一定有返回的值
- 保证不能对其他变量的值进行修改
纯函数的优点: 可以保证外部传入的变量不会发生改变,有输入就会有想要结果返回
var names = ["abc", "cba", "nba", "mba"]
// 1.slice: 纯函数(没有修改传入值)
var newNames = [].slice.apply(names, [1, 3])
console.log(names)
// 2.splice: 操作数组的利器(不是纯函数,修改原来传入值)
names.splice(2, 2)
console.log(names)
函数柯里化
函数柯里化:是将一个多个参数复杂逻辑函数,改为接受单个参数的函数并返回此函数,以此进行返回单一参数的函数,最后一个函数返回最终值
function add(x: number){
return function(y: number){
return function(z: number){
return x + y + z
}
}
}
console.log(add(10)(20)(30));
// 打印60
柯里化优点:每个函数处理自己部分逻辑,而不是全部逻辑交给一个函数处理
自动柯里化函数
function foo(x, y, z) {
console.log(x + y + z)
}
// 手动转化
// 封装函数: 自动转化柯里化过程(有一点难度)
function hyCurrying(fn) {
function curryFn(...args) {
// 两类操作:
// 第一类操作: 继续返回一个新的函数, 继续接受参数
// 第二类操作: 直接执行fn的函数
if (args.length >= fn.length) { // 执行第二类
// return fn(...args)
console.log(this,'this');
return fn.apply(this, args)
} else { // 执行第一类
return function(...newArgs) {
// return curryFn(...args.concat(newArgs))
return curryFn.apply(this, args.concat(newArgs))
}
}
}
return curryFn
}
// 对其他的函数进行柯里化
var fooCurry = hyCurrying(foo)
fooCurry(10)(20)(30)
fooCurry(55, 12, 56)
组合函数
组合函数是将函数组合后返回成一个新的函数的一种函数。
with与eval函数(了解)
with
eval
Promise
Promise是一个类,翻译为承诺、许诺、期约
promise的状态一旦被确认后就是不能再次修改的
then方法-接受两个参数
then返回值
then本身也有返回值,它的返回值是一个Promise,所以我们可以进行如下的链式调用
then返回的Promise是:
当then方法中的回调函数本身在执行的时候,Promise状态处于pending状态。then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将返回值作为resolve的参数。
当then方法抛出一个异常时,那么Promise处于reject状态,后续可用catch获取获取返回值。
catch方法-返回值
finally方法
Promise无论是fulfilled还是reject状态,最后一步都会执行finally方法
Promise的类方法
resolve方法
Promise.resolve(参数)相当于实例化Promise且回调resolve函数
reject方法
all方法
Promise.all():可以将多个Promise做为all方法的参数,当所有的Promise状态变成fulfilled状态时,状态即为fulfilled,所有promise的返回值组成一个数组返回
当有一个reject,Promise.all的状态即为reject,并且会将第一个reject的返回值作为返回值
const p1 = new Promise((resolve, reject)=>{
resolve('123')
})
const p2 = new Promise((resolve, reject)=>{
reject('123')
})
const p3 = new Promise((resolve, reject)=>{
resolve('123')
})
const p4 = Promise.all([p1, p2, p3])
p4.then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
allSettled方法
该方法会在传入所有Promise有结果后,无论是fulfilled或reject都会执行到最后,且 Promise.allSettled这个新方法最终状态都是fulfilled,所以用then获取返回值(返回值是一个数组包着对象,里面有status跟value)
race方法
race有竞争和竞赛,表示多个Promise相互竞争,谁先有结果,就使用谁的结果
const p1 = new Promise((resolve, reject)=>{
setTimeout(() => {
resolve('123')
}, 100);
})
const p2 = new Promise((resolve, reject)=>{
setTimeout(() => {
reject('321')
}, 1000);
})
const p3 = new Promise((resolve, reject)=>{
setTimeout(() => {
resolve('1')
}, 10);
})
const p4 = Promise.race([p1, p2, p3])
p4.then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
// 打印为‘1’
any方法
let、const和var区别
var声明的变量会进行作用域提升,其实let和const声明的变量在执行下上文创建的时候也被创建出来,只是不能访问(所以不能认为let和const声明的变量不会进行作用域提升)
var声明的变量
var声明的变量会给window添加属性,还有var声明的变量没有块级作用域,(var只有函数作用域和全局作用域)
块级作用域
通过let、const、function、class声明的标识符是具备块级作用域的限制的(在非严格模式下函数没有块级作用域的限制)
暂时性死区
从块级作用域的顶部一直到变量声明之前,这个都处于暂时性死区
防抖和节流
防抖函数
防抖函数:触发一个事件,不会立即触发对应的函数,而是在对应时间后触发函数,当然在对应时间内再次触发事件,又会重新开始计时,等待对应时间过后才会触发对应的函数。
防抖应用场景:
普通防抖函数
function _debounce(fn, dealy){
let timer = null
return ()=>{
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn()
timer = null
},dealy)
}
}
获取事件的防抖函数
<!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>
</head>
<body>
<button>按钮</button>
<input type="text">
<script>
function hydebounce(fn, delay) {
// 1.用于记录上一次事件触发的timer
let timer = null
// 2.触发事件时执行的函数
const _debounce = function(...args) {
// 2.1.如果有再次触发(更多次触发)事件, 那么取消上一次的事件
if (timer) clearTimeout(timer)
// 2.2.延迟去执行对应的fn函数(传入的回调函数)
timer = setTimeout(() => {
console.log(args);
fn.apply(this, args)
timer = null // 执行过函数之后, 将timer重新置null
}, delay);
}
// 返回一个新的函数
return _debounce
}
</script>
<script>
// 1.获取input元素
const inputEl = document.querySelector("input")
console.log(inputEl.oninput);
// 未进行防抖处理代码
// let counter = 1
// inputEl.oninput = function(event) {
// console.log(`发送网络请求${counter++}:`, this, event)
// }
// 2.underscore防抖处理代码
// let counter = 1
// inputEl.oninput = _.debounce(function() {
// console.log(`发送网络请求${counter++}:`, this.value)
// }, 1000)
// 3.自己实现的防抖
let counter = 1
inputEl.oninput = hydebounce(function(event) {
console.log(`发送网络请求${counter++}:`, this, event)
}, 1000)
</script>
</body>
</html>
防抖函数加上取消功能
<!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>
</head>
<body>
<button>按钮</button>
<input type="text">
<button class="cancel">取消</button>
<script>
function hydebounce(fn, delay) {
// 1.用于记录上一次事件触发的timer
let timer = null
// 2.触发事件时执行的函数
const _debounce = function(...args) {
// 2.1.如果有再次触发(更多次触发)事件, 那么取消上一次的事件
if (timer) clearTimeout(timer)
// 2.2.延迟去执行对应的fn函数(传入的回调函数)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null // 执行过函数之后, 将timer重新置null
}, delay);
}
// 3.给_debounce绑定一个取消的函数
_debounce.cancel = function() {
if (timer) clearTimeout(timer)
}
// 返回一个新的函数
return _debounce
}
</script>
<script>
// 1.获取input元素
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector(".cancel")
// 未进行防抖处理代码
// let counter = 1
// inputEl.oninput = function(event) {
// console.log(`发送网络请求${counter++}:`, this, event)
// }
// 2.underscore防抖处理代码
// let counter = 1
// inputEl.oninput = _.debounce(function() {
// console.log(`发送网络请求${counter++}:`, this.value)
// }, 1000)
// 3.自己实现的防抖
let counter = 1
const debounceFn = hydebounce(function(event) {
console.log(`发送网络请求${counter++}:`, this, event)
}, 5000)
inputEl.oninput = debounceFn
// 4.实现取消的功能
cancelBtn.onclick = function() {
debounceFn.cancel()
}
</script>
</body>
</html>
首次是否立即执行
<!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>
</head>
<body>
<button>按钮</button>
<input type="text">
<button class="cancel">取消</button>
<script>
// 原则: 一个函数进行做一件事情, 一个变量也用于记录一种状态
function hydebounce(fn, delay, immediate = false) {
// 1.用于记录上一次事件触发的timer
let timer = null
let isInvoke = false
// 2.触发事件时执行的函数
const _debounce = function(...args) {
// 2.1.如果有再次触发(更多次触发)事件, 那么取消上一次的事件
if (timer) clearTimeout(timer)
// 第一次操作是不需要延迟
if (immediate && !isInvoke) {
fn.apply(this, args)
isInvoke = true
return
}
// 2.2.延迟去执行对应的fn函数(传入的回调函数)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null // 执行过函数之后, 将timer重新置null
isInvoke = false
}, delay);
}
// 3.给_debounce绑定一个取消的函数
_debounce.cancel = function() {
if (timer) clearTimeout(timer)
console.log(timer, 'timer');
timer = null
isInvoke = false
}
// 返回一个新的函数
return _debounce
}
</script>
<script>
// 1.获取input元素
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector(".cancel")
// 未进行防抖处理代码
// let counter = 1
// inputEl.oninput = function(event) {
// console.log(`发送网络请求${counter++}:`, this, event)
// }
// 2.underscore防抖处理代码
// let counter = 1
// inputEl.oninput = _.debounce(function() {
// console.log(`发送网络请求${counter++}:`, this.value)
// }, 1000)
// 3.自己实现的防抖
let counter = 1
const debounceFn = hydebounce(function(event) {
console.log(`发送网络请求${counter++}:`, this, event)
}, 1500)
inputEl.oninput = debounceFn
// 4.实现取消的功能
cancelBtn.onclick = function() {
debounceFn.cancel()
}
</script>
</body>
</html>
利用Promise获取返回值
<!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>
</head>
<body>
<button>按钮</button>
<input type="text">
<button class="cancel">取消</button>
<script>
// 原则: 一个函数进行做一件事情, 一个变量也用于记录一种状态
function hydebounce(fn, delay, immediate = false, resultCallback) {
// 1.用于记录上一次事件触发的timer
let timer = null
let isInvoke = false
// 2.触发事件时执行的函数
const _debounce = function(...args) {
return new Promise((resolve, reject) => {
try {
// 2.1.如果有再次触发(更多次触发)事件, 那么取消上一次的事件
if (timer) clearTimeout(timer)
// 第一次操作是不需要延迟
let res = undefined
if (immediate && !isInvoke) {
res = fn.apply(this, args)
if (resultCallback) resultCallback(res)
resolve(res)
isInvoke = true
return
}
// 2.2.延迟去执行对应的fn函数(传入的回调函数)
timer = setTimeout(() => {
res = fn.apply(this, args)
if (resultCallback) resultCallback(res)
resolve(res)
timer = null // 执行过函数之后, 将timer重新置null
isInvoke = false
}, delay);
} catch (error) {
reject(error)
}
})
}
// 3.给_debounce绑定一个取消的函数
_debounce.cancel = function() {
if (timer) clearTimeout(timer)
timer = null
isInvoke = false
}
// 返回一个新的函数
return _debounce
}
</script>
<script>
// 1.获取input元素
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector(".cancel")
// 2.手动绑定函数和执行
const myDebounceFn = hydebounce(function(name, age, height) {
console.log("----------", name, age, height)
return "coderwhy 哈哈哈哈"
}, 1000, false)
myDebounceFn("why", 18, 1.88).then(res => {
console.log("拿到执行结果:", res)
})
</script>
</body>
</html>
节流函数
节流函数:事件触发一定时间内,无论重复触发多少次该事件,函数都会按照一定时间后发生
<!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>
</head>
<body>
<button>按钮</button>
<input type="text">
<script>
function hythrottle(fn, interval, leading = true) {
let startTime = 0
const _throttle = function(...args) {
// 1.获取当前时间
const nowTime = new Date().getTime()
// 对立即执行进行控制
if (!leading && startTime === 0) {
startTime = nowTime
}
// 2.计算需要等待的时间执行函数
const waitTime = interval - (nowTime - startTime)
if (waitTime <= 0) {
fn.apply(this, args)
startTime = nowTime
}
}
return _throttle
}
</script>
<script>
// 1.获取input元素
const inputEl = document.querySelector("input")
// 2.underscore节流处理代码
// let counter = 1
// inputEl.oninput = _.throttle(function() {
// console.log(`发送网络请求${counter++}:`, this.value)
// }, 1000)
// 3.自己实现的节流函数
let counter = 1
inputEl.oninput = hythrottle(function(event) {
console.log(`发送网络请求${counter++}:`, this.value, event)
}, 1000)
</script>
</body>
</html>
节流应用场景:
引用赋值、浅拷贝和深拷贝
引用赋值:指针指向同一个对象,相互影响
浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响
深拷贝:两个对象没有任何关系,互不影响
const info = {
name: "why",
age: 18,
friend: {
name: "kobe"
},
// running: function() {},
// [Symbol()]: "abc",
// obj: info
}
// 循环引用
// info.obj = info
// 1.操作一: 引用赋值
// const obj1 = info
// 2.操作二: 浅拷贝
// const obj2 = { ...info }
// obj2.name = "james"
// obj2.friend.name = "james"
// console.log(info.friend.name)
// const obj3 = Object.assign({}, info)
// // obj3.name = "curry"
// obj3.friend.name = "curry"
// console.log(info.friend.name)
// 3.操作三: 深拷贝
// 3.1.JSON方法
// const obj4 = JSON.parse(JSON.stringify(info))
// info.friend.name = "curry"
// console.log(obj4.friend.name)
// console.log(obj4)
JSON.parse实现的深拷贝对函数和symbol和null和undefined是无法处理的,还有对象的循环引用用JSON.parse会报错
递归深拷贝函数
function deepCopy(info){
let typeValue = typeof info
if(!(info !== null && (typeValue === 'object' || typeValue ==='function'))){
return info
}
let newValue = Array.isArray(info) ? [] : {}
for(let key in info){
newValue[key] = deepCopy(info[key])
}
return newValue
}
async和await
async异步函数
await
localStorage与sessionStorage
Object.defineProperty 与 Object.defineProperties
Object.defineProperty
这个方法可以直接定义一个新属性,或者修改一个对象的现有属性,并返回此对象
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty可接收三个参数, obj要定义属性的对象,prop要定义或修改的属性的名称或Symbol,descriptor要定义或修改的属性描述符。(返回值:被传递给函数的对象)
属性描述符
数据属性描述符:
存取属性描述符
object.defineProperties(了解)
object其他对象方法补充
Proxy代理
proxy是es6新增的一个类,这个类是用来创造代理对象,通过代理对象对原对象实施监听。
proxy基本使用
proxy基本捕获器
proxy所有捕获器
proxy和Object.defineProperty对比
补充:1.proxy可以对整个对象的属性进行监听,而Object.defineProperty是进行循环对象属性监听
浏览器的事件循环
因为js语言是单线程,遇到程序过于复杂,这时候如果还是同步处理机制的话,就会出现阻塞。所以需要异步处理代码机制,浏览器为我们提供了事件循环机制