JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-(一)

简介: JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-

title: JavaScript高级进阶(更新中)
date: 2023-03-04 22:34:24.11
updated: 2023-04-02 21:45:50.35
url: /archives/javascript-gao-ji-jin-jie--geng-xin-zhong-
categories:
tags: js | javascript

JavaScript 高级

This 指向规则

案例

function foo() {
        console.log(this)
    }
    // 1 调用方式1
    foo();
    // 2 调用方式2 放入对象中调用
    var obj = {
        name: "why",
        foo: foo
    }
    obj.foo()
    // 调用方式三 通过 call/apply 调用
    foo.call("abc")

指向定义

this 是js 给函数的一个绑定值。

  1. 函数在调用时 JavaScript会默认给this绑定一个值;
  2. this的绑定和定义的位置(编写的位置)没有关系;
  3. this的绑定和调用方式以及调用的位置有关系
  4. this是在运行时被绑定的

无严格模式下 为 window 如果打开严格模式 则为 udnefined

this 的绑定规则如下:

  1. 绑定一:默认绑定 PS: 没有绑定到任何对象时 & 函数定义在对象中但是被独立调用 对象也是 window
  2. 绑定二:隐式绑定 PS:由JS 绑定到调用对象 指向对象
  3. 绑定三:new绑定
  • new 执行过程
  • 1 创建空对象
  • 2 修改this 指向为空对象
  • 3 执行函数体代码
  • 没有显示返回非空对象时 默认返回这个对象
  1. 绑定四 显示绑定
  • 如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用

function foo() {
        console.log(this)
}   
var obj = {
         name: "why",
       foo: foo
    }
foo.call(123)
console 输出内容 {name: 'why', foo: ƒ}


  • call/apply 可以帮助我们完成这个效果

额外函数补充

Call / Apply 调用方法 两者区别不大  但是又细微差别

apply

function foo(name, age, height) {
        console.log("foo 函数this 指向", this);
        console.log("参数:", name, age, height);
    }
 
    // 普通调用  直接入参
    foo("why", 18, 1.22)
    // apply
    // 第一个参数 绑定 this 
    // 第二个参数 传入额外的实参 以数组的形式
    //   foo.apply("apply",["why", 18, 1.22])
    foo.apply("123", ["why", 18, 1.22])

call

function foo(name, age, height) {
        console.log("foo 函数this 指向", this);
        console.log("参数:", name, age, height);
    }
    // call
    // 第一个参数 绑定 this
    // 后续参数以 参数列表形式 
    foo.call("call", "远目鸟", 18, 12)

两者 相同处 都是调用方法 第一参数都指向this 唯一区别只在后续传入的参数的形势

  • apply 为数组
  • call 为列表 以 , 分割

bind :会创建 绑定函数  我们希望调用foo 的时候总是让this 指向 obj

function foo() {
        console.log("foo 函数this 指向", this);
    }
    var obj = {
        name: "why"
    }
    // 需求 调用foo时 总是绑定 obj 
    var bar = foo.bind(obj)
    bar()

在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 实际开发中 使用不多 作为参考 了解即可

内置函数

一般 对于浏览器 的内置函数 或者是第三方框架的 this 指向 我们只能用经验去判断 一个一个去源码或者文档查看和并不现实

This 优先级

  1. 默认绑定 优先级最低
  2. 显式绑定 高于隐式绑定
  3. new 高于隐式绑定 PS:new不能和 call/apply 一起使用
  4. new绑定优先级高于bind
  5. 同显式 bind 优先级高于 call/apply

拓展: 规则之外

情况一:如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:

function foo() {
        console.log("foo 函数this 指向", this);
    }
    var obj = {
        name: "why"
    }
    foo.call(obj)
    foo.call(null)
    foo.call(undefined)
    var bar = foo.bind(obj)
    bar()

但是打开严格模式 就会可以使用基础属性 直接显示 null 或者 undefined

情况二:创建一个函数的 间接引用,这种情况使用默认绑定规则。

  • 这种情况 (obj2.foo = obj1.foo) 会使用默认规则 指向 window
var obj1 = {
        name: "obj1",
        foo: function () {
            console.log("foo 函数this 指向", this);
        }
    }
    var obj2 = {
        name: "obj2"
    };
    obj1.foo();
    (obj2.foo = obj1.foo)();

情况三:箭头函数

  • 箭头函数不会绑定this、arguments属性
  • 箭头函数不能作为构造函数来使用
// {} 是执行体
    var arrFn = () => { }
    // 指向的是对象 需要加小括号才可以做到
    var arrFn = () => ({ name: "why" })

箭头函数

  • 基本写法
  • ():函数的参数
  • {}:函数的执行体

var foo3 = (name, age) => {
      console.log("箭头函数的函数体")
      console.log(name, age)
    }


  • 优化写法
  • 只有一个参数时, 可以省略()
names.forEach(item => {
       console.log(item)
    })


  • 只有一行代码时, 可以省略{}
