利用事件冒泡机制,将原有子元素绑定的事件,绑定给祖先元素,祖先元素通过event.target || event.srcElement属性 定位触发此次事件的目标子元素(只绑定一次事件,即可应用到多个元素,即使元素时后添加的)
优点:减少了DOM操作,极大地改善了代码性能
局限:focus,blur之类事件本身没有冒泡机制,无法委托
mousemove,mouseout这类的事件,虽然存在事件冒泡,但是每次都需要计算位置,消耗很高很麻烦,同样不适用于事件委托
事件的传播:
微软设计思想:事件应该由内向外传播->先触发子元素,再触发祖先元素->事件在冒泡阶段执行
网景设计思想:事件应该由外向内传播->先触发祖先元素,在触发子元素->事件在捕获阶段执行
w3c最终官方思想:综合两个公司的方案,
将事件传播分为三个阶段,(ie8及以上浏览器)
1、捕获阶段
-在捕获阶段是从最外层的祖先元素,向目标元素进行事件捕获
但是默认不触发事件
2、目标阶段
-事件捕获到目标元素,捕获结束,在目标元素上执行事件
3、冒泡阶段
-事件从目标元素向祖元素传递,依次触发祖先元素的事件
(ie8及以下浏览器,没有捕获阶段!!!)
一般情况我们不会在捕获阶段触发事件
js基本数据类型
基本数据类型
String 字符串
Number 数值型
Boolean 布尔型
Null 空值型
Undefined 未定义
只要不是以上五种 ,都是Object
引用数据类型
Object 对象(复合数据类型)
对象分类:
1、内建对象
-由ES标准中定义的对象,再任何ES实现中都可以使用
-比如:Math String Number Function
2、宿主对象
-由js运行环环境提供的对象、主要指浏览器提供的对象
-比如:BOM 浏览器对象模型
DOM 文档对象模型
3、自定义对象,开发人员自建对象
基本数据类型的一些方法
2. 判断
typeof:
可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
不能判断: null与object object与array
instanceof:
判断对象的具体类型
可以判断: undefined, null
内存存储方式
栈内存(k,v存储,占内存小) 堆内存
变量名|保存基本数据类型值
变量名|保存地址值 >>>>>>>>> 对象类型数据
undefined:
代表变量定义了 但未赋值
null:
代表变量定义并已赋值,只是值为null
赋值null的作用:
1、初始赋值为null,表示变量x将要被赋值为对象
var x=null
2、引用类型x结束前赋值为null,表示x成为垃圾对象,要被垃圾回收器回收
x=null
js的变量本身是没有类型的, 变量的类型实际上是变量内存中数据的类型
* 数据 的类型
* 基本类型 栈内存
* 对象类型 堆内存
* 变量 的类型(变量内存值的类型,变量内存中的数据的类型)
* 基本类型: 保存就是基本类型的数据 栈内存保存 变量名及数据
* 引用类型: 保存的是地址值 栈内存保存变量名及对对象的引用地址值
JavaScript语言使用构造函数(constructor)作为对象的模板。 所谓“构造函数”,就是专门用来生成“对象”的函数。 它提供模板,描述对象的基本结构。 一个构造函数,可以生成多个对象,这些对象都有相同的结构。 构造函数的写法就是一个普通的函数,但是有自己的特征和用法。
-构造函首字母大写
-和普通函数区别数调用方式不同,构造函数需要new关键字来调用
(不写 new 的时候就是普通函数调用,没有创造对象的能力)
-每次 new 的时候,函数内部的 this 都是指向当前这次的实例化对象
理解1: 本质上,都是值(基本值/地址值)传递
理解2: 区别数据类型的情况,(基本数据类型)是值传递, (对象类型)是引用传递(地址值)
1. JS 环境中分配的内存的生命周期
* 分配内存, 当我们申明变量、函数、对象的时候,系统会自动为他们分配内存(js引擎处理)
* 使用内存,即读写内存,也就是使用变量、函数等
* 释放内存,使用完毕,由垃圾回收机制自动回收不再使用的内存
2. 释放内存
* 局部变量: 函数执行完自动释放,前提:这个局部变量 没有被其他地方引用
* 全局变量: 在网页也就是window对象关闭后释放
* 对象: 对象没有引用==>成为垃圾对象==>垃圾回收器回收,不是立即回收
解释:function f(){
var b={}
}
f();
引用类型变量b会在函数f()执行完自动释放,而b指向的对象 {} 会在稍后某个时刻被垃圾回收器回收
1. 什么是对象?
* 多个数据的封装体
* 用来保存多个数据的容器
* 一个对象代表现实中的一个事物
2. 为什么要用对象?
* 统一管理多个数据
3. 对象的组成
* 属性: 属性名(字符串)和属性值(任意)组成
* 方法: 一种特别的属性(属性值是函数)
4. 如何访问对象内部数据?
* .属性名: 编码简单, 有时不能用
* ['属性名']: 编码麻烦, 能通用
5、问题: 什么时候必须使用['属性名']的方式?
1. 属性名包含特殊字符: - 空格
2. 属性名不确定,(属性名是动态变化
12、JS语句分号问题
//js语句后面可以不加分号
//下面两种情况不加分号会有问题:
// --应在前方加分号
//1、小括号开头的前一条语句,否则可能被js引擎理解为上一语句为函数名称的函数调用
() ===> ;()
// 例子:
var a=3
;(function(){
})();
//2、中方括号开头的前一条语句,否则会被js引擎理解为上一条语句名为数组名的数组调用
[] ===> ;{}
// 例子:
var a=3
;[1,3].foreach(function(){
})
1. 函数的prototype属性
* 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
* 原型对象中有一个属性constructor, 它指向函数对象
* -------> :指向
函数------>函数.prototype属性--->原型对象---->原型对象.constructo属性----->函数
2. 给原型对象添加属性(一般都是方法)
* 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
a:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的隐式原型去找,如果还是找不到,就到隐式原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
b:如果对象自身和它的隐式原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
构造函数添加方法和原型添加方法的区别
1. 方法所处的位置不相同,构造函数内的方法就在构造函数内,prototype上的方法在原型链上
2. 使用prototype的方法在继承的时候是共有的,多个继承对象共有这个方法,所以一旦改变,所有的都改变
3. prototype上的变量和方法是不被序列化的
4. 通过原型法添加方法的好处是:所有对象都共享,节约内存。
通过构造函数添加成员方法,在建立多个实例时会存在浪费内存问题。
1. 每个函数都有一个prototype属性,即显式原型
2. 每个对象都有一个__proto__属性,可称为隐式原型
!!!函数也是对象---函数对象 函数也有__proto__属性
函数(prototype属性)及其对象(__proto__属性),都指向同一个原型对象,保存的是地址值
3. 对象的隐式原型的值为其对应构造函数的显式原型的值
4. 内存结构(图)
5. 总结:
* 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象(指向的地址值)
* 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
* 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
//定义构造函数 创建了一个Function对象,被变量Fn引用
function Fn() { // 内部语句: this.prototype = {}
}
// 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
console.log(Fn.prototype)
// 2. 每个实例对象都有一个__proto__,可称为隐式原型
//创建实例对象
var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype===fn.__proto__) // true
//给原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
//通过实例调用原型的方法
fn.test()
每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,这样就形成了一个链式结构,叫做原型链。别名: 隐式原型链
原型链是实现继承、查找对象属性的主要方法。
其基本思想:
是利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个prototype属性,指向原型对象。
原型对象都包含一个指向构造函数的指针(constructor)。
A. 原型链指向的实例对象
1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不是)
2. 所有函数都是Function的实例(包含Function)
3. Object的原型对象是原型链尽头
B. 原型链查找对象属性方法
* 访问一个对象的属性时,
* 先在自身属性中查找,找到返回
* 如果没有, 再沿着__proto__这条链向上查找, 找到返回
* 如果最终没找到, 返回undefined
* 表达式: A instanceof B
* 如果B函数的显式原型对象在A对象的原型链(隐式原型链)上,返回true, 否则返回false
1. 读取对象的属性值时: 会自动到原型链中查找
2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
开发建议:
方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
js中一切皆对象,
对象由构造函数构造 (构造函数本身也是对象,即函数对象)。对象有属性,对象的类别决定了它的属性。任何对象上都有一个内部属性__proto__,它指向了它的构造函数的原型对象属性prototype。函数对象上有一个原型对象属性prototype,它也有个内部属性__proto__,它一般指向需要继承的父原型对象。默认情况下自定义的构造函数中它指向Object的原型对象。
1. 变量声明提升
* 通过var定义(声明)的变量, 在定义语句之前就可以访问到
* 变量值: undefined
2. 函数声明提升
* 通过function声明的函数, 在之前就可以直接调用
* 值: 函数定义(对象)
3. 问题: 变量提升和函数提升是如何产生的?
js全局执行上下文的预处理操作产生的
1. 代码分类(位置)
* 全局代码
* 函数(局部)代码
2. js全局执行上下文步骤
* 步骤一:在执行全局代码前将window确定为全局执行上下文
* 步骤二:对全局数据进行预处理
* var定义的全局变量==>undefined, 添加为window的属性
* function声明的全局函数==>赋值(函数对象) , 添加为window的方法
* this==>赋值(window)
* 步骤三:开始执行全局代码
3. 函数执行上下文 (调用之前才会创建执行上下文对象)
* 在 调用函数, 准备执行函数体 之前, 创建对应的函数执行上下文对象(虚拟的概念, 存在于栈中,函数执行完消失)
函数执行上下文对象属性===局部变量===存在栈中
* 对局部数据进行预处理
* 形参变量==>赋值(实参)==>添加为执行上下文的属性
* arguments==>赋值(实参列表), 添加为执行上下文的属性
* var定义的局部变量==>undefined, 添加为执行上下文的属性
* function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
* this==>赋值(调用函数的对象)
* 开始执行函数体代码
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window
上下文执行栈数量公式 n+1 【n:函数调用次数 1:window函数(全局函数)】
先执行变量提升再执行函数提升?
//先预处理变量, 后预处理函数
function a() {}
var a;
console.log(typeof a) //Function
作用域:
1. 理解
* 就是一块"地盘", 一个代码段所在的区域
* 它是静态的(相对于上下文对象), 在编写代码时就确定了
2. 分类
* 全局作用域
* 函数作用域
* 块作用域(ES6及以后版本存在 )
3. 作用
* 隔离变量,不同作用域下同名变量不会有冲突
4.作用域数量公式
* n+1 【n:定义函数次数 1:window 全局作用域】
作用域链:
1. 理解
* 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
* 查找变量时就是沿着作用域链来查找的,在对应作用域的上下文环境中查找
2. 查找一个变量的查找规则
在上下级作用域中的执行上下文中 由下向上(由内到外)沿着作用域链依次查找目标变量,未找到则会报错 x is not defined
//产生闭包
function fn1 () {
var a = 2
var b = 'abc'
function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
console.log(a)
}
// fn2()
}
fn1()
1. 如何产生闭包?
* 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
2. 闭包、闭包函数是什么?
闭包是一个函数和该函数被定义时的词法环境的组合
一个函数和 对其周围状态的引用 捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
闭包函数:定义在一个函数内部的子函数,能够读取父函数内部变量的函数,称为闭包函数(有权访问另一个函数作用域中的变量)
3. 产生闭包的条件?
* 1、函数嵌套
* 2、内部函数引用了外部函数的数据(变量/函数)
* 3、执行内部函数定义(不用调用函数)
4、闭包函数为什么延长了局部变量的生命周期?
闭包是一种机制,本质就是一个为保护部分数据而产生的一种不会被自动销毁回收的栈空间(会导致内存溢出和内存泄露)
在闭包机制中、父函数声明在被执行会产生私有作用域,在栈中存放,(一般在函数执行完这个私有作用域就会释放),
但是当前私有作用域的某一部分内容被其他东西占用(比如闭包函数引用父函数的局部变量),所以当前私有作用域及其私有变量就不会被释放,且能够享受到和全局变量一样的特权,关闭页面/浏览器前不会被回收,从而延长了局部变量的生命周期
<!--
1. 将函数作为另一个函数的返回值
2. 将函数作为实参传递给另一个函数调用
-->
<script type="text/javascript">
// 1. 将函数作为另一个函数的返回值
function fn1() {//fn1()一执行就产生闭包 因为 fn2() 声明式创建函数 具有函数变量提升特性
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('atguigu', 2000)
//思考怎样产生闭包,为什么?
</script>
<script type="text/javascript">
<!--
3.闭包的应用2 : 定义JS模块
* 具有特定功能的js文件
* 将所有的数据和功能都封装在一个函数内部(私有的)
* 只向外暴露一个包含 n个方法的对象或函数
* 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
var module = myModule()
module.doSomething()
module.doOtherthing()
myModule2.doSomething()
myModule2.doOtherthing()
</script>
myModule.js
function myModule() {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
myModule2.js
(function () {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()
<!--
1. 缺点
* 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
* 容易造成内存泄露
2. 解决
* 能不用闭包就不用
* 及时释放
-->
<script type="text/javascript">
function fn1() {
var arr = new Array[100000]
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null //让内部函数成为垃圾对象-->回收闭包
</script>
<!--
1. 内存溢出
* 一种程序运行出现的错误
* 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
2. 内存泄露
* 占用的内存没有及时释放,导致可用内存变少
* 内存泄露积累多了就容易导致内存溢出
* 常见的内存泄露:
* 意外的全局变量
* 没有及时清理的计时器或回调函数
* 闭包
-->
<script type="text/javascript">
// 1. 内存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
// 2. 内存泄露
// 意外的全局变量
function fn() {
a = new Array(10000000)
console.log(a)
}
fn()
// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
// 闭包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
// f = null
</script>
<!--
方式一: Object构造函数模式
* 套路: 先创建空Object对象, 再动态添加属性/方法
* 适用场景: 起始时不确定对象内部数据
* 问题: 语句太多
-->
<script type="text/javascript">
/*
一个人: name:"Tom", age: 12
*/
var p = new Object()
p = {}
p.name = 'Tom'
p.age = 12
p.setName = function (name) {
this.name = name
}
p.setaAge = function (age) {
this.age = age
}
console.log(p)
</script>
<!--
方式二: 对象字面量模式
* 套路: 使用{}创建对象, 同时指定属性/方法
* 适用场景: 起始时对象内部数据是确定的
* 问题: 如果创建多个对象, 有重复代码
-->
<script type="text/javascript">
var p = {
name: 'Tom',
age: 23,
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: 24,
setName: function (name) {
this.name = name
}
}
</script>
<!--
方式三: 工厂模式
* 套路: 通过工厂函数动态创建对象并返回
* 适用场景: 需要创建多个对象
* 问题: 对象没有一个具体的类型, 都是Object类型
-->
<script type="text/javascript">
// 工厂函数: 返回一个需要的数据的函数
function createPerson(name, age) {
var p = {
name: name,
age: age,
setName: function (name) {
this.name = name
}
}
return p
}
var p1 = createPerson('Tom', 12)
var p2 = createPerson('JAck', 13)
console.log(p1)
console.log(p2)
</script>
<!--
方式四: 自定义构造函数模式
* 套路: 自定义构造函数, 通过new创建对象
* 适用场景: 需要创建多个类型确定的对象
* 问题: 每个对象都有相同的数据, 浪费内存
-->
<script type="text/javascript">
function Person(name, age) {
this.name = name
this.age = age
this.setName = function (name) {
this.name = name
}
}
var p1 = new Person('Tom', 12)
var p2 = new Person('Tom2', 13)
console.log(p1, p1 instanceof Person)
</script>
<!--
方式五: 构造函数+原型的组合模式
* 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
* 适用场景: 需要创建多个类型确定的对象
-->
<script type="text/javascript">
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
var p1 = new Person('Tom', 12)
var p2 = new Person('JAck', 23)
p1.setName('TOM3')
console.log(p1)
Person.prototype.setAge = function (age) {
this.age = age
}
p1.setAge(23)
console.log(p1.age)
Person.prototype = {}
p1.setAge(34)
console.log(p1)
var p3 = new Person('BOB', 12)
p3.setAge(12)
</script>
方式1: 原型链继承
<!--
方式1: 原型链继承
1. 套路
1. 定义父类型构造函数
2. 给父类型的原型添加方法
3. 定义子类型的构造函数
4. 创建父类型的对象赋值给子类型的原型
5. 将子类型原型的构造属性设置为子类型
6. 给子类型原型添加方法
7. 创建子类型的对象: 可以调用父类型的方法
2. 关键
1. 子类型的原型为父类型的一个实例对象
-->
<script>
//父类型
function Supper(){
this.supProp='Supper property'
}
Supper.prototype.showSupperProp=function () {
console.log(this.supProp)
}
//子类型
function Sub(){
this.subProp='Sub property'
}
Sub.prototype.showSubProp=function () {
console.log(this.subProp)
}
Sub.prototype=new Supper();//关键点:子类型的原型对象 等于父类型的实例
var sub=new Sub()
sub.showSupperProp();//父类方法
console.log(Sub)
</script>
方式1: 原型链继承 内存关系结构图
方式2: 借用构造函数继承(假的)
<!--
方式2: 借用构造函数继承(假的)
1. 套路:
1. 定义父类型构造函数
2. 定义子类型构造函数
3. 在子类型构造函数中调用父类型构造
2. 关键:
1. 在子类型构造函数中通用call()调用父类型构造函数
-->
<script type="text/javascript">
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)
</script>
方式三:组合继承
<!--
方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用call()方法 借用父类型构建函数初始化 相同属性
-->
<script type="text/javascript">
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
function Student(name, age, price) {
//相当于调用了父类的构造函数
//等价于this.Person(name,age) 能够给父类设置初始化值
//call 让函数指定被谁调用
Person.call(this, name, age) // 为了得到(设置)属性
this.price = price
}
//此处等价与 Student.prototype.__proto__ = Person.prototype
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)
</script>
这样写好点,视频里的,多了对象的属性
<!--
1. 进程:程序的一次执行, 它占有一片独有的内存空间
2. 线程: CPU的基本调度单位, 是程序执行的一个完整流程
3. 进程与线程
* 一个进程中一般至少有一个运行的线程: 主线程
* 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
* 一个进程内的数据可以供其中的多个线程直接共享
* 多个进程之间的数据是不能直接共享的
4. 浏览器运行是单进程还是多进程?
* 有的是单进程
* firefox
* 老版IE
* 有的是多进程
* chrome
* 新版IE
但都是多线程的
5. 如何查看浏览器是否是多进程运行的呢?
* 任务管理器==>进程
6. 浏览器运行是单线程还是多线程?
* 都是多线程运行的
7.JavaScript单线程还是多线程
js是单线程运行的
使用H5中的Web Workers可以多线程运行
-->
<!--
1. 什么是浏览器内核?
* 支持浏览器运行的最核心的程序
2. 不同的浏览器可能不太一样
* Chrome, Safari: webkit
* firefox: Gecko
* IE: Trident
* 360,搜狗等国内浏览器: Trident + webkit
3. 内核由很多模块(也是代码)组成
主线程:
* html文档解释器模块 : 负责页面html文本的解析,生成document dom对象树
* js引擎模块:负责将解析出的document中包含的js脚本进行编译与运行
* CSS解释器模块 : 负责解析dom对象树的CSS的样式信息来构建绘图模型
* 布局和渲染模块 : 利用绘图模型进行页面的布局和效果的绘制
分线程:
* 定时器模块 : 负责定时器的管理
* 网络请求模块 : 负责服务器请求(常规/Ajax)
* 事件响应模块 : 负责事件的管理
-->