JavaScript高级

111 阅读10分钟

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绑定

image.png

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一样

浏览器渲染

网页解析过程

  1. dns服务器将域名解析为IP地址
  2. 建立TCP连接:三次握手
  3. 发送HTTP请求,服务器处理请求报文并返回HTTP报文
  4. 浏览器接受服务器返回文件,进行解析渲染页面
  5. 断开连接:四次挥手

浏览器渲染页面过程

image.png

image.png 先下载html,解析html,遇到link下载css文件,解析css文件。形成html树跟css树后进行render 树形成, 然后根据布局统一形成最终render树,然后进行绘制,最后展示-display(注意css解析及成树不会阻塞DOM树的形成,但会阻塞render树的形成

注意:render树与DOM树并不是一一对应的关系,比如元素设置为display:none这种元素不会出现render树上

layout和paint

image.png

回流和重绘

回流:第一次确定节点的大小和位置,叫做布局。之后改变位置和大小引起修改计算叫做回流。

回流的几种情况:

image.png

重绘:第一次渲染内容称之为绘制,之后重新渲染称为重绘(重绘不会引起回流,回流一定会引起重绘,所以回流比较消耗性能)

重绘的几种情况:

image.png

尽量避免引起回流:

image.png

image.png

script元素

浏览器解析html过程中,遇到script元素的话是不能继续构建dom树,它会先下载js文件,执行完js文件,执行完js后,再继续解析html,构建DOM树

为什么先解析js文件

因为js作用之一就是操作DOM,修改DOM。等到DOM渲染后再执行js,会造成严重的回流和重绘,影响页面的性能

解决js文件执行会造成html阻塞

脚本往往比html页面更重,处理时间需要更长,所以会造成页面短时间空白,用户体验不好。script元素提供两个属性deferasync解决这个问题。

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:返回函数参数个数

image.png

image.png

image.png

传统函数里面arguments

arguments是一种类似数组的对象数据,能通过索引进行访问,却不能使用数组的一些方法。(箭头函数里面不绑定arguments,所以不能箭头函数使用arguments

arguments转数组

image.png

纯函数

  1. 输入的值保证一定有返回的值
  2. 保证不能对其他变量的值进行修改

纯函数的优点: 可以保证外部传入的变量不会发生改变,有输入就会有想要结果返回

    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)

组合函数

组合函数是将函数组合后返回成一个新的函数的一种函数。

image.png

image.png

with与eval函数(了解)

with

image.png

eval

image.png

Promise

Promise是一个类,翻译为承诺、许诺、期约

image.png

image.png

promise的状态一旦被确认后就是不能再次修改的

image.png

then方法-接受两个参数

image.png

then返回值

then本身也有返回值,它的返回值是一个Promise,所以我们可以进行如下的链式调用

then返回的Promise是:

当then方法中的回调函数本身在执行的时候,Promise状态处于pending状态。then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将返回值作为resolve的参数。

当then方法抛出一个异常时,那么Promise处于reject状态,后续可用catch获取获取返回值。

image.png

catch方法-返回值

image.png

finally方法

Promise无论是fulfilled还是reject状态,最后一步都会执行finally方法

image.png

Promise的类方法

resolve方法

Promise.resolve(参数)相当于实例化Promise且回调resolve函数

image.png

reject方法

image.png

all方法

Promise.all():可以将多个Promise做为all方法的参数,当所有的Promise状态变成fulfilled状态时,状态即为fulfilled,所有promise的返回值组成一个数组返回

当有一个reject,Promise.all的状态即为reject,并且会将第一个reject的返回值作为返回值

image.png

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)

image.png

race方法

race有竞争和竞赛,表示多个Promise相互竞争,谁先有结果,就使用谁的结果

image.png

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方法

image.png

let、const和var区别

var声明的变量会进行作用域提升,其实let和const声明的变量在执行下上文创建的时候也被创建出来,只是不能访问(所以不能认为let和const声明的变量不会进行作用域提升

var声明的变量

var声明的变量会给window添加属性,还有var声明的变量没有块级作用域,(var只有函数作用域和全局作用域

块级作用域

通过let、const、function、class声明的标识符是具备块级作用域的限制的(在非严格模式下函数没有块级作用域的限制

暂时性死区

从块级作用域的顶部一直到变量声明之前,这个都处于暂时性死区

防抖和节流

防抖函数

防抖函数:触发一个事件,不会立即触发对应的函数,而是在对应时间后触发函数,当然在对应时间内再次触发事件,又会重新开始计时,等待对应时间过后才会触发对应的函数。

防抖应用场景

image.png

普通防抖函数

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>

节流应用场景:

image.png

引用赋值、浅拷贝和深拷贝

引用赋值:指针指向同一个对象,相互影响

浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响

深拷贝:两个对象没有任何关系,互不影响

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异步函数

image.png

await

image.png

localStorage与sessionStorage

image.png

image.png

image.png

Object.defineProperty 与 Object.defineProperties

Object.defineProperty

这个方法可以直接定义一个新属性,或者修改一个对象的现有属性,并返回此对象

Object.defineProperty(obj, prop, descriptor)

Object.defineProperty可接收三个参数, obj要定义属性的对象,prop要定义或修改的属性的名称或Symbol,descriptor要定义或修改的属性描述符。(返回值:被传递给函数的对象

属性描述符

image.png

数据属性描述符:

image.png

image.png

存取属性描述符

image.png

image.png

object.defineProperties(了解)

image.png

object其他对象方法补充

image.png

Proxy代理

proxy是es6新增的一个类,这个类是用来创造代理对象,通过代理对象对原对象实施监听。

proxy基本使用

image.png

proxy基本捕获器

image.png

proxy所有捕获器

image.png

proxy和Object.defineProperty对比

image.png

补充:1.proxy可以对整个对象的属性进行监听,而Object.defineProperty是进行循环对象属性监听

浏览器的事件循环

因为js语言是单线程,遇到程序过于复杂,这时候如果还是同步处理机制的话,就会出现阻塞。所以需要异步处理代码机制,浏览器为我们提供了事件循环机制

image.png

宏任务和微任务

image.png