names.forEach(item => console.log(item))


  • 只要一行代码时, 表达式的返回值会作为箭头函数默认返回值, 所以可以省略return
var newNums = nums.filter(item => item % 2 === 0)
var newNums = nums.filter(item => item % 2 === 0)


  • 如果箭头函数默认返回的是对象, 在省略{}的时候, 对象必须使用()包裹 () => ({name: "why"})
var arrFn = () => ["abc", "cba"]
    var arrFn = () => {} // 注意: 这里是{}执行体
    var arrFn = () => ({ name: "why" })
    console.log(arrFn())


箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。

我们来看一个模拟网络请求的案例:

  • 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
  • 我们需要拿到obj对象,设置data;
  • 但是直接拿到的this是window,我们需要在外层定义:var _this = this
  • 在setTimeout的回调函数中使用this就代表了obj对象
  • 但是如果使用箭头函数根据特性他会向上寻找this 省去了_this = this的操作
var obj = {
        data: [],
        getData: function () {
            request("/11", (res) => {
                this.data = [].concat(res)
            })
        }
    }
    function request(url, callbackFn) {
        var res = ["abc", "cba", "nba"]
        callbackFn(res)
    }
    obj.getData()

总结

  1. this的指向问题与优先级 是踏入JS的敲门砖,如果不先系统了解之后使用的时候可能会出现奇怪的错误
  2. 使用ES6的语法 箭头函数 提前熟悉ES6语法可以提升开发效率

浏览器渲染原理

我们通过url 进入到页面拿到资源以及获得返回资源的过程

浏览器内核

常见的浏览器内核有

  • Trident ( 三叉戟):IE、360安全浏览器、搜狗高速浏览器、百度浏览器、UC浏览器;
  • Gecko( 壁虎) :Mozilla Firefox;
  • Presto(急板乐曲)-> Blink (眨眼):Opera
  • Webkit :Safari、360极速浏览器、搜狗高速浏览器、移动端浏览器(Android、iOS)
  • Webkit -> Blink :Google Chrome,Edge

页面渲染流程:

浏览器的渲染页面过程

HTML解析过程

一般情况下服务器会给浏览器返回 xx.html 文件 解析html 其实就是 Dom 树的构建过程

我们可以根据以下html 结构 来简单的分析出 html 的解析过程

解析CSS 规则树

在解析的过程中,如果遇到CSS的link元素,那么会由浏览器负责下载对应的CSS文件:


PS: 这里下载 CSS 是不会影响到 DOM树的解析的

下载完成后 就会对CSS 文件解析出对应的 规则树 , 案例如下图 :

body{font-size: 16px}
p{font-weight: bold}
span{color: red}
p span{display:none}
img{float: right}

解析步骤 构建 Render Tree

当有了DOM Tree和 CSSOM Tree后,就可以两个结合来构建Render Tree了

需要注意的是:

  • link元素不会阻塞DOM Tree的构建过程,但是会阻塞Render Tree的构建过程
  • Render Tree和DOM Tree并不是一一对应的关系,比如对于display为none的元素,压根不会出现在render tree中;

解析步骤 布局和绘制

  • 渲染树(Render Tree)上运行布局(Layout)以计算每个节点的几何体。
  • 渲染树会表示显示哪些节点以及其他样式,但是不表示每个节点的尺寸、位置等信息;
  • 布局是确定呈现树中所有节点的宽度、高度和位置信息;
  • 将每个节点绘制(Paint)到屏幕上
  • 在绘制阶段,浏览器将布局阶段计算的每个frame转为屏幕上实际的像素点;
  • 包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素(比如img)

渲染的流程可以参考下图 :

完成以上五步 成功在浏览器渲染出 对应的 xx.html 文件

回流和重绘

回流(reflow)

reflow

  • 我们渲染出来的节点大小位置 也就是布局时第一次渲染出之后就确定的
  • 之后对于节点大小和位置重新计算的行为 叫做回流(reflow)

回流在什么时候会出现 :

  1. DOM 结构发生变化 (添加 & 移除)
  2. 改变了 CSS 样式代码 也就是布局
  3. 修改了 窗口尺寸
  4. 或者是调用了某些内置函数 获取位置和尺寸信息

重绘 (reprint)

  • 我们渲染的第一次,在之前的流程图中叫做 printing
  • 在之后需要重新渲染的时候 成为重绘

重绘怎么出现 :

  1. 修改CSS 如 颜色 文字样式

拓展思路

  1. 只要出现回流 就一定会引起重绘 其实看到上述的解释 也很容易就发现 回流也是在出发样式代码或者改变的时候触发
  2. 回流的性能并不好 也很明显 重新渲染整个DOM 很浪费性能

总结

  • 修改样式 尽可能减少回流次数 也就是设计好之后,非必要不去改动样式和DOM的结构
  • 避免频繁使用 JS 去操作DOM
  • 尽可能减少函数获取储存位置的信息

特殊解析 - composite合成

绘制的过程,可以将布局后的元素绘制到多个合成图层中。

