JavaScript基础(一)
1. js在浏览器中的执行过程
我们首先要知道的是,浏览器内核包含以下两部分:
渲染引擎:解析HTML和CSS,渲染网页代码,页面生成以后的脚本操作和样式表操作会触发“重流”和“重绘”。
js引擎:又叫“js解释器”,读取js代码并处理运行,具体做了什么我们后面会说。
注:两个引擎各自的运行都是单线程的,同时浏览器中两个引擎也具有互斥性,在同一时刻只有一个引擎运行。
· 浏览器的渲染过程
简单概括,浏览器先读取并解析HTML,生成DOM树,在这一过程中,如果遇到外部资源,就加载并解析外部资源,包括CSS和JS等资源。但是外部资源又分为阻塞资源和非阻塞资源,在请求和解析非阻塞资源(CSS、图片)的时候,浏览器不会停止原本HTML的解析,但会组织页面渲染;而当遇到阻塞资源(js)的时候,便会阻塞页面渲染,暂停HTML的解析。
然后我们来看
· 浏览器的渲染过程浏览器请求和解析js资源
请求到js资源之后,浏览器本身不会执行js代码,而是通过js引擎来执行。js引擎对js语言逐句进行识别、解释、转换、执行,也因此js是一种脚本语言。不过这种逐句解释执行的方式速度相对较慢,如今大部分浏览器为了提高运行速度,都会对js进行一定程度的编译,生成类似于字节码的中间代码,然后运行在虚拟机上。
(可以理解为,js引擎并不是一个简单的解释器,还担任了一个虚拟机的角色)
2. js脚本调用策略
脚本的调用时机很重要,为什么这么说?
要知道,HTML是顺序执行的。如果我们使用DOM操作元素,就要特别注意javascript的调用顺序了,否则一不小心就会出现 Uncaught TypeError,提示找不到目标元素,因为在我们操作元素的时候它还没有被加载。
那么解决办法都有哪些呢?
如果是调用内部js,即js代码写在当前html文档中:
- 可以将js代码写在文档底部,body的结束标签之前,这样就可以在整个页面加载完成之后才加载js代码;
- js代码也可以写在头部,通过事件监听器,监听页面文档加载完成,举个例子:
<head>
<meta charset="utf-8">
<title>使用 JavaScript 的示例</title>
<script>
document.addEventListener("DOMContentLoaded", function() {
function createParagraph() {
let para = document.createElement('p');
para.textContent = '你点击了这个按钮!';
document.body.appendChild(para);
}
let button = document.getElementById('btn')
button.addEventListener('click', createParagraph)
});
</script>
</head>
<body>
<button id="btn">点我呀</button>
</body>
上边这段代码,如果不监听DOMContentLoaded事件,就会报错,原因前边已经说过了。
如果是调用外部js,正常情况下也是按照顺序加载,不过我们还可以通过JavaScript的async和defer关键字来控制调用顺序。
- async:异步调用外部js文件,遇到即下载,下载完即运行,不会阻塞页面渲染进程,但是运行顺序不确定。适用于多个外部js文件互相独立,且不依赖本页面其它任何脚本的情况。
- defer:应用该属性的js文件会自动等待页面解析完成之后按照顺序进行调用。如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。
3.js变量
变量又叫标识符,是值的一个符号名,或者说是一个容器。其本质就是在内存开辟一块空间来存储数据,变量本身不是数据。
·数据类型
最新的ECMAScript规定了七种基本数据类型和一种复杂数据类型。
基本数据类型: null,undefined,Number,String,Boolean,BigInt,Symbol
复杂数据类型:Object
·变量的声明
var:声明全局变量和局部变量
let:声明块作用域的局部变量
const:声明一个只读变量(常量)
·变量的作用域
全局变量: 在函数之外声明的变量,可被文档中任何其他代码访问
局部变量: 在函数内部声明的变量,只能在当前函数内部使用
·变量提升
什么是变量提升?
js中可以先使用变量再声明变量,这样并不会发生异常。但是,提升的只是变量的声明,不包括赋值,怎么理解呢?举个栗子~
var foo = 1;
function func() {
console.log(foo);
var foo = 2;
}
func();
以上这段代码的执行结果是 undefined,它就可以等同于:
var foo = 1;
function func() {
var foo;
console.log(foo);
foo = 2;
}
func();
注:let和const声明的变量也有变量提升,不过并不会给默认的undefined值,而是会抛出引用错误(ReferenceError)
·函数提升
对于函数来说,只有函数声明可以提升,函数表达式不可以提升,首先要分清函数声明和函数表达式。
// 函数声明
func();
function func() {
console.log(2); //输出2
}
// 函数表达式
func();
var func = function() {
console.log(2); //Uncaught TypeError: func is not a function
}
其实很容易理解,函数表达式中,后声明的函数只会提升声明部分,func返回值不是定义的function而是undefined。
2023-01-17 增加
console.log(a) //打印结果1:ƒ a() {console.log('a')}
function a() {
console.log('a')
}
var a = 1
console.log(a) //打印结果2:ƒ a() {console.log('b')}
function a() {
console.log('a')
}
var a = 1
function a() {
console.log('b')
}
分析上面两段变量提升的代码,比较两个打印结果,发现了什么?
结合js的解析流程深入了解一下这个变量提升的过程:
- 遇到函数声明 function a ,放入 VO(variable object,全局执行上下文中的变量对象) ,并且让其初始值为 undefined。
- 遇到 var a ,准备放入 VO ,结果发现 VO 已经存在同名变量,跳过。
- 再次遇到 function a,准备放入 VO ,结果发现 VO 已经存在同名变量,覆盖。
由此可见,当存在同名的var声明和function声明的变量时,存在一个优先级,函数声明优先级要高一些。