js作用域、闭包和浏览器渲染原理

一、js作用域

变量的作用域是指变量在整个程序中作用(可访问)的范围。

1. 作用域的类型

三个类型:

  • 全局作用域
  • 局部作用域/函数作用域
  • 块级作用域(ES6新增)
1. 全局作用域

如果一个变量为全局作用域,那么这个变量在程序的任意位置都可以访问。反之亦然。

var carName = " porsche";

console.log(carname)// 此处的代码能够使用 carName 变量,输出porsche

function myFunction() {

    console.log(carname)// 此处的代码也能够使用 carName 变量,输出porsche
}

能够全局访问的变量的声明位置一定不会在函数或者{}中。

2. 局部作用域

在函数内部声明的变量为函数作用域,也叫局部作用域。该变量只能在函数内部访问而不能在函数外部访问。

function myFunction() {
  var carName = "porsche";
  console.log(carName)
}
myFunction()
//输出porsche
function myFunction() {
  var carName = "porsche";
  console.log(carName)
}
console.log(carName)
// ReferenceError: carName is not defined
3. 块级作用域

let关键字定义的变量为块级作用域变量。let定义的变量在它定义的一个{}里生效。

{
  let a = 1
}
console.log(a)  // ReferenceError: a is not defined

变量生效的{}里面的{}也可以访问变量,也就是允许作用域嵌套。代码如下:

{
  let a = 1
  console.log(a)  // 输出1
  {
    console.log(a)   // 输出1
  }
}

内层作用域可以定义外层作用域的同名变量。代码如下:

{
    let insane = 'Hello World'
    {
      let insane = 'Hello World1'
      console.log(insane) // 输出'Hello World1'
    }
    console.log(insane) // 输出'Hello World'
}
{
    let insane = 'Hello World'
    {
      insane = 'Hello World1'
      console.log(insane)  // 输出'Hello World'
    }
    console.log(insane)  // 输出'Hello World'
}

上述两种输出结果说明:如果在内层作用域中定义了外层作用域同名变量,那么内外层作用域的变量不共用一个存储地址,也就是它们虽然同名但是实际上是两个完全不同的变量;如果内层作用域不声明同名变量只访问,那么就符合作用域嵌套原理。

2. varletconst的区别

  1. var声明的变量存在变量提升,letconst声明的变量不存在变量提升。

变量提升:javaScript在编译阶段会将变量和函数的声明首先放入内存中,这种形式和将变量和函数的声明提升到代码的最前面的效果一样,所以被称为变量提升。

注意:

  • 变量提升只会提升变量和函数的声明,而不会提升变量和函数的赋值。

    console.log(a)  // undefined
    var a = 1
    
  • 变量提升的优先级是:函数>变量。(仅限于函数和变量均在使用后声明的情况)

    console.log(a)  // [Function: a]
    var a = 1
    function a() {}
    console.log(a)  // 1
    
  1. var不存在暂时性死区,letconst存在暂时性死区。

暂时性死区:只有等到声明变量的那一行代码出现,才可以获取和使用该变量。(和变量提升是同一个原理)

// 1. var
console.log(a)  // undefined
var a = 10

// 2. let
console.log(b)  // Cannot access 'b' before initialization
let b = 10

// 3. const
console.log(c)  // Cannot access 'c' before initialization
const c = 10

  1. letconst属于块级作用域,var不属于块级作用域。

  2. var允许重复声明变量,letconst在同一作用域下(不包含嵌套的作用域)不允许重复声明变量。

    // 1. var
    var a = 10
    var a = 20 // 20
    
    // 2. let
    let b = 10
    let b = 20 // Identifier 'b' has already been declared
    
    // 3. const
    const c = 10
    const c = 20 // Identifier 'c' has already been declared
    
  3. 使用const声明的变量一旦赋值后就不能修改,而letvar不是。

综上所述,letconst属于一类,var属于一类;而letconst的区别就是const定义的是常量而let定义的是变量。

3. 作用域链

作用域链就是作用域的集合。

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。

