一、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. var
、let
、const
的区别
var
声明的变量存在变量提升,let
和const
声明的变量不存在变量提升。
变量提升:
javaScript
在编译阶段会将变量和函数的声明首先放入内存中,这种形式和将变量和函数的声明提升到代码的最前面的效果一样,所以被称为变量提升。注意:
变量提升只会提升变量和函数的声明,而不会提升变量和函数的赋值。
console.log(a) // undefined var a = 1
变量提升的优先级是:函数
>
变量。(仅限于函数和变量均在使用后声明的情况)console.log(a) // [Function: a] var a = 1 function a() {} console.log(a) // 1
var
不存在暂时性死区,let
和const
存在暂时性死区。
暂时性死区:只有等到声明变量的那一行代码出现,才可以获取和使用该变量。(和变量提升是同一个原理)
// 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
-
let
和const
属于块级作用域,var
不属于块级作用域。 -
var
允许重复声明变量,let
和const
在同一作用域下(不包含嵌套的作用域)不允许重复声明变量。// 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
-
使用
const
声明的变量一旦赋值后就不能修改,而let
和var
不是。
综上所述,
let
和const
属于一类,var
属于一类;而let
和const
的区别就是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
位于程序的最外层,程序的每一处都可以访问到它,因此是全局作用域。name
在person
函数中定义,只能在person
函数或其嵌套的函数中被访问,所以属于函数作用域。age
在student
函数中定义,也为函数作用域。- 程序执行的过程中,调用
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. 闭包形成的条件
- 有函数嵌套
- 内部函数引用外部作用域的变量参数
- 返回值是函数
- 创建一个对象函数,让其长期驻留
创建对象函数之后,可以通过给该对象函数赋值为
null
进行内存销毁。
3. 闭包产生的原因
全局变量容易污染环境,而局部变量无法长期驻留内存,于是我们需要一种机制,既能长期保存变量又不污染全局,这就是闭包。
4. 闭包的优缺点
1. 优点
- 创建一个安全的环境,保证内部代码不受到外部的干涉。
- 让父函数中变量的值始终保存在内存中。
2. 缺点
- 内存消耗较大。
- 函数调用完需要手动回收变量。
三、浏览器渲染原理
1. 浏览器输入url
之后会发生什么
DNS
域名解析- 建立
TCP
连接 - 发起
HTTP
请求 - 接受响应结果
- 浏览器解析
html
- 浏览器布局渲染
2. HTML
文档的加载顺序
html
文档是自上而下加载的。
head
标签中遇到script
标签时,如果引用了外部脚本就下载,否则就直接执行(此时控制权交给js
引擎(解释器)),执行完毕后再将控制权交给渲染引擎。(**head
**标签的加载)head
完毕后,开始解析body
中的代码,此时如果遇到script
,同样会将控制权交给js
引擎。(**body
**标签的加载)- 当
body
中的代码全部执行完毕,并且整个页面的css
样式加载完毕,css
会重新渲染整个页面的html
元素。(**css
**样式的加载)
所以,
script
标签写在标签靠后的位置较好,因为js
会操作html
元素,如果在body
之前写js
逻辑会造成找不到页面元素。
另:
- 图片或视频的加载是异步的,也就是说在加载
css
和dom
过程中,加入出现图片链接,浏览器会额外去下载这个图片,不会阻塞后面的资源解析,但是图片的加载却受css
样式的影响,图片加载完成之前,css
必须加载完成。 - 如果遇到
script
中有错误语法,会直接报错并忽略该script
块的执行,而跳到下一个script
块执行。 - 外部样式和外部脚本也是异步加载的,加载外部文件的时候,不会阻塞后面
dom
的解析,外部脚本执行没有async
、defer
的属性时,会被外部样式阻塞,也就是说要等到外部css
加载完才会执行外部脚本,添加async
或defer
就不会受到阻塞。
3. 浏览器渲染过程
浏览器解析内容主要分为三个部分:
HTML/XHTML/SVG
:解析这三种文件后,会生成DOM树(DOM Tree
)CSS
:解析样式表,生成CSS
规则树(CSS Rule Tree
)JavaScript
:解析脚本,通过DOM API
和CSS DOM API
操作DOM Tree
和CSS Rule Tree
,与用户进行交互。