会形成新的合成层的属性:

  • 3D transforms
  • video、canvas、iframe
  • opacity 动画转换时
  • position: fixed
  • will-change
  • animation 或 transition 设置了opacity、transform

PS:分层确实可以提高性能,但是它以内存管理为代价,所以不作为性能优化策略来使用

script元素和页面解析的关系

JS 在我们渲染过程中的那一步呢?

  1. 在渲染html的时候 js 没有继续构造DOM的能力
  2. 如果需要需要的部分 会先停止构建,下载js 执行脚本
  3. 把需要构建的东西构建完成后 继续执行构建 DOM

这么做有什么好处?

  1. JS 有操作和修改DOM的作用
  2. 为什么会先去执行js脚本? 因为之前提到了 回流时很吃性能的所以最好一次性弄好 减少不必要的回流

代码案例

index.html

<script src="./js/test.js"></script>
<body>
    <div class="box"></div>
</body>
<script>
    var boxel = document.getElementsByClassName("box")
    console.log(boxel);
</script>

test.js

debugger
console.log("hello")

新的问题:

  • 在现在的开发模式中 大多都是使用vue和React 作为开发框架 JS 的占比往往很大 处理事件也会变长
  • 这也导致了 如果解析阻塞 那么在脚本解析完成之前 可能界面什么都不显示

这里 js 给我们提供了两个属性 来解决这个问题

defer属性

defer 属性告诉浏览器不要等待脚本下载,而继续解析HTML,构建DOM Tree,如果脚本提前下载好就等待加载,等DOM完成 在触发DOMContentLoaded之前执行defer中的代码

PS: defer 按照默认顺序执行 不会影响顺序 且可以操作DOM

<script>
    window.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded");
    })
</script>
<script>
    var boxel = document.getElementsByClassName("box")
    console.log(boxel);
</script>
<script defer>
    console.log("defer-demo")
</script>
<script>
    debugger
    console.log("hello")
</script>

建议:

  • 将defer放入head中使用 这个属性的特性放在末尾 就本末倒置了
  • defer 只对外置脚本有效果

async属性

async 特性与 defer 有些类似,它也能够让脚本不阻塞页面。

它的特性:

  • 浏览器不会因 async 脚本而阻塞(与 defer 类似);
  • async脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本
  • async不会能保证在DOMContentLoaded之前或者之后执行
<script>
    window.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded");
    })
</script>
<script async>
    console.log("defer-demo")
</script>

总结

  • defer 通常用于文档解析操作DOM且有顺序要求的JS代码
  • async 通常用于独立脚本 可以理解为没有什么依赖的脚本 如果有依赖 那么不保证一定能提前加载到

总结

  1. 首先时了解和认识一些浏览器的内核
  2. 了解从服务器加载 到渲染页面的流程
  3. 细化每一步的大致内容
  4. 发现有问题且探索到问题的一些解决方法


JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-(二)https://developer.aliyun.com/article/1469504

目录
相关文章
|
30天前
|
存储 JSON JavaScript
JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-(三)
JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-
40 1
|
30天前
|
JavaScript 前端开发 算法
JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-(二)
JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-
28 0
|
30天前
|
JavaScript 前端开发 Web App开发
JavaScript基础语法(codewhy版本)(一)
JavaScript基础语法(codewhy版本)
88 1
JavaScript基础语法(codewhy版本)(一)
|
30天前
|
JavaScript 前端开发 算法
JavaScript基础语法(codewhy版本)(二)
JavaScript基础语法(codewhy版本)
20 0
JavaScript基础语法(codewhy版本)(二)
|
3月前
|
算法 JavaScript 前端开发
JavaScript学习 -- SM4算法应用实例
JavaScript学习 -- SM4算法应用实例
52 0
|
9月前
|
前端开发 JavaScript
JS(第二十五课)ES6语法Symbol
JS(第二十五课)ES6语法Symbol
61 0
|
11月前
|
JavaScript 前端开发 容器
javascript百炼成仙 第一章 掌握JavaScript基础1.8 对象的取值
javascript百炼成仙 第一章 掌握JavaScript基础1.8 对象的取值
123 0
|
JavaScript
第06/90步《番外篇》第3章 JS 语法实例讲解 第17课
今天学习《番外篇》第3章 JS 语法实例讲解 第17课 JS 语法基础一:基本数据类型和变量、常量,这节课主要学习 JS 语言的标识符、基本数据类型、变量、常量等内容。
52 0
|
JavaScript 云计算
第09/90步《番外篇》第3章 JS 语法实例讲解 第20课
今天学习《番外篇》第3章 JS 语法实例讲解 第20课 面向对象语法二:函数、类与作用域,这节课学习与函数、闭包、类和模块有关的面向对象编程基础语法。
49 0
|
JavaScript 前端开发
第07/90步《番外篇》第3章 JS 语法实例讲解 第18课
今天学习《番外篇》第3章 JS 语法实例讲解 第18课 JS 语法基础二:操作符和逻辑控制语句,这节课主要学习操作符和基本的逻辑控制语句。
46 0