var age = 18
console.log(age)
var obj = {name: 'Tom'}
console.log(obj.name)
function fn () {
var obj = {name: 'Tom'}
}
var a = 3
var b = a + 2
5、关于赋值与内存的问题
问题:var a = xxx,a 内存中到底保存的是什么?
xxx是基本数据,保存的就是这个数据
xxx是对象,保存的是对象的地址值
xxx是一个变量,保存的xxx的内存内容(可能是基本数据,也可能是地址值)
var a = 3
a = function () {
}
var b = 'abc'
a = b
b = {}
a = b
6、关于引用变量赋值问题
2 个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据
2 个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一引用变量依然指向前一个对象
var obj1 = {name: 'Tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age) // 12
function fn (obj) {
obj.name = 'A'
}
fn(obj1)
console.log(obj2.name) //A
var a = {age: 12}
var b = a
a = {name: 'BOB', age: 13}
b.age = 14
console.log(b.age, a.name, a.age) // 14 Bob 13
function fn2 (obj) {
obj = {age: 15}
}
fn2(a)
console.log(a.age) // 13
7、关于数据传递问题
问题:在 js 调用函数时传递变量参数时,是值传递还是引用传递
理解1:都是值(基本/地址值)传递
理解2:可能是值传递,也可能是引用传递(地址值)
var a = 3
function fn (a) {
a = a +1
}
fn(a)
console.log(a) // 3
function fn2 (obj) {
console.log(obj.name)
}
var obj = {name: 'Tom'}
fn2(obj) // Tom
8、JS 引擎如何管理内存
问题:JS 引擎如何管理内存?
内存生命周期
分配小内存空间,得到它的使用权
存储数据,可以反复进行操作
释放小内存空间
释放内存
局部变量:函数执行完自动释放
对象:成为垃圾对象==>垃圾回收器回收
var a = 3
var obj = {}
obj = undefined
function fn () {
var b = {}
}
fn() // b是自动释放, b所指向的对象是在后面的某个时刻由垃圾回收器回收
3、对象
1、什么是对象?
多个数据的封装体
用来保存多个数据的容器
一个对象代表现实中的一个事物
2、为什么要用对象?
统一管理多个数据
3、对象的组成
属性:属性名(字符串)和属性值(任意)组成
方法:一种特别的属性(属性值是函数)
4、如何访问对象内部数据?
.属性名:编码简单,有时不能用
['属性名']:编码麻烦,能通用
var p = {
name: 'Tom',
age: 12,
setName: function (name) {
this.name = name
},
setAge: function (age) {
this.age = age
}
}
p.setName('Bob')
p['setAge'](23)
console.log(p.name, p['age'])
问题:什么时候必须使用 ['属性名'] 的方式?
属性名包含特殊字符: -、空格
属性名不确定(是变量)
var p = {}
//1. 给p对象添加一个属性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])
//2. 属性名不确定
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value
console.log(p[propName])
4、函数
1、什么是函数?
实现特定功能的 n 条语句的封装体
只有函数是可以执行的,其它类型的数据不能执行
2、为什么要用函数?
提高代码复用
便于阅读交流
3、如何定义函数?
函数声明
表达式
4、如何调用(执行)函数?
test():直接调用
obj.test():通过对象调用
new test():new 调用
test.call(obj)/test.apply(obj):临时让 test 成为 obj 的方法进行调用
全称: Immediately-Invoked Function Expression(立即执行函数)
2、作用
隐藏实现
不会污染外部(全局)命名空间
用它来编码 js 模块
(function () { //匿名函数自调用
var a = 3
console.log(a + 3)
})()
var a = 4
console.log(a)
;(function () {
var a = 1
function test () {
console.log(++a)
}
window.$ = function () { // 向外暴露一个全局函数
return {
test: test
}
}
})()
$().test() // 1.
7、函数中的 this
1、this 是什么?
任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是 window
所有函数内部都有一个变量 this
它的值是调用函数的当前对象
2、如何确定 this 的值?
函数调用的时候确定
test():window
p.test():p
new test():新创建的对象
p.call(obj):obj
箭头函数没有自己的 this,箭头函数中所谓的 this,其实就是外层代码块的 this
function Person(color) {
console.log(this)
this.color = color;
this.getColor = function () {
console.log(this)
return this.color;
};
this.setColor = function (color) {
console.log(this)
this.color = color;
};
}
Person("red"); //this是谁? window
var p = new Person("yello"); //this是谁? p
p.getColor(); //this是谁? p
var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj
var test = p.setColor;
test(); //this是谁? window
function fun1() {
function fun2() {
console.log(this);
}
fun2(); //this是谁? window
}
fun1();
二、函数高级
超级重点,两大神兽:原型和闭包
1、原型和原型链
(1) 原型
函数的 prototype 属性
每个函数都有一个 prototype 属性,它默认指向一个 Object 空对象(即称为原型对象)
原型对象中有一个属性 constructor,它指向函数对象
给原型对象添加属性(一般都是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {//alt + shift +r(重命名rename)
}
console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性)
// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)
//给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()
function Fn() {
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a, fn1)
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a, fn2)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1)
var p2 = new Person('Jack', 12)
p2.setName('Cat')
console.log(p2)
console.log(p1.__proto__===p2.__proto__) // true
(4) 探索 instanceof
instanceof 是如何判断的?
表达式:A instanceof B
如果 B 函数的显式原型对象在 A 对象的原型链上,返回 true,否则返回 false
Function 是通过 new 自己产生的实例
案例1
function Foo() { }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true
/*
测试题1
*/
function A () {
}
A.prototype.n = 1
var b = new A()
A.prototype = {
n: 2,
m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m) // 1 undefined 2 3
/*
测试题2
*/
function F (){}
Object.prototype.a = function(){
console.log('a()')
}
Function.prototype.b = function(){
console.log('b()')
}
var f = new F()
f.a() // a()
// f.b() // Uncaught TypeError: f.b is not a function
F.a() // a()
F.b() // b()
console.log(f) // F {}
console.log(Object.prototype) // {a: ƒ, constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, …}
console.log(Function.prototype) // ƒ () { [native code] }
2、执行上下文与执行上下文栈
(1) 变量提升与函数提升
变量声明提升
通过 var 定义(声明)的变量,在定义语句之前就可以访问到
值:undefined
函数声明提升
通过 function 声明的函数,在之前就可以直接调用
值:函数定义(对象)
问题:变量提升和函数提升是如何产生的?
在 js 中 js 引擎会优先解析 var 变量和 function 定义!在预解析完成后从上到下逐步进行!
解析 var 变量时,会把值存储在“执行环境”中,而不会去赋值,值是存储作用!例如:alert(a); var a = 2; 这时会输出 undefined,意思是没有被初始化没有被赋值!这并不是没有被定义、错误了的意思!
在解析 function 时会把函数整体定义,这也就解释了为什么在 function 定义函数时为什么可以先调用后声明了!其实表面上看是先调用了,其实在内部机制中第一步实行的是把以 function 方式定义的函数先声明了(预处理)
/*
面试题:输出 undefined
*/
var a = 3
function fn () {
console.log(a)
var a = 4
}
fn() // undefined
console.log(b) //undefined 变量提升
fn2() //可调用 函数提升 fn2()
// fn3() //不能 变量提升 Uncaught TypeError: fn3 is not a function
var b = 3
function fn2() {
console.log('fn2()')
}
var fn3 = function () {
console.log('fn3()')
}
var c = 1
function c(c) {
console.log(c)
var c = 3
}
console.log(c) // 1
c(2) // 报错 Uncaught TypeError: c is not a function
// 再看一个
// 声明阶段
function x(){ //函数声明
//console.log(5)此句会被下句代码覆盖
console.log(3)
}
var x;//变量声明,因为x已经声明过了,此处不进行声明(忽略)
//执行阶段
console.log(x) // ƒ x(){//函数声明//console.log(5);此句会被下句代码覆盖console.log(3);}
console.log(x()) // 3
x=1
x=100 //x的值被覆盖
console.log(x) // 100
console.log(x()) // Uncaught TypeError: x is not a function
3、作用域与作用域链
(1) 作用域
理解:
就是一块“地盘”,一个代码段所在的区域
它是静态的(相对于上下文对象),在编写代码时就确定了
分类:
全局作用域
函数作用域
没有块作用域(ES 6 有了)
/* //没块作用域
if(true) {
var c = 3
}
console.log(c) // 3 有块作用域则报错*/
作用:
隔离变量,不同作用域下同名变量不会有冲突
例如把如下代码分割
var a = 10,
b = 20
function fn(x) {
var a = 100,
c = 300;
console.log('fn()', a, b, c, x)
function bar(x) {
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x)
}
bar(100) // bar() 1000 20 300 400 100
bar(200) // bar() 1000 20 300 400 200
}
fn(10) // fn() 100 20 300 10
(2) 作用域与执行上下文
区别 1:
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
全局执行上下文环境是在全局作用域确定之后,js 代码马上执行之前创建
函数执行上下文是在调用函数时,函数体代码执行之前创建
区别 2:
作用域是静态的,只要函数定义好了就一直存在,且不会再变化
执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
联系:
执行上下文(对象)是从属于所在的作用域
全局上下文环境==>全局作用域
函数上下文环境==>对应的函数使用域
还是上面那串代码
(3) 作用域链
理解:
多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
查找变量时就是沿着作用域链来查找的
查找一个变量的查找规则:
在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入 2
在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入 3
再次执行 2 的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
var a = 1
function fn1() {
var b = 2
function fn2() {
var c = 3
console.log(c) // 3
console.log(b) // 2
console.log(a) // 1
console.log(d) // Uncaught ReferenceError: d is not defined
}
fn2()
}
fn1()
(4) 面试题
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();
}
show(fn); // 10
var fn = function () {
console.log(fn)
}
fn() // f(){console.log(fn)}
var obj = {
fn2: function () {
console.log(fn2)
//console.log(this.fn2) // ƒ () {// console.log(fn2) console.log(this.fn2)}
}
}
obj.fn2() // Uncaught ReferenceError: fn2 is not defined
4、闭包
(1) 引入实例
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//遍历加监听
/*// 无论点哪个都是 第4个 ——点击的时候循环已经执行完了
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function () {
alert('第'+(i+1)+'个')
}
}*/
/*
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
//将btn所对应的下标保存在btn上,利用这中方式可以实现
btn.index = i
btn.onclick = function () {
alert('第'+(this.index+1)+'个')
}
}*/
//利用闭包
for (var i = 0,length=btns.length; i < length; i++) {
(function (j) {
var btn = btns[j]
btn.onclick = function () {
alert('第'+(j+1)+'个')
}
})(i)
}
</script>
(2) 理解闭包
如何产生闭包?
当一个**嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)**时,就产生了闭包
闭包到底是什么?
使用 Chrome 调试查看
包含被引用变量(函数)的对象
注意:闭包存在于嵌套的内部函数中
产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)
function fn1 () {
var a = 2
var b = 'abc'
function fn2 () { // 声明函数形式定义函数,会产生闭包
console.log(a) // 使用了外部函数的变量
}
// fn2()
}
fn1()
function fun1() {
var a = 3
var fun2 = function () { // 声明变量形式定义函数,不会产生闭包
console.log(a)
}
}
fun1()
(3) 常见的闭包
将函数作为另一个函数的返回值
将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
/* f调用的是内部函数,fn1外部函数执行了一次,产生1个闭包 */
// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('test', 2000)
(4) 闭包的作用
使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
让函数外部可以操作(读写)到函数内部的数据(变量/函数)
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
// return a
}
function fn3() {
a--
console.log(a)
}
return fn3
}
var f = fn1()
f() // 1
f() // 0
(5) 闭包的生命周期
产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
死亡:在嵌套的内部函数成为垃圾对象时
function fn1() {
//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
var a = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null // 闭包死亡(包含闭包的函数对象成为垃圾对象)
(6) 闭包的应用:自定义 JS 模块
模块:
具有特定功能的 js 文件
将所有的数据和功能都封装在一个函数内部(私有的)
只向外暴露一个包括 n 个方法的对象或函数
模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
例如:
文件 myModule1.js 就是一个模块
function myModule() {
// 私有数据
var msg = 'Hello This is Module1'
// 操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
function fn1() {
var arr = new Array[100000]
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null //让内部函数成为垃圾对象-->回收闭包
补充:内存溢出与内存泄露
内存溢出
一种程序运行出现的错误
当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
内存泄露
占用的内存没有及时释放
内存泄露积累多了就容易导致内存溢出
常见的内存泄露:
意外的全局变量
没有及时清理的计时器或回调函数
闭包
(8) 面试题
//代码片段一
var name = "The Window"
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name
}
}
}
alert(object.getNameFunc()()) // the window
//代码片段二
var name2 = "The Window"
var object2 = {
name2 : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name2;
}
}
}
alert(object2.getNameFunc()()) // my object
// 三
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)//undefined,0,0,0
var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
var c = fun(0).fun(1)
c.fun(2)
c.fun(3) // undefined,0,1,1
三、面向对象高级
1、对象创建模式
有如下五种方式:
(1) Object 构造函数模式
套路:先创建空 Object 对象,再动态添加属性/方法
适用场景:起始时不确定对象内部数据
问题:语句太多
/*
一个人: name:"Tom", age: 12
*/
// 先创建空Object对象
var p = new Object()
p = {} //此时内部数据是不确定的
// 再动态添加属性/方法
p.name = 'Tom'
p.age = 12
p.setName = function (name) {
this.name = name
}
//测试
console.log(p.name, p.age)
p.setName('Bob')
console.log(p.name, p.age)
(2) 对象字面量模式
套路:使用 {} 创建对象,同时指定属性/方法
适用场景:起始时对象内部数据是确定的
问题:如果创建多个对象,有重复代码
var p = {
name: 'Tom',
age: 12,
setName: function (name) {
this.name = name
}
}
//测试
console.log(p.name, p.age)
p.setName('JACK')
console.log(p.name, p.age)
var p2 = { //如果创建多个对象代码很重复
name: 'Bob',
age: 13,
setName: function (name) {
this.name = name
}
}
(3) 工厂模式
套路:通过工厂函数动态创建对象并返回
适用场景:需要创建多个对象
问题:对象没有一个具体的类型,都是 Object 类型
function createPerson(name, age) { //返回一个对象的函数===>工厂函数
var obj = {
name: name,
age: age,
setName: function (name) {
this.name = name
}
}
return obj
}
// 创建2个人
var p1 = createPerson('Tom', 12)
var p2 = createPerson('Bob', 13)
// p1/p2 是 Object 类型
function createStudent(name, price) {
var obj = {
name: name,
price: price
}
return obj
}
var s = createStudent('张三', 12000)
// s 也是 Object
(4) 自定义构造函数模式
套路:自定义构造函数,通过 new 创建对象
适用场景:需要创建多个类型确定的对象
问题:每个对象都有相同的数据,浪费内存
//定义类型
function Person(name, age) {
this.name = name
this.age = age
this.setName = function (name) {
this.name = name
}
}
var p1 = new Person('Tom', 12)
p1.setName('Jack')
console.log(p1.name, p1.age)
console.log(p1 instanceof Person)
function Student (name, price) {
this.name = name
this.price = price
}
var s = new Student('Bob', 13000)
console.log(s instanceof Student)
var p2 = new Person('JACK', 23)
console.log(p1, p2)
方法两者都有,且相同,不需要单独拥有,可放到原型中(方法一般放到原型中)——>
(5) 构造函数+原型的组合模式
套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
适用场景:需要创建多个类型确定的对象
function Person(name, age) { //在构造函数中只初始化一般函数
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
var p1 = new Person('Tom', 23)
var p2 = new Person('Jack', 24)
console.log(p1, p2)
2、继承模式
继承,在 JS 中有三种方式:
(1) 原型链继承
套路:
定义父类型构造函数
给父类型的原型添加方法
定义子类型的构造函数
创建父类型的对象赋值给子类型的原型
将子类型原型的构造属性设置为子类型
给子类型原型添加方法
创建子类型的对象:可以调用父类型的方法
关键:
子类型的原型为父类型的一个实例对象
//父类型
function Supper() {
this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function () {
console.log(this.supProp)
}
//子类型
function Sub() {
this.subProp = 'Sub property'
}
// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型的原型的constructor指向子类型
Sub.prototype.constructor = Sub
Sub.prototype.showSubProp = function () {
console.log(this.subProp)
}
var sub = new Sub()
sub.showSupperProp()
// sub.toString()
sub.showSubProp()
console.log(sub) // Sub
(2) 借用构造函数继承
套路:
定义父类型构造函数
定义子类型构造函数
在子类型构造函数中调用父类型构造
关键:
在子类型构造函数中通过 call() 调用父类型构造函数
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age, price) {
Person.call(this, name, age) // 相当于: this.Person(name, age)
/*this.name = name
this.age = age*/
this.price = price
}
var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)
(3) 组合继承
利用原型链实现对父类型对象的方法继承
利用 super() 借用父类型构建函数初始化相同属性
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
function Student(name, age, price) {
Person.call(this, name, age) // 为了得到属性
this.price = price
}
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student // 修正 constructor 属性
Student.prototype.setPrice = function (price) {
this.price = price
}
var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)
ES 6 中的继承:使用 class 类的 extends 关键字实现继承
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
setName(name) {
this.name = name
}
}
class Student extends Person {
constructor(name, age, price) {
super(name, age)
this.price = price
}
setPrice(price) {
this.price = price
}
}
var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)
四、线程机制与事件机制
1、进程与线程
(1) 进程(process)
程序的一次执行,它占有一片独有的内存空间
可以通过 Windows 任务管理器查看进程
(2) 线程(thread)
是进程内的一个独立执行单元
是程序执行的一个完整流程
是 CPU 的最小的调度单元
应用程序必须运行在某个进程的某个线程上
一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
一个进程内的数据可以供其中的多个线程直接共享
多个进程之间的数据是不能直接共享的
线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用
(3) 相关问题
何为多进程与多线程?
多进程运行:一应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时有多个线程运行
比较单线程与多线程?
多线程
优点
能有效提升 CPU 的利用率
缺点
创建多线程开销
线程间切换开销
死锁与状态同步问题
单线程
优点
顺序编程简单易懂
缺点
效率低
JS 是单线程还是多线程?
jS 是单线程运行的,但使用 H5 中的 Web Workers 可以多线程运行
浏览器运行是单线程还是多线程?
都是多线程运行的
浏览器运行是单进程还是多进程?
有的是单进程
firefox
老版 IE
有的是多进程
chrome
新版 IE
如何查看浏览器是否是多进程运行的呢?
任务管理器-->进程
2、浏览器内核
支撑浏览器运行的最核心的程序
不同的浏览器可能不一样
Chrome、Safari:webkit
firefox:Gecko
IE:Trident
360、搜狗等国内浏览器:Trident + webkit(双核)
内核由很多模块组成
主线程
js 引擎模块:负责 js 程序的编译与运行
html、css文档解析模块:负责页面文本的解析
DOM/CSS模块:负责 dom/css 在内存中的相关处理
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
......
分线程
定时器模块:负责定时器的管理
DOM事件响应模块:负责事件的管理
网络请求模块:负责 ajax 请求
3、定时器引发的思考
定时器真是定时执行的吗?
定时器并不能保证真正定时执行(设置了 n 秒,则是 n 秒之后执行)
一般会延迟一丁点(可以接受),也有可能延迟很长时间(不能接受)
定时器回调函数是在分线程执行的吗?
在主线程执行的,js 是单线程的
定时器是如何实现的?
事件循环模型
document.getElementById('btn').onclick = function () {
var start = Date.now()
console.log('启动定时器前...')
setTimeout(function () {
console.log('定时器执行了', Date.now()-start)
}, 200)
console.log('启动定时器后...')
// 做一个长时间的工作
for (var i = 0; i < 1000000000; i++) {
}
}