var sex = '男';
function person() {
    var name = '张三';
    function student() {
        var age = 18;
        console.log(name); // 张三
        console.log(sex); // 男 
    }
    student();
    console.log(age); // Uncaught ReferenceError: age is not defined
}
person();
  • 首先分析该程序中的三个变量的作用域:
    • sex位于程序的最外层,程序的每一处都可以访问到它,因此是全局作用域。
    • nameperson函数中定义,只能在person函数或其嵌套的函数中被访问,所以属于函数作用域。
    • agestudent函数中定义,也为函数作用域。
  • 程序执行的过程中,调用student函数,需要输出name值,首先在student函数内部查找name变量,没有找到,就到student外部查找为[张三]。
  • 输出sex同理,在student外部作用域person中没有找到,就继续到上一层作用域也就是全局作用域中查找到了[男]。
  • age变量在student函数中定义,只能在student函数中被访问,因此无法输出age值。

二、闭包

1. 含义

计数器

/**
 * 请补全JavaScript代码,要求每次调用函数"closure"时会返回一个新计数器。每当调用某个计数器时会返回一个数字且该数字会累加1。
注意:
1. 初次调用返回值为1
2. 每个计数器所统计的数字是独立的
 */

const closure = () => {
  let count = 1
  return function() {
    return count++
  }
}

let counter = closure()
console.log(counter())  // 1
console.log(counter())  // 2
console.log(counter())  // 3

counter = null

这里首先调用了closure函数返回了一个新函数counter,相当于创建了一个父作用域,该作用域中定义了变量count初始值为1。然后每调用一次counter函数就会执行count++来修改父作用域的变量count值。

闭包的含义:闭包就是能够读取其他函数内部变量的函数。(counter函数就是一个闭包)

2. 闭包形成的条件

  1. 有函数嵌套
  2. 内部函数引用外部作用域的变量参数
  3. 返回值是函数
  4. 创建一个对象函数,让其长期驻留

创建对象函数之后,可以通过给该对象函数赋值为null进行内存销毁。

3. 闭包产生的原因

全局变量容易污染环境,而局部变量无法长期驻留内存,于是我们需要一种机制,既能长期保存变量又不污染全局,这就是闭包。

4. 闭包的优缺点

1. 优点
  1. 创建一个安全的环境,保证内部代码不受到外部的干涉。
  2. 让父函数中变量的值始终保存在内存中。
2. 缺点
  1. 内存消耗较大。
  2. 函数调用完需要手动回收变量。

三、浏览器渲染原理

1. 浏览器输入url之后会发生什么

  1. DNS域名解析
  2. 建立TCP连接
  3. 发起HTTP请求
  4. 接受响应结果
  5. 浏览器解析html
  6. 浏览器布局渲染

2. HTML文档的加载顺序

html文档是自上而下加载的。

  • head标签中遇到script标签时,如果引用了外部脚本就下载,否则就直接执行(此时控制权交给js引擎(解释器)),执行完毕后再将控制权交给渲染引擎。(**head**标签的加载)
  • head完毕后,开始解析body中的代码,此时如果遇到script,同样会将控制权交给js引擎。(**body**标签的加载)
  • body中的代码全部执行完毕,并且整个页面的css样式加载完毕,css会重新渲染整个页面的html元素。(**css**样式的加载)

所以,script标签写在标签靠后的位置较好,因为js会操作html元素,如果在body之前写js逻辑会造成找不到页面元素。

另:

  • 图片或视频的加载是异步的,也就是说在加载cssdom过程中,加入出现图片链接,浏览器会额外去下载这个图片,不会阻塞后面的资源解析,但是图片的加载却受css样式的影响,图片加载完成之前,css必须加载完成。
  • 如果遇到script中有错误语法,会直接报错并忽略该script块的执行,而跳到下一个script块执行。
  • 外部样式和外部脚本也是异步加载的,加载外部文件的时候,不会阻塞后面dom的解析,外部脚本执行没有asyncdefer的属性时,会被外部样式阻塞,也就是说要等到外部css加载完才会执行外部脚本,添加asyncdefer就不会受到阻塞。

3. 浏览器渲染过程

浏览器解析内容主要分为三个部分:

  • HTML/XHTML/SVG:解析这三种文件后,会生成DOM树(DOM Tree
  • CSS:解析样式表,生成CSS规则树(CSS Rule Tree
  • JavaScript:解析脚本,通过DOM APICSS DOM API操作DOM TreeCSS Rule Tree,与用户进行交互。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值