JavaScript学习
前言
个人为了更好的学习JavaScript,在网上收集的各种关于JavaScript的基础知识:
LeetCode :https://leetcode-cn.com
菜鸟教程:https://www.runoob.com/js/js-strings.html
one: https://laixiazheteng.com/one
掘金:https://juejin.cn/
https://github.com/qq449245884/xiaozhi
以上网站是个人觉得学习最好用的网站
提示:以下是本篇文章正文内容,下面代码片段是为了自己学习
一、JavaScript是什么?
JavaScript是一种轻量级(弱类型)语言,一种解释性语言,特别要注意变量名的大小写
二、学习JavaScript的输出方式
- console.log()
使用console.log() 写入到浏览器的控制台
代码如下(示例):
console.log("Hello,World!")
- window.alert()
window.aler() 弹出警告框
代码如下(示例):
window.alert("我会弹出警告框")
console.log()相比window.alert()的优点就是console.log()更适合调试代码,能够看到数据的结构化,
alert()弹出一个对象就是(object,object)
三 、JavaScript的变量
- 3.1 语法
JavaScript程序是用unicode字符集编写的- 区分大小写 JavaScript中的一切(变量名,函数和操作符)都区分大小写,例如: 变量名test和变量名Test代表的是两个不同的变量
- 标识符 标识符是指变量名,属性名 和 函数名 或 函数的参数
标识符的规则:按照大驼峰和小驼峰的规则进行命名,不能含有关键字和保留字.例如:this true false不能当作标识符
myName || MyName
//变量名test和Test是两个不同的变量
let test = 1
let Test = 1
- 3.2变量
- 变量:是用于存储数据的容器,使用var定义的变量将成为定义该变量作用域中的局部变量,只在在定义的函数块中生效
function test(){
var name = "局部作用域中的变量name"
console.log(name) //局部作用域中的变量name
}
console.log(name) //报错 name在test函数外是无法找到的
- 变量提升: 函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部,
JS引擎会在正式执行代码之前进行一次”预编译“,预编译简单理解就是在内存中开辟一些空间,存放一些变量和函数 步骤如下:
开始预编译
查找函数声明,作为GO属性,值赋予函数体(函数声明优先)
查找变量声明,作为GO属性,值赋予undefined
x = 5 //执行之前 变量提升作为window的属性 值设置为5 window.x = 5
var x
console.log(x) //x = 5
console.log(a) // undefined 这里的a作为 window.a 存在 没有值就设置值为undefined
var a = 5
无论是变量还是函数,都必须先声明后使用。PS:在开发中应该使用let来约束变量提升
- 复制变量值:
- 从一个变量向另一个变量 复制基本类型值(number,string,boolean,null,undefined,symbol)会在变量对象上创建一个新值,然后将新值复制到为新变量分配的位置上
let a = 1
let b = a
a++
console.log(b , a ) // b :1 a :2
//a 保存了值 1 将a用来初始化b b也就保存了值1 这里b中的1 和 a中的1 完全没有关系
// a++ 变量a值为2
- 当一个变量向另一个变量复制引用类型时,同样也是将变量对象中的值复制到新的变量分配的空间中不同的时这里的值是一个指针,也就是两个变量同时指向一个存储在堆中的对象
let obj1 = {a:10,b:1,c:0} //声明obj1
let obj2 = obj1 //将obj1 复制到 obj2
obj2.a = 8 //obj2 在这里其实就是一个指针指向了obj1的属性 a b c
console.log(obj1.a) // 8
四、数据类型
- 值类型(基本类型):字符串(String) 数字(Number)布尔(Boolean),两空(Null Undefined) 唯一值(Symbol)
- 引用数据类型: 对象(Object) 数组(Array)函数(Function)
注:symbol是EcomScript 6(ES6)引入的一种新类型,表示独一无二的值
- 字符串(String)
- 1.字符串是存储字符(比如“str” || ’str‘)的变量
let str = '123'
let str = "123"
let str = String(2) //"2"
- Number(数字)
- js只有一种数字类型,数字可以带小数点,也可以不带 Number类型是64位双精度浮点型,通过符号位(1)+ 指数位(11)+ 小数部分有效位(52组成
- js 是如何存储数字:
- 第一位 是 符号位 0表示正数 1表示负数
- 接下来11位 是 指数位 也就是2的多少次幂
- 最后52位表示尾数位 存储的是数值精确度
0.1
转二进制
0.0001100110011001100110011001100110011001100110011001100110011
保留52位尾数 (从第一个1向后数52位)
0.00011001100110011001100110011001100110011001100110011010
0.2
转二进制
0.001100110011001100110011001100110011001100110011001100110011
保留52位尾数
0.0011001100110011001100110011001100110011001100110011010
进行相加 二进制相加 0 + 0 = 0 1+0 = 1 1+1 = 10 这里的1向前 0保留
0.00011001100110011001100110011001100110011001100110011010
0.0011001100110011001100110011001100110011001100110011010
----------------------------------------------------------
0.01001100110011001100110011001100110011001100110011001110
相加后的结果保留52位尾数
0.010011001100110011001100110011001100110011001100110100
转十进制
0.30000000000000004
0.1 => 0.1*2 = 0.2 取整 0
0.2 => 0.2*2 = 0.4 取整 0
0.4 => 0.4*2 = 0.8 取整 0
0.8 => 0.8*2 = 1.6 取整 1
0.6 => 0.6*2 = 1.2 取整 1
0.2 => 0.2*2 = 0.4 取整 0
0.4 => 0.4*2 = 0.8 取整 0
0.8 => 0.8*2 = 1.6 取整 1
0.6 => 0.6*2 = 1.2 取整 1
...发生循环
得到结果 0.1(十进制)= 00011001100110011001100110011... (0011)循环(二进制)
小数部分采用 *2取整法 整数采用 /2 取余方法
绝不要在数字前面写 0 javascript 会将数值常量解释为八进制
-
boolean(布尔值)
*boolean类型只有2个值 true 和 false
如果Boolean对象无初始值或者值为:
: false
: 0
: null
: undefined
: " "
: NaN
那么该Boolean对象的初始值都为false 其他值都为true
- Null 和 Undefined(两空)
- undefined :未定义的值 语义 变量最原始的状态,而非人为的操作结果
- 声明了一个变量,但是没有赋值
- 访问对象上不存在的属性
- 函数定义了参数却没有传入参数
- 使用void 对表达式求值
//声明变量 没有初始化
let a ;
console.log(a) //undefined`
//访问对象上不存在的属性
let obj = {"a":1,"b":2}
console.log(obj.c || obj["c"]) //undefined
//函数定义参数没有传入参数
let a = function(val)=>{console.log(val)} //undefined
a() // 没有传入参数
//void对表达式求值
void false //undefined
void [] //undefined
void 1 + 1 //NaN 这里为NaN 是因为void 1 = undefined
ECMScript 明确规范 void操作符对任何表达式求值都为undefined
-
Null :空值 语义:希望表示对象是人为的赋值一个空值对象,在内存里表示 栈中的对象没有指向堆中的内存对象
- 一般以下两种情况我们会将变量赋值为null
- 如果定义的变量将来用于保存对象,那么将该对象初始化为null
- 当一个全局变量不在需要使用的时候,使用null将该变量进行解除引用
undefined 表示一个变量自然的原始的状态值
null表示人为设置的空对象,而不是原始状态
- symbol(唯一值)
- 作为ES6新增的原始类型,创建Symbol 就像其他原始类型一样 以全局调用的方式创建
let a = Symbol("1");
let c = Symbol(1)
//不要使用new
- 使用symbol作为唯一值
在设置一些状态时候我们一般会用数字或者字符串表示,但作为Symbol是最好的选择,因为每次创建的symbol都是唯一的
let state = {
OPEN:Symbol('以打开'),
CLOSE:Symbol('以关闭'),
}
console.log(state.OPEN) //symbol('以打开')
- 使用symbol作为对象属性
使用symbol作为属性名称(对象的键)
let a = Symbol(open)
let b = {
a:open
}
关于symbol的详细使用symbol的终极使用教程
- 对象(object)
- 对象由花括号分隔,在括号内部,对象的属性以键值对的形式定义,属性由逗号分割
- 访问对象的方法有两种
- 点访问
- 键访问
let test = {
a:1,
b:2,
c:3
}
console.log(test.a) //点访问
console.log(test[a]) //键访问
五、运算符
1. 算数运算符
*算数运算符用于执行两个变量或值的运算
1+1 // 2 加法 如果其中1个变量为string类型 ‘+’ 就属于连接符号
1-1 // 0 减法
1*1 // 1 乘法
1/1 // 1 除法
3%1 // 0 取模(余数)
++1 // 自增 ++在前先自增返回结果 ++在数字后面先返回结果在自增
--1 // 自减
2. 赋值运算符
赋值运算符用于给变量赋值
let a = 1 // 将右边的操作数1赋值给变量a
a += 1 // 2 将右边的操作数和变量a进行加法运算赋值给变量a
a -= 1 // 1 将右边的操作数和变量a进行减法运算并赋值
a*= 1 // 1 等同于 a * 1 = a
a/=1 // 1 等同于 a / 1 = a
a%=1 // 0 等同于 a % 1 = a 任何数%1等于0
用字符串的+运算符等同于连接符(字符串连接)
3. 比较运算符
比较运算符用于逻辑语句中使用,测定变量类型或值是否相等
let a = 1, b = '1'
a==b // true 相等运算符会先进行类型转换在进行值比较
a=== b //false 先进行类型比较是否相同,在进行值比较
a != b //true 类型转换后进行值比较
a!==b //true 类型比较之后再进行值比较
a > b //false
a < b //false
a >= b //false
a <= b //false
在条件语句中使用比较运算符,对值和类型比较之后的结果来执行语句 注意:对象类型的比较不是值比较,而是引用类型是否指向同一个内存
let a = {b:1}, b = a ; a === b
4. 逻辑运算符
用于测定变量与值之间的逻辑
- && 且运算符 依次对运算数进行判断 如果为true继续进行比较 为false就返回上一个运算数的值
if( 1 && 2 && 3){dosomething()} //123都为真
if(0) dosomething() // 0为false 不会进行dosomething()
- || 或运算 如果第一个运算子为true 就返回第一个运算子的值 如果为false 就返回第二个运算子的值
if(1 || 0 || false){console.log(1)} // 1
true || x // true
0 || 1 // 1 0 是 false
let a = 1 > 2 | 3 // a = 3
let a = undefined || 0 // a = 0
通过短路规则 一般或运算适用于设置一个变量为默认值
- !取反运算 将真变为假 假变为真
!1 // false
!0 // true
!2 // false
!!0 = Boolean(0) // false
根据Boolean类型除了 0 undefined … 6个值可以同过!取反运算转为true 其他转为false
将一个数进行2次取反 等于 Boolean() 方法
5. 条件运算符
也称为三元运算符,
语法: 条件表达式 ? 语句1 : 语句2
先对条件表达式求值
值为 true 执行语句1 返回执行结果
值为 false 执行语句2 返回执行结果
值为非boolean值会将值强制转换为boolean类型
let a = 1, b = 2;
a > b ? true : false //false 上面和下面的写义是一样的
-------------------------
if(a > b){
return true
}else{
return false
}
6. 位运算符
位运算符用于32位数字上,任何数字操作都将转换为32位,结果会转换为数字
1.与运算符(&)
let a = 2, b = 3
a & b // 2
//a 转换为二进制 10进制转二进制是除2取余 等于10
//b 转换为二进制 11
// 1 0
// & 1 1
// --------
// 1 0
//将二进制10 转换为 十进制 用的是 0 * 2^0 + 1* 2^1 = 2
快速算法:有0返回0
- 或运算符(|)
let a = 2, b = 3
a | b // 3
//a 转换为二进制 10进制转二进制是除2取余 等于10
//b 转换为二进制 11
// 1 0
// & 1 1
// --------
// 1 1
//将二进制10 转换为 十进制 用的是 1 * 2^0 + 1* 2^1 = 3
快速算法:有1返回1
- 否运算符(~)
对一个二进制位取反
let a = 2, b = 3
~a // -1
快速算法:-1减去取反数
- 异或运算符(^)
let a = 2, b = 3
a ^= b // 1
a ^= 0 // 取整最快的方法
a ^= b, b^= a, a^= b // 最快的值交换方法
快速算法 2个数转换为二进制 相同返回0 不同返回1
- 左移运算符(<<)
将一个数得二进制进行向左移动指定位数
let a = 2
a << 3 // 16
快速算法: m * (2 ^ n) m = a n = 3
- 右移运算符(>>)
将一个数二进制向右移动指定位数
let a = 2
a >> 3 // 0
快速算法:Math.floor(m / (2 ^ n)) m = a n = 3
7. 其他运算符
- 指数级运算符(**)
求幂运算符 等同于Math.pow()
let a = 2, b= 3;
a ** b // 8 2的3次方
a ** b ** b // 134217728
注意:求幂运算符是从右向左进行依次计算 2 ** 3 ** 3 = 2 ** 27 = 134217728
- 空值合并运算符(??)
空值合并操作符是一个逻辑运算符
语句1 ?? 语句2
当语句1为null 或 undefined 返回语句2
当语句1为其他值 返回语句1
let a = null
let b = a ?? 1 // b = 1
let b = 0 ?? a // b = 0
空值合并操作符不能和其他逻辑运算符一起使用,因为优先级未定义
- 可选链操作符(?.)
适用于在对象属性是否存在的时候,使用?. 不会报错,而是返回undefined或null
语法
obj?.prop // obj.first?.second 在访问second之前会隐式的访问obj.first是否存在 不存在就返回null
obj?.[expr] //可选链和表达式 let nestedProp = obj?.[‘prop’ + ‘Name’];
arr?.[index] // 可选链访问数组元素 let arr = arr?.[45] arr不存在下标45的元素也不会报错
func?.(args) // 可选链和函数 let result = someInterface.customMethod?.();
六、函数
函数是js中的组件之一,使用一个函数你必须定义在你希望调用的作用域内
简单概括 :一次封装,四处使用
1.函数定义
- 函数声明方式
- 函数表达式
- 使用Function构造函数
函数声明方式
语法:
function 函数名(参数1,参数2,...){
//执行语句
}
//例如
function sum (num1,num2){
return num1 + num2;
}
//调用
sum(1,1) //2
函数表达式
语法:
var 变量名 = function(参数1,参数2,...){
// 要执行语句
}
//例如
var square = function(x) {return x * x)
//调用
square(2) // 4
函数表达式也可以使用函数名,并且可以用在函数内部代表函数本身
const factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};
console.log(factorial(3)); //16
使用Function构造函数
Function构造函数可以接受任意数量的参数,最后一个参数为参数体
new Function("参数1","参数2",...,'参数n','函数体',);
例:
var sum = new Function('num1','num2', 'return num1 + num2');
sum(1,2) // 3
三种定义方式区别
- 作用域上来区分 函数声明式和函数表达式是局部变量,而
Function()
构造函数是全局变量
var name = '我是全局变量'
//函数声明式
function a(){
var name = '我是a的局部变量'
return name
}
console.log(a()) //我是a的局部变量
//函数表达式
var b = function(){
var name = '我是b的局部变量'
return name
}
console.log(b()) //我是b的局部变量
// Function 函数构造
function c(){
var name = '我是c的局部变量'
return new Function('return name')
}
console.log(c()()) // 我是全局变量
- 执行效率上 Function()构造函数要低于其他2种方式,因为每次执行一次都要重新编译,并生成新的对象,特别是循环体中;
console.time('使用Function构造函数方式所用时间')
for(let i = 0;i< 10000;i++){
var f = new Function('num1','num2','return num1 + num2')
f(i,i+1)
}
console.timeEnd('使用Function构造函数方式所用时间') //使用Function构造函数方式所用时间: 7.8310546875 ms
console.time('使用表达式构造函数方式所用时间')
for(let i = 0;i< 10000;i++){
var ff = function(num1,num2){return num1 + num2}
ff(i,i+1)
}
console.timeEnd('使用表达式构造函数方式所用时间') // 使用表达式构造函数方式所用时间: 0.443115234375 ms
- 从编译顺序上来说 函数声明存在变量提升,其他两中不存在
console.log(typeof f) //function
console.log(typeof g) //undefined
console.log(typeof h) //undefined
function f(){
return true
}
var g = function(){
return true
}
console.log(typeof g) //function
var h = new Function('return true')
console.log(typeof h) //function
2.函数的调用
JavaScript中函数的调用有4种方式:函数调用,方法调用,构造器调用,间接调用
函数调用
普通的函数调用,函数的返回值就是调用表达式的值,
function test(x,y){
return x + y
}
let a = test(1,2) //3 x+y 表达式的返回值 = test() 函数的返回值
这里的函数是一个全局对象,所以在非严格模式下this
指向全局对象
方法调用
将函数保存为一个对象的属性时称为方法,这个时候在调用对象的方法,this
是绑定在对象身上的
function print(){
console.log(this.value)
}
var value = 1
let obj = {value:2}
obj.fun = print
print() //1 这里的this指向全局对象 value = 1
obj.fun() //2 这里的this指向对象obj身上 obj的value属性 = 2
构造函数调用
函数或者方法之前带有new
关键字称为构造函数调用
function fn(){
this.a = 1
}
let fn1 = new fn() //函数的原型也是object相当于 fn1继承了fn的所有属性
let fn1 = new fn //这样也是可以的,当构造函数没有参数可以不用括号
fn1.a //1
函数的调用上下文创建对象
function Book(bookName){
this.bookName = bookName
}
var bookName = '老版'
var book = new Book('新版')
console.log(bookName) //老版
console.log(book.bookName) //新版
Book('新版1')
console.log(bookName) //新版1
console.log(book.bookName) //新版
第一次调用Book
用的是构造函数调用
,this
指向的是对象Book
,全局变量bookName
值不变,对象Book
的属性bookName
为新版
第二次调用时普通调用方式
,this
指向的是全局变量
,所以对象属性bookName
值不变,全局bookName
值为新版1
间接调用
函数也是对象,函数对象也可以包含方法 , call()
和 apply()
可以间接的调用函数,任何函数可以作为任何对象的方法来调用,call(对象,列表参数) , apply(对象,数组参数)
var obj = {}
function sum(x,y){
return x + y
}
let a = sum.call(obj,1,2) //3
let b = sum.apply(obj,[1,2]) //3
显式调用所需this值
3.词法(静态)作用域和动态作用域
JavaScript 采用的是静态作用域
,静态作用域就是在函数中的变量既不是形参也不是函数内部变量时,会去函数所定义的环境中找寻局部变量
动态作用域
: 函数中的变量既不是形参也不是局部变量时,会去函数调用时的环境中查找
let a = '全局'
function b(){
console.log(a) // 全局
}
function c(){
let a = 'c中的局部变量a'
b()
}
c() b中的变量a根据词法作用域 会找函数b所定义的位置 找变量a 也就是let a = '全局'
当然动态作用域来说 变量a的函数b所调用的环境是c 那么 a = 'c中的局部变量a'
思考
let a = 1
function b(){
console.log(a)
}
- 函数b里的变量a是否是外部变量a?
- 函数b里的变量a的值 是不是外部变量a的值
No.1 根据静态作用域 函数b里的a确定是外部变量a
No.2 函数b里的变量a的值不一定是外部变量a的值
let a = 1
function b(){
console.log(a)
}
a = 2
b()
这里a的值就是2
静态作用域并不能确定变量的值,只能确定变量的位置·
4. 调用栈(call stack)
什么是执行上下文
执行上下文其实就是当前JavaScript在编译和执行环境中的一个抽象概念,一个语言环境
JavaScript在执行代码之前会预编译,先初始化全局对象
创建一个全局对象,这个对象的属性任何地方都访问,this指向的是window,会将js常用的一些属性和方法存入全局对象(Math,Date,…)接下来会创建一个执行环境栈
的同时创建一个全局执行环境
并将其存入执行环境栈,这里的全局执行环境就是全局执行上下文
执行上下文的主要类型有两种:
- 全局执行上下文:不在任何函数内的代码都位于全局执行上下文中,全局对象在浏览器环境中
this
指向window
,一个程序只能存在一个执行上下文 - 函数执行上下文:每次调用函数时都会创建一个
函数执行环境
,函数的执行环境可以存在多个,这些函数执行环境是按照执行环境栈的LIFO(后入先出)的方式执行一系列步骤
调用栈(执行环境栈)
执行环境栈具有LIFO (last in first out)
后进先出的结构,用于存储代码在执行期间创建的所有执行上下文
JavaScript在首次读取脚本时,会先创建一个执行环境栈的同时创建一个全局执行上下文,将全局执行上下文push
到执行环境栈中,每次调用函数的时候会创建一个函数执行上下文,并push
到执行环境栈的栈顶,当函数执行上下文运行完成之后又会被执行环境栈pop
出来.
举例一段例子:
let myOtherVal = 5
function a(){
console.log('myVal',myVal)
b()
}
function b(){
console.log('myOtherVal',myOtherVal)
c()
}
function c(){
console.log('Hello,world!')
}
a()
var myVal = 10
myVal undefined
myOtherVal 5
Hello,world!
这就是以上代码打印的结果
变量函数声明创建时
第一步创建一个全局变量,将函数名和变量存入全局变量,下面是伪代码:
var GlobalStack =[] //执行环境栈
var GlobalObject = {} //全局对象
GlobalObject.myOtherVal = 5
GlobalObject.myVal = Undefined; // 这里的myVal存在变量提升系统只是定义还未复制
GlobalObject.a = a()
GlobalObject.b = b()
GlobalObject.c = c()
GlobalStack.push(GlobalObject) //执行环境栈先将全局对象push进去
//调用a() 创建一个函数执行上下文
GlobalStack.push(GlobalObject.a)
GlobalObject.a
//执行a函数里的方法console.log('myVal',myVal) 根据词法作用域a函数定义环境中找到myVal = undefined
//调用b函数
//创建b执行上下文
GlobalStack.push(GlobalObject.b)
GlobalObject.b
//执行b函数里的console.log('myOtherVal',myOtherVal) 根据词法作用域a函数定义环境中找到myOtherVal= 5
// 调用c函数
GlobalStack.push(GlobalObject.c )
GlobalObject.c
//执行函数c里的console.log('Hello,world!')
//c函数生命周期执行完毕之后pop(c), 依次回收其他函数执行环境
GlobalStack.pop(GlobalObject.c )
GlobalStack.pop(GlobalObject.b)
GlobalStack.pop(GlobalObject.a )
//给全局环境中的myVal 复制
myVal = 10
//全局执行环境生命周期执行完毕
GlobalStack.pop(GlobalObject)
七、JS引擎的底层工作原理
1.JavaScript引擎和全局内存
js是编译语言的同时也是解释语言; 先看一段代码:
let num = 5
function pow(num){
return num * num
}
如果问你如何在浏览器中处理上述代码?你会怎么说!
首先读取这段代码是JavaScript引擎,会先将代码中的引用放入全局内存.
全局内存(也称为堆) js引擎保存变量和函数声明的地方,回到上面示例:当js引擎读取上面代码时会在全局内存中放入2个变量
即使示例中只有变量和函数,但js在更大的环境中预定义了更多的函数和变量(Date, Math…)
全局内存 this
指向全局对象window
上例中js代码没有任何执行操作,但是我们执行函数会怎么样呢?
var num = 5;
function pow(num){
return num * num
}
pow(num)
当函数pow
被调用时,JavaScript会为全局执行上下文
和调用堆栈
腾出空间
我们现在执行了一个pow
函数,js引擎中有一个基本组件 调用堆栈
同时,JavaScript引擎还分配了一个全局执行上下文
如果我函数内嵌套一个变量或者函数,js引擎也会像下面的示例中一样创建一个函数执行上下文(本地执行上下文)
var num = 5;
function pow(num){
let fixed = 10
return num * num
}
pow(num)
在pow函数中嵌套一个fixed变量,pow函数会创建一个函数执行上下文将变量fixed存入pow函数
函数执行环境可以有多个,但全局执行环境有且只有一个
2.JavaScript是单线程
JavaScript是单线程是因为只有一个调用堆栈来处理我们函数,也是顶部函数在执行,底部函数就不能离开调用堆栈;
如果JavaScript涉及异步代码怎么办?
就算JavaScript每次都只执行一个函数,也有一种方法可以让外部(如浏览器)执行稍慢的函数,下面将讨论异步函数
当js引擎浏览代码时,会逐行读取并执行以下步骤:
- 将变量和函数放入全局内存(堆)中
- 将函数放入调用堆栈中
- 创建一个全局执行上下文,在其中执行函数
- 如果内部有多个变量和函数,创建多个本地执行上下文
以上就是JavaScript同步机制所有步骤
3. 异步JavaScript,回调队列和事件循环
了解了同步js代码在浏览器中运行方式之后,关于js异步函数是怎么运行的 ?
当我们运行一个异步函数时,浏览器接受并运行它,如下代码:
setTimeout(callback,1000);
function callback(){
console.log('setTimeOut:我是浏览器组件')
}
js中并没有setTimeout
这个内置函数,setTimeout
是浏览器API的免费工具,setTimeout函数由浏览器运行,只存在于函数堆栈中一会,并立即删除
如下示例:
var num = 2
function pow(num){
return num * num
}
setTimeout(callback,1000);
function callback(){
console.log('setTimeOut:我是浏览器组件')
}
setTimeout在浏览器中运行10秒后,计算器除法,运行回调函数,但首先必须通过回调队列
,回调队列是一个有序的数据结构,
每个异步函数在放入函数堆栈时都会经过回调队列 是依靠于事件循环(Event Loop)
事件循环只有一个任务:检查调用堆栈是否为空,如果回调队列中有回调函数,且函数堆栈时空,将异步函数就放入其中
当pow()函数执行完毕时 函数堆栈是空的 事件循环就会将回调队列的callback()放入函数调用堆栈中,执行callback