博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript 高级笔记
阅读量:3947 次
发布时间:2019-05-24

本文共 34702 字,大约阅读时间需要 115 分钟。

JavaScript 高级笔记

1. 基础总结深入

1.1 数据类型

1.1.1 数据类型分类
  • 基本()类型
    • String:任意字符串
    • Number:任意的数字
    • boolean:true/false
    • undefined:undefined
    • null:null
  • 对象(引用)类型
    • Object:任意对象
    • Function:一种特别的对象(可以执行)
    • Array:一种特别的对象(数值下标, 内部数据是有序的)
1.1.2 数据类型判断
  • typeof
    • 可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
    • 不能判断: null与object object与array
  • instanceof
    • 判断对象的具体类型
  • ===
    • 可以判断: undefined, null
//1. 基本  // typeof返回数据类型的字符串表达  var a  console.log(a, typeof a, typeof a==='undefined',a===undefined )  // undefined 'undefined' true true  console.log(undefined==='undefined')  a = 4  console.log(typeof a==='number')  a = 'atguigu'  console.log(typeof a==='string')  a = true  console.log(typeof a==='boolean')  a = null  console.log(typeof a, a===null) // 'object'-------------------------------------------------------------------------------------    //2. 对象  var b1 = {
b2: [1, 'abc', console.log], b3: function () {
console.log('b3') return function () {
return 'xfzhang' } } } console.log(b1 instanceof Object, b1 instanceof Array) // true false console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true console.log(typeof b1.b2, '-------') // 'object' console.log(typeof b1.b3==='function') // true console.log(typeof b1.b2[2]==='function') b1.b2[2](4) console.log(b1.b3()())
1.1.3 数据类型相关问题
  1. undefined 与 nul l的区别?
  • undefined 代表定义未赋值
  • null 定义并赋值了, 只是值为 null
// 1. undefined与null的区别?  var a  console.log(a)  // undefined  a = null  console.log(a) // null
  1. 什么时候给变量赋值为 null 呢?
  • 初始赋值, 表明将要赋值为对象
  • 结束前, 让对象成为垃圾对象(被垃圾回收器回收)
//起始  var b = null  // 初始赋值为null, 表明将要赋值为对象  //确定对象就赋值  b = ['atguigu', 12]  //最后  b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收)  // b = 2
  1. 严格区别变量类型与数据类型?
  • 数据的类型
    • 基本类型
    • 对象类型
  • 变量的类型(变量内存值的类型)
    • 基本类型: 保存就是基本类型的数据
    • 引用类型: 保存的是地址值
var c = function () {
}console.log(typeof c) // 'function'

1.2 数据、变量与内存

1.2.1 什么是数据
  • 存储在内存中代表特定信息的'东东', 本质上是二进制0101...
  • 数据的特点: 可传递, 可运算
  • 一切皆数据,函数也是数据
  • 内存中所有操作的目标: 数据
    • 算术运算
    • 逻辑运算
    • 赋值
    • 运行函数(调用函数传参)
1.2.2 什么是内存
  • 内存条通电后产生的可储存数据的空间(临时的)
  • 内存产生和死亡
    • 内存条(电路板)==>通电==>产生内存空间==>存储数据==>处理数据==>断电==>内存空间和数据都消失
  • 内存的空间是临时的,而硬盘的空间是持久的
  • 分配内存
    • 声明变量和函数或创建对象时,JS 引擎会自动为此分配一定大小的内存来存放对应的数据
  • 释放内存
    • 清空内存中的数据,标识内存可以再分配使用(内存不释放就不能复用)
  • 自动释放
    • 栈空间的局部变量
  • 垃圾回调器回调
    • 堆空间的垃圾对象
  • 一块小内存的2个数据
    • 内部存储的数据
    • 地址值
  • 内存分类
    • 栈: 全局变量/局部变量
    • 堆: 对象
1.2.3 什么是变量
  • 可变化的量, 由变量名和变量值组成
  • 每个变量都对应的一块小内存, 变量名用来查找对应的内存, 变量值就是内存中保存的数据
1.2.4 内存,数据, 变量三者之间的关系
  • 内存是一个容器, 用来存储程序运行需要操作的数据
  • 变量是内存的标识, 通过变量找到对应的内存,进而操作(读/写)内存中的数据
1.2.5 相关问题
  • 关于赋值和内存的问题
问题: var a = xxx, a内存中到底保存的是什么?    * xxx是基本数据, 保存的就是这个数据    * xxx是对象, 保存的是对象的地址值    * xxx是一个变量, 保存的xxx的内存内容(可能是基本数据, 也可能是地址值)
  • 关于引用变量赋值问题
关于引用变量赋值问题  * 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
  • 关于数据传递问题
问题: 在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
  • JS引擎如何管理内存
问题: JS引擎如何管理内存?1. 内存生命周期  * 分配小内存空间, 得到它的使用权  * 存储数据, 可以反复进行操作  * 释放小内存空间2. 释放内存  * 局部变量: 函数执行完自动释放  * 对象: 成为垃圾对象==>垃圾回收器回收  var a = 3  var obj = {
} obj = undefined function fn () {
var b = {
} } fn() // b是自动释放, b所指向的对象是在后面的某个时刻由垃圾回收器回收

1.3 对象

1.3.1 什么是对象
  • 多个数据的封装体
  • 用来保存多个数据容器
  • 一个对象代表现实中的一个事物
1.3.2 为什么要用对象
  • 统一管理多个数据
1.3.3 对象的组成
  • 属性
    • 代表现实事物的状态数据
    • 由属性名(字符串)和属性值(任意)组成
  • 方法
    • 代表现实事物的行为数据
    • 一种特别的属性(属性值是函数)
1.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])

1.4 对象方法

  • 函数也可以称为对象的属性
    • 如果一个函数作为一个对象的属性保存
    • 那么称这个函数是这个对象的方法
    • 调用函数就说调用对象的方法(method)
  • 只是名称上的区别,没有其他区别
// 创建一个对象var obj = new Object();// 向对象中添加属性obj.name = 'zs';obj.age = 18;// 对象的属性值可以是任何的数据类型,也可以是个函数obj.sayName = function(){
console.log(obj.name);};function fun(){
console.log(obj.name);}// console.log(obj.sayName);// 调方法obj.sayName();// 调函数fun();

1.5 枚举对象中的属性

  • 枚举对象中的属性
    • 使用 for...in 语句
  • 语法
    • for(var 变量 in 对象){}
  • for...in 语句
    • 对象中有几个属性,循环体就会执行几次
    • 每次执行时,会将对象中的一个属性的名字赋值给变量
for(var n in obj){
console.log(n) // 属性名 console.log(obj[n]) // 属性值}

1.6 作用域

  • 作用域指一个变量的作用的范围
1.6.1 全局作用域
  • 直接编写在 script 标签中的 JS 代码,都在全局作用域
  • 全局作用域在页面打开时创建,在页面关闭时销毁
  • 在全局作用域中有一个全局对象 window
    • 代表的是一个浏览器的窗口,它由浏览器创建
    • 可以直接使用
  • 在全局作用域中
    • 创建的变量都会作为 window 对象的属性保存
    • 创建的函数都会作为 window 对象的方法保存
    • 将函数定义在全局作用域,污染了全局作用域的命名空间
      • 而且定义在全局作用域中也很不安全
var a = 10;console.log(window.a) // 10function fun(){
console.log(12)}window.fun() // 12
  • 变量声明提前
    • 使用 var 关键字声明的变量,会在所有的代码执行之前被声明
    • 但是如果声明变量时不使用 var 关键字,则变量不会被声明提前
    • 全局作用域中的变量都是全局变量
      • 页面的任意的部分都可以访问的到
console.log(a); // undefinedvar a = 123;
  • 函数声明提前
    • 使用函数声明形式创建的函数 function 函数()
      • 它会在所有的代码执行之前就被创建
    • 使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用
fun()fun2() // undefined is not a function// 函数声明,会被提前创建function fun(){
console.log(1212)}// 函数表达式,不会被提前创建var fun2 = function(){
console.log(2121)}
1.6.2 函数作用域
  • 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
  • 每调用一次函数就会创建一个新的函数作用域,他们之间时相互独立
  • 函数作用域可以访问到全局作用域的变量
  • 全局作用域无法访问到函数作用域的变量
  • 在函数作用域操作一个变量时,它会先在自身作用域中寻找
    • 如果有就直接使用
    • 如果没有则向上一级作用域中寻找,直到找到全局作用域
    • 如果全局作用域中依然没有找到,则会报错 ReferenceError
  • 在函数中要访问全局变量可以使用 window 对象
// 创建一个变量var a = 10;function fun(){
var a = 12 var b = 20; console.log(a) // 12}fun()console.log(b) // b is undefinedconsole.log(a) // 10
  • 函数作用域也有声明提前的特性
    • 使用 var 关键字声明的变量,会在函数中所有的代码执行之前被声明
    • 函数声明也会在函数中所有的代码执行之前执行
function fun3(){
fun4() // console.log(a) // undefined var a = 32 function fun4(){
alert(12) }}fun3()
  • 在函数中,不使用 var 声明的变量都会成为全局变量
var c = 33function fun5(){
// console.log(c) // undefined // var c = 10 console.log(c) // 33 // d 没有使用 var 关键字,则会设置为全局变量 d = 100}fun5()// 在全局输出cconsole.log(d) // 100
  • 定义形参就相当于在函数作用域中声明了变量
var e = 23function fun6(e){
alert(e) // undefined}fun6()

1.7 使用工厂方法创建对象

  • 通过该方法可以大批量的创建对象
  • 使用工厂方法创建的对象,使用的构造函数都是 Object
    • 所以创建的对象都是 Object 这个类型
    • 就导致我们无法区分出多种不同类型的对象
// 创建对象var obj = {
name: 'zs', age: 18, sayName: function(){
alert(this.name) }}function createPerson(name, age, gender){
// 创建一个新的对象 var obj = new Object() // 向对象中添加属性 obj.name = name; obj.age = age; obj.gender = gender; obj.sayName: function(){
alert(this.name); } // 将新的对象返回 return obj;}var obj2 = createPerson('zx',21,'男')console.log(obj2)

1.8 原型对象

  • 原型 prototype
    • 我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype
    • 这个属性对应着一个对象,这个对象就是我们所谓的原型对象
  • 如果函数作为普通函数调用 prototype 没有任何作用
  • 当函数以构造函数的形式调用
    • 它所创建的对象中都会有一个隐含的属性
    • 指向该构造函数的原型对象
    • 可以通过 __proto__ 来访问该属性
  • 原型对象就相当于一个公共的区域
    • 所有同一个类的实例都可以访问到这个原型对象
    • 可以将对象中共有的内容,统一设置到原型对象中
  • 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找
    • 如果有,则直接使用
    • 如果没有,则会去原型对象中寻找,如果找到则直接使用
  • 以后我们创建构造函数时,可以将这些对象共有的属性和方法
    • 统一添加到构造函数的原型对象中
    • 这样不用分别为每一个对象添加
    • 不会影响到全局作用域
    • 就可以使每个对象都具有这些属性和方法

在这里插入图片描述

function MyClass(){
}// 向 MyClass 的原型中添加属性 aMyClass.prototype.a = 123;// 向 MyClass 的原型中添加一个方法MyClass.prototype.sayHello = function(){
alert('hello');};var mc = new MyClass();var mc2 = new MyClass();// console.log(MyClass.prototype); // [object Object]// console.log(mc.__proto__); // [object Object]// console.log(mc2.__proto__ == MyClass.prototype); // trueconsole.log(mc.a) // 123mc.sayHello();
  • 使用 in 检查对象中是否含有某个属性
    • 如果对象中没有,但是原型中有,也会返回 true
  • 可以使用对象的 hasOwnProperty()检查对象自身中是否含有该属性
    • 使用该方法只有当对象自身中含有属性时,才会返回 true
console.log("name" in mc)console.log(mc.hasOwnProperty("name"))
  • 原型对象也是对象,所以它也有原型
    • 当我们使用一个对象的属性或方法时,会先在自身中寻找
    • 自身中如果有,则直接使用
    • 如果没有,则去原型对象中寻找,如果原型对象中有,则使用
    • 如果没有,则去原型对象的原型对象中寻找,直到找到 Object 对象的原型
  • Object 对象的原型没有原型对象
    • 如果在 Object 中依然没有找到,则返回 undefined
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"))

在这里插入图片描述

1.9 垃圾回收

  • 垃圾回收(GC)
    • 程序运行过程中也会产生垃圾
    • 这些垃圾积攒过多以后,会导致程序运行的速度过慢
    • 所以需要一个垃圾回收的机制,来处理程序运行过程中产生的垃圾
  • 当一个对象没有任何的变量或属性对它进行引用
    • 此时我们将永远无法操作该对象
    • 此时这种对象就是一个垃圾,这种对象过多占用大量的内存空间,导致程序运行变慢
    • 所以这种垃圾必须清理
  • 在 JS 中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁
    • 我们不需要也不能进行垃圾回收的操作
  • 我们需要做的只是要将不再使用的对象设置为 null 即可
var obj = new Object();obj = null

2. 函数

2.1 什么是函数

  • 实现特定功能的 n 条语句的封装体
  • 只有函数是可以执行的, 其它类型的数据不能执行
  • 使用 typeof 检查一个函数对象时,会返回 function
// 创建一个函数对象// 可以将要封装的代码以字符串的形式传递给构造函数var fun = new Function("console.log('Hello 这是我的第一个函数');")// 封装到函数中的代码不会立即执行// 函数中的代码会在函数调用的时候执行// 调用函数 语法:函数对象()// 当调用函数时,函数中封装的代码会按照顺序执行fun();/*	使用函数声明来创建一个函数	语法:		function 函数名([形参1,形参2...形参N]){			语句		}	*/function fun2(){
console.log("第二个函数") alert("哈哈哈") document.write("123")}// console.log(fun2)// 调用 fun2fun2()/* 使用函数表达式来创建一个函数 语法: var 函数名 = function([形参1, 形参2...形参N]){ 语法... } */var fun3 = function(){
console.log("我是匿名函数中封装的代码")};fun3()

2.2 为什么要用函数

  • 提高代码复用
  • 便于阅读交流
/*  编写程序实现以下功能需求:    1. 根据年龄输出对应的信息    2. 如果小于18, 输出: 未成年, 再等等!    3. 如果大于60, 输出: 算了吧!    4. 其它, 输出: 刚好!  */  function showInfo (age) {
if(age<18) {
console.log('未成年, 再等等!') } else if(age>60) {
console.log('算了吧!') } else {
console.log('刚好!') } } showInfo(17) showInfo(20) showInfo(65)

2.3 如何定义函数

  • 函数声明
  • 函数表达式
var fun = new Function(){
...} // 构造函数 function fn1 () {
// 函数声明 console.log('fn1()') } var fn2 = function () {
// 函数表达式 console.log('fn2()') } fn1() fn2()

2.4 如何调用(执行)函数

  • test(): 直接调用
  • obj.test(): 通过对象调用
  • new test(): new调用
  • test.call/apply(obj): 临时让 test 成为 obj 的方法进行调用
var obj = {
} function test2 () {
this.xxx = 'atguigu' } // obj.test2() 不能直接, 根本就没有 test2.call(obj) // obj.test2() // 可以让一个函数成为指定任意对象的方法进行调用 console.log(obj.xxx)

2.5 函数的参数

  • 形参
    • 可以在函数的()中来指定一个或多个形参(形式参数)
    • 多个形参之间使用,隔开,声明形参就相当于在函数内部声明了对应的变量
    • 但是并不赋值
function sum(a,b){
console.log(a+b);}
  • 实参
    • 在调用函数时,可以在()中指定实参(实际参数)
    • 实参将会赋值给函数中对应的形参
sum(1,2)
  • 调用函数时解析器不会检查实参的类型
  • 所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查
  • 函数的实参可以是任意的数据类型
sum(123,'hello')sum(true, false)
  • 调用函数时,解析器也不会检查实参的数量
  • 多余的实参不会被赋值
  • 如果实参的数量少于形参的数量,则没有对应实参的形参将是 undefined
sum(123,345,'hello',true,null)sum(123)

2.6 函数的返回值

  • 可以使用 return设置函数的返回值
  • 语法
    • return 值
  • return 后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果
  • 在函数中 return 后的语句都不会执行
  • 如果 return 语句后不跟任何值就相当于返回一个 undefined
  • 如果函数中不写 return,则也会返回 undefined
  • return 后可以跟任意类型的数据类型,也可以是个对象,也可以是个函数
// 创建一个函数,用来计算三个数的和function sum(a,b,c){
var d = a + b + c; return d; // alert('123') // return; // return undefined;}// 调用函数// 变量 result 的值就是函数的执行结果// 函数返回什么 result 的值就是什么var result = sum(4,7,8)console.log(result)
  • 返回值的类型
for(var i=0; i<5; i++){
if(i == 2){
// 使用 break 可以退出当前的循环 // break; // continue 用于跳出当次循环 // continue; // 使用 return 可以结束整个函数 return; } }

2.7 IIFE

  • 全称: 立即调用函数表达式(Immediately-Invoked Function Expression)
  • 作用
    • 隐藏实现
    • 不会污染外部(全局)命名空间
    • 用它来编码js模块
(function () {
//匿名函数自调用 var a = 3 console.log(a + 3) // 6 })() var a = 4 console.log(a) // 4 ;(function () {
var a = 1 function test () {
console.log(++a) // 2 } window.$ = function () {
// 向外暴露一个全局函数 return {
test: test } } })() $().test() // 1. $是一个函数 2. $执行后返回的是一个对象

2.8 函数中的 this

2.8.1 this 是什么
  • 解析器在调用函数每次都会向函数内部传递一个隐含的参数
    • 这个隐含的参数就是 this,this 指向的是一个对象
    • 这个对象称为函数执行的上下文对象
    • 根据函数的调用方式的不同this 会指向不同的对象
      • 函数的形式调用时,this 永远都是 window
      • 方法的形式调用时,this 就是调用方法的那个对象
  • 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是 window
  • 所有函数内部都有一个变量 this
  • 它的值是调用函数的当前对象
2.8.2 如何确定 this 的值
  • 当以函数的形式调用时,this 是 window
  • 当以方法的形式调用时,谁调用方法 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(); //this是谁? window

2.9 JS 中的分号问题

  • js 一条语句的后面可以不加分号
  • 是否加分号是编码风格问题, 没有应该不应该,只有你自己喜欢不喜欢
  • 在下面2种情况下不加分号会有问题
    • 小括号开头的前一条语句
    • 中方括号开头的前一条语句
  • 解决办法: 在行首加分号
  • 强有力的例子: vue.js库
var a = 3  ;(function () {
})() /* 错误理解 var a = 3(function () { })(); */ var b = 4 ;[1, 3].forEach(function () {
}) /* 错误理解 var b = 4[3].forEach(function () { }) */

2.10 构造函数

  • 构造函数就是一个普通的函数,创建方式和普通函数没有区别
    • 不同的是构造函数习惯上首字母大写
  • 构造函数和普通函数的区别就是调用方式的不同
    • 普通函数是直接调用
    • 构造函数需要使用 new 关键字来调用
function Person(){
}var per = new Person()console.log(per)
  • 构造函数的执行流程
    • 立刻创建一个新的对象
    • 将新建的对象设置为函数中 this
      • 在构造函数中可以使用 this 来引用新建的对象
    • 逐行执行函数中的代码
    • 将新建的对象作为返回值返回
function Person(name, age){
this.name = name; this.age = age;}var per = new Person('zx',18)console.log(per)
  • 使用同一个构造函数创建的对象,称为一类对象,也将一个构造函数称为一个类
  • 我们将通过一个构造函数创建的对象,称为是该类的实例
  • 使用 instanceof 可以检查一个对象是否是一个类的实例
    • 语法:
      • 对象 instanceof 构造函数
      • 如果是,则返回 true
      • 如果不是,则返回 false
  • 所有的对象都是 Object 的后代
    • 所以任何对象和 Object 做 instanceof 检查时都会返回 true
console.log(per instanceof Person)

2.11 回调函数

  • 什么函数才是回调函数?

    • 定义的函数
    • 没有调用这个函数
    • 最终这个函数执行了(在某个时刻或某个条件下)
  • 常见的回调函数

    • dom 事件回调函数 ==>发生事件的 dom 元素
    • 定时器回调函数 ===> window
    • ajax 请求回调函数
    • 生命周期回调函数
document.getElementById('btn').onclick = function () {
// dom事件回调函数 alert(this.innerHTML) } //定时器 // 超时定时器 // 循环定时器 setTimeout(function () {
// 定时器回调函数 alert('到点了'+this) }, 2000)

3. 函数高级

3.1 原型与原型链

3.1.1 原型 prototype
  • 函数的 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) // true console.log(Fun.prototype.constructor===Fun) // true //给原型对象添加属性(一般是方法) ===>实例对象可以访问 Fun.prototype.test = function () {
console.log('test()') } var fun = new Fun() fun.test() // test()
3.1.2 显式原型与隐式原型
  • 每个函数 function 都有一个 prototype ,即显式原型(属性)
  • 每个实例对象都有一个__proto__,可称为隐式原型(属性)
  • 对象的隐式原型的值为其对应构造函数的显式原型的值
  • 内存结构(图)

在这里插入图片描述

  • 总结
    • 函数的 prototype 属性: 在定义函数时自动添加的, 默认值是一个空 Object 对象
    • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的 prototype 属性值
    • 原型对象即为当前实例对象的父对象
    • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
//定义构造函数  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()
3.1.3 原型链
  • 原型链(图解)
    • 访问一个对象的属性时,
      • 先在自身属性中查找,找到返回
      • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
      • 如果最终没找到, 返回undefined
    • 别名: 隐式原型链
    • 作用: 查找对象的属性(方法)

在这里插入图片描述

// console.log(Object)  //console.log(Object.prototype)  console.log(Object.prototype.__proto__)  function Fn() {
this.test1 = function () {
console.log('test1()') } } console.log(Fn.prototype) Fn.prototype.test2 = function () {
console.log('test2()') } var fn = new Fn() fn.test1() fn.test2() console.log(fn.toString()) console.log(fn.test3) // fn.test3() /* 1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足) */ console.log(Fn.prototype instanceof Object) // true console.log(Object.prototype instanceof Object) // false console.log(Function.prototype instanceof Object) // true /* 2. 所有函数都是Function的实例(包含Function) */ console.log(Function.__proto__===Function.prototype) /* 3. Object的原型对象是原型链尽头 */ console.log(Object.prototype.__proto__) // null
  • 构造函数/原型/实体对象的关系(图解)
var o1 = new Object();var o2 = {
};

在这里插入图片描述

  • 构造函数/原型/实体对象的关系2(图解)
function Foo(){
}// 本质var Foo = new Function(){
}Function = new Function()所有函数的__proto__都是一样的

在这里插入图片描述

  • 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object) // true	console.log(Object.prototype instanceof Object) // false	console.log(Function.prototype instanceof Object) // true
  • 所有函数都是Function的实例(包含Function本身)
console.log(Function.__proto__===Function.prototype)
  • Object的原型对象是原型链尽头
console.log(Object.prototype.__proto__) // null
  • 原型继承
    • 构造函数的实例对象自动拥有构造函数原型对象的属性(方法)
    • 利用的就是原型链
3.1.4 原型链属性问题
  • 读取对象的属性值时: 会自动到原型链中查找
  • 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  • 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
function Fn() {
} Fn.prototype.a = 'xxx' var fn1 = new Fn() console.log(fn1.a, fn1) // xxx var fn2 = new Fn() fn2.a = 'yyy' console.log(fn1.a, fn2.a, fn2) // xxx yyy 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) // Bob 12 var p2 = new Person('Jack', 12) p2.setName('Cat') console.log(p2) // Cat 12 console.log(p1.__proto__===p2.__proto__) // true
3.1.5 探索 instanceof
  • instanceof 是如何判断的?
    • 表达式: A instanceof B
      • A 是实例对象,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

在这里插入图片描述

/* 案例2 */  console.log(Object instanceof Function) // true  console.log(Object instanceof Object) // true  console.log(Function instanceof Function) // true  console.log(Function instanceof Object) // true  function Foo() {
} console.log(Object instanceof Foo) // false

在这里插入图片描述

3.1.6 原型面试题
/* 测试题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() // b is not a function F.a() // a() F.b() // b() console.log(f) console.log(Object.prototype) console.log(Function.prototype)

3.2 执行上下文与执行上下文栈

3.2.1 变量提升与函数提升
  • 变量声明提升
    • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
    • 值: undefined
  • 函数声明提升
    • 通过function声明的函数, 在之前就可以直接调用
    • 值: 函数定义(对象)
  • 问题: 变量提升和函数提升是如何产生的?
/* 面试题 : 输出 undefined */  var a = 3  function fn () {
console.log(a) var a = 4 } fn() // undefined console.log(b) //undefined 变量提升 fn2() //可调用 函数提升 // fn3() //不能 变量提升 var b = 3 function fn2() {
console.log('fn2()') } var fn3 = function () {
console.log('fn3()') }
3.2.2 执行上下文
  • 代码分类(位置)
    • 全局代码
    • 函数(局部)代码
  • 全局执行上下文
    • 在执行全局代码前将window确定为全局执行上下文
    • 对全局数据进行预处理
      • var定义的全局变量==>undefined, 添加为window的属性
      • function声明的全局函数==>赋值(fun), 添加为window的方法
      • this==>赋值(window)
    • 开始执行全局代码
// 全局执行上下文  console.log(a1, window.a1) // undefined undefined  window.a2() // a2()  console.log(this) // window  var a1 = 3  function a2() {
console.log('a2()') } console.log(a1) // 3
  • 函数执行上下文
    • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
    • 局部数据进行预处理
      • 形参变量==>赋值(实参)==>添加为执行上下文的属性
      • arguments==>赋值(实参列表), 添加为执行上下文的属性
      • var定义的局部变量==>undefined, 添加为执行上下文的属性
      • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
      • this==>赋值(调用函数的对象)
    • 开始执行函数体代码
// 函数(局部)执行上下文  function fn (a1){
console.log(a1) // 2 console.log(a2) // undefined a3() // a3() console.log(this) // window console.log(arguments) // 2,3 伪数组 var a2 = 3 function a3(){
console.log('a3()') } } fn(2,3)
3.2.3 执行上下文栈
  • 全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  • 函数执行上下文创建后, 将其添加到栈中(压栈)
  • 当前函数执行完后,将栈顶的对象移除(出栈)
  • 所有的代码执行完后, 栈中只剩下window

在这里插入图片描述

// 1. 进入全局执行上下文  var a = 10  var bar = function (x) {
var b = 5 // 3. 进入foo执行上下文 foo(x + b) } var foo = function (y) {
var c = 5 console.log(a + c + y) } // 2. 进入bar函数执行上下文 bar(10)
3.2.4 执行上下文面试题
console.log('gb: '+ i) // i为undefinedvar i = 1foo(1)function foo(i) {
if (i == 4) {
return } console.log('fb:' + i) foo(i + 1) //递归调用: 在函数内部调用自己 console.log('fe:' + i)}console.log('ge: ' + i)## 测试题1: 先执行变量提升, 再执行函数提升function a() {
}var aconsole.log(typeof a) // 'function'## 测试题2:if (!(b in window)) {
var b = 1}console.log(b) // undefined## 测试题3:var c = 1function c(c) {
console.log(c) var c = 3}c(2) // 报错

3.3 作用域与作用域链

3.3.1 作用域
  • 理解
    • 就是一块"地盘", 一个代码段所在的区域
    • 它是静态的(相对于上下文对象), 在编写代码时就确定了
  • 分类
    • 全局作用域
    • 函数作用域
    • 没有块作用域(ES6有了)
  • 作用
    • 隔离变量不同作用域下同名变量不会有冲突
/*  //没块作用域if(true) {  var c = 3}console.log(c)*/var a = 10,  b = 20function 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
3.3.2 作用域与执行上下文
  • 区别1
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  • 区别2
    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  • 联系
    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域
var a = 10,b = 20function 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 100bar(200) // bar() 1000 20 300 400 200}fn(10) // fn() 100 20 300 10
3.3.3 作用域链
  • 理解
    • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
    • 查找变量时就是沿着作用域链来查找的
  • 查找一个变量的查找规则
    • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
    • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
    • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
var a = 1function fn1() {
var b = 2 function fn2() {
var c = 3 console.log(c) // 3 console.log(b) // 2 console.log(a) // 1 console.log(d) // d is not defined } fn2()}fn1()
3.3.4 作用域面试题
var x = 10;function fn() {
console.log(x); // 10}function show(f) {
var x = 20; f();}show(fn); // 10
var fn = function () {
console.log(fn)}fn() // fn函数var obj = {
fn2: function () {
console.log(fn2) // 找不到fn2 //console.log(this.fn2) // 能找到fn2 }}obj.fn2() // fn2 is not defined

3.4 闭包

3.4.1 闭包引入
3.4.2 闭包理解
  • 如何产生闭包?
    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  • 闭包到底是什么?
    • 使用chrome调试查看
    • 理解一: 闭包是嵌套的内部函数(绝大部分人)
    • 理解二: 包含被引用变量(函数)的对象(极少数人)
    • 注意: 闭包存在于嵌套的内部函数中
  • 产生闭包的条件?
    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
function fn1 () {
var a = 2 var b = 'abc' function fn2 () {
//执行函数定义就会产生闭包(不用调用内部函数) console.log(a) } fn2()}fn1() // 2function fun1() {
var a = 3 var fun2 = function () {
console.log(a) }}fun1()
3.4.3 常见的闭包
  • 将函数作为另一个函数的返回值
  • 将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值function fn1() {
var a = 2 function fn2() {
a++ console.log(a) } return fn2}var f = fn1()f() // 3f() // 4// 2. 将函数作为实参传递给另一个函数调用function showDelay(msg, time) {
setTimeout(function () {
alert(msg) }, time)}showDelay('atguigu', 2000)
3.4.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() // 1f() // 0
3.4.5 闭包的生命周期
  • 产生
    • 嵌套内部函数定义执行完时就产生了(不是在调用)
  • 死亡
    • 嵌套的内部函数成为垃圾对象时
function fn1() {
//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了) var a = 2 function fn2 () {
a++ console.log(a) } return fn2}var f = fn1()f() // 3f() // 4f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
3.4.6 闭包的应用——自定义JS模块
  • 定义JS模块

    • 具有特定功能的js文件
    • 所有的数据和功能都封装在一个函数内部(私有的)
    • 只向外暴露一个包含n个方法的对象或函数
    • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
  • 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 }})()
3.4.7 闭包的缺点及解决
  • 缺点
    • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
    • 容易造成内存泄露
  • 解决
    • 能不用闭包就不用
    • 及时释放
function fn1() {
var arr = new Array[100000] function fn2() {
console.log(arr.length) } return fn2}var f = fn1()f()f = null //让内部函数成为垃圾对象-->回收闭包
3.4.8 内存溢出与内存泄露
  • 内存溢出
    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  • 内存泄露
    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露
      • 意外的全局变量
      • 没有及时清理的计时器或回调函数
      • 闭包
// 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
3.4.9 闭包面试题
//代码片段一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,0var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2var c = fun(0).fun(1)c.fun(2)c.fun(3)//undefined,0,1,1

4. 面向对象

4.1 对象简介

  • 对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性

4.2 对象的分类

  • 内建对象
    • ES 标准中定义的对象,在任何的 ES 的实现中都可以使用
    • 比如:Math String Number Boolean Function Object...
  • 宿主对象
    • JS 的运行环境提供的对象,目前来讲主要指由浏览器提供的对象
    • 比如 BOM DOM
  • 自定义对象
    • 由开发人员自己创建的对象

4.3 对象的属性名和属性值

  • 属性名
    • 对象的属性名不强制要求遵守标识符的规范
    • 但还是尽量按照标识符的规范去做
  • 如果要使用特殊的属性名不能采用 . 的方式来操作
    • 需要使用另一种方式
    • 语法:对象["属性名"] = 属性值
    • 读取时也需要采用这种方式
  • 使用[]这种形式去操作属性,更加的灵活
  • []中可以直接传递一个变量,这样变量值是多少就会读取那个属性
var obj = new Object()obj.name = 'zs'obj.var = 'hello'console.log(obj.var)obj["123"] = 789var n = 'nihao'console.log(obj["123"])
  • 属性值
    • JS 对象的属性值,可以是任意的数据类型
    • 甚至也可以是一个对象
  • in 运算符
    • 通过该运算符可以检查一个对象中是否含有指定的属性
      • 如果有则返回 true, 没有则返回 false
    • 语法
      • "属性名" in 对象
obj.test = trueobj.test = nullobj.test = undefined// 创建一个对象var obj2 = new Object()obj2.name = 'zs'// 将 obj2 设置为 obj 的属性obj.test = obj2console.log(obj.test.name) // zsconsole.log(obj.test2) // undefined// 检查 obj 中是否含有 test2 属性console.log("test2", in obj) // false

4.4 对象字面量

  • 使用对象字面量,可以在创建对象时,直接指定对象中的属性
  • 语法:{属性名: 属性值,属性名: 属性值...}
    • 对象字面量的属性名可以加引号也可以不加,建议不加
    • 如果要使用一些特殊的名字,则必须加引号
    • 属性名和属性值是一组一组的名值对结构
    • 名和值之间使用:连接,多个名值对之间使用,隔开
    • 如果一个属性之后没有其他的属性了,就不要写,
// 创建一个对象var obj = new Object()// 使用对象字面量来创建一个对象var obj = {
}// console.log(typeof obj) // objectobj.name = 'zs'// console.log(obj.name)var obj2 = {
name: 'zs', age: 23, gender: '男', test: {
name: 'zd'}}console.log(obj2.test)

5. 面向对象高级

5.1 对象创建模式

5.1.1 Object 构造函数模式
  • Object构造函数模式
    • 套路: 先创建空Object对象, 再动态添加属性/方法
    • 适用场景: 起始时不确定对象内部数据
    • 问题: 语句太多
/*一个人: name:"Tom", age: 12 */// 先创建空Object对象var p = new Object()p = {
} //此时内部数据是不确定的// 再动态添加属性/方法p.name = 'Tom'p.age = 12p.setName = function (name) {
this.name = name}//测试console.log(p.name, p.age)p.setName('Bob')console.log(p.name, p.age)
5.1.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 }}
5.1.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
5.1.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.1.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)

5.2 继承模式

5.2.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

在这里插入图片描述

5.2.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)
5.2.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)

6. 线程机制与事件机制

6.1 进程与线程

  • 进程
    • 程序的一次执行, 它占有一片独有的内存空间
  • 线程
    • 是进程内的一个独立执行单元
    • 是程序执行的一个完整流程
    • CPU最小基本调度单位

6.3 图解

在这里插入图片描述

6.4 相关知识

  • 进程与线程
    • 应用程序必须运行在某个进程的某个线程上
    • 一个进程中一般至少有一个运行的线程: 主线程进程启动后自动创建
    • 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
    • 一个进程内的数据可以供其中的多个线程直接共享
    • 多个进程之间的数据是不能直接共享的
  • 线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用

6.5 相关问题

6.5.1 何为多进程与多线程?
  • 多进程运行: 一应用程序可以同时启动多个实例运行
  • 多线程: 在一个进程内, 同时有多个线程运行
6.5.2 比较单线程与多线程?
  • 多线程
    • 优点
      • 能有效提升CPU的利用率
    • 缺点
      • 创建多线程开销
      • 线程间切换开销
      • 死锁与状态同步问题
  • 单线程
    • 优点
      • 顺序编程简单易懂
    • 缺点
      • 效率低
6.5.3 JS是单线程还是多线程?
  • js是单线程运行的
  • 但使用H5中的 Web Workers可以多线程运行
6.5.4 浏览器运行是单进程还是多进程?
  • 单进程
    • firefox
    • 老版IE
  • 多进程
    • chrome
    • 新版IE
6.5.5 浏览器运行是单线程还是多线程?
  • 都是多线程

6.2 浏览器内核

6.2.1 什么是浏览器内核?
  • 支持浏览器运行的最核心的程序
6.2.2 不同的浏览器可能不太一样
  • Chrome, Safari: webkit
  • firefox: Gecko
  • IE: Trident
  • 360,搜狗等国内浏览器: Trident + webkit
6.2.3 内核由很多模块组成
  • 主线程
    • js引擎模块 : 负责js程序的编译与运行
    • html,css文档解析模块 : 负责页面文本的解析
    • dom/css模块 : 负责dom/css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制
  • 分线程
    • 定时器模块 : 负责定时器的管理
    • 网络请求模块 : 负责服务器请求(常规/Ajax)
    • DOM事件响应模块 : 负责事件的管理

6.3 定时器引发的思考

  • 定时器真是定时执行的吗?
    • 定时器并不能保证真正定时执行
    • 一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)
  • 定时器回调函数是在分线程执行的吗?
    • 主线程执行的, 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++) {
}}

6.4 JS是单线程的

  • 如何证明js执行是单线程的?
    • setTimeout()的回调函数是在主线程执行的
    • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
  • 为什么js要用单线程模式, 而不用多线程模式?
    • JavaScript的单线程,与它的用途有关
    • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM
    • 这决定了它只能是单线程,否则会带来很复杂的同步问题
  • 代码的分类
    • 初始化代码
    • 回调代码
  • js引擎执行代码的基本流程
    • 先执行初始化代码: 包含一些特别的代码 回调函数(异步执行)
      • 设置定时器
      • 绑定事件监听
      • 发送ajax请求
    • 后面在某个时刻才会执行回调代码
setTimeout(function () {
console.log('timeout 2222') alert('22222222') // 当弹出111111 1秒后弹出2222222}, 2000)setTimeout(function () {
console.log('timeout 1111') alert('1111111') // 当弹出-----1秒后弹出111111}, 1000)setTimeout(function () {
console.log('timeout() 00000')}, 0)function fn() {
console.log('fn()')}fn()console.log('alert()之前')alert('------') //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时console.log('alert()之后')

6.5 浏览器的事件循环模型

  • 所有代码分类
    • 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
    • 回调执行代码(异步代码): 处理回调逻辑
  • js引擎执行代码的基本流程:
    • 初始化代码===>回调代码
  • 模型的2个重要组成部分:
    • 事件(定时器/DOM事件/Ajax)管理模块
    • 回调队列
  • 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
6.5.1 模型原理图

在这里插入图片描述

6.5.2 相关重要概念
  • 执行栈
    • execution stack
    • 所有的代码都是在此空间中执行的
  • 浏览器内核
    • browser core
    • js引擎模块(在主线程处理)
    • 其它模块(在主/分线程处理)
  • 同一个: callback queue
    • 任务队列
      • task queue
    • 消息队列
      • message queue
    • 事件队列
      • event queue
  • 事件轮询
    • event loop
    • 从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
  • 事件驱动模型
    • event-driven interaction model
  • 请求响应模型
    • request-response model
function fn1() {
console.log('fn1()')}fn1()document.getElementById('btn').onclick = function () {
console.log('点击了btn')}setTimeout(function () {
console.log('定时器执行了')}, 2000)function fn2() {
console.log('fn2()')}fn2()

6.6 Web Workers(多线程)

6.6.1 Web Workers 介绍
  • H5规范提供了js分线程的实现, 取名为: Web Workers
  • 我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面
  • 但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质
6.6.2 Web Workers 使用
  • 创建在分线程执行的js文件
var onmessage =function (event){
//不能用函数声明 console.log('onMessage()22'); var upper = event.data.toUpperCase();//通过event.data获得发送来的数据 postMessage( upper );//将获取到的数据发送会主线程}
  • 在主线程中的js中发消息并设置回调
//创建一个Worker对象并向它传递将在新线程中执行的脚本的URLvar worker = new Worker("worker.js");  //接收worker传过来的数据函数worker.onmessage = function (event) {
console.log(event.data); };//向worker发送数据worker.postMessage("hello world");
6.6.3 Web Workers 图解

在这里插入图片描述

6.6.4 相关API
  • Worker: 构造函数, 加载分线程执行的js文件
  • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
  • Worker.prototype.postMessage: 向另一个线程发送消息
6.6.5 应用练习
  • 直接在主线程
  • 使用Worker在分线程
6.6.6 不足
  • worker内代码不能操作DOM(更新UI)
  • 不能跨域加载JS
  • 不是每个浏览器都支持这个新特性

转载地址:http://rdqwi.baihongyu.com/

你可能感兴趣的文章
android camera拍照/录像后查看图片/视频并删除所有内容后自动回到camera预览界面
查看>>
android 图库中对非mp4格式的视频去掉"修剪"功能选项
查看>>
how to disable watchdog
查看>>
android SDIO error导致wifi无法打开或者连接热点异常的问题
查看>>
android USB如何修改Serial Number or SN?
查看>>
android 用svn管理的版本编译出来有问题
查看>>
android 如何用jar包代替java代码编译
查看>>
android 数据连接关闭的情况下如何让彩信发不出去
查看>>
android 编辑彩信,加入几页铃声,预览暂停界面,铃声名字不见了
查看>>
android 在新建短信时,加入名称为","(英文逗号)的联系人时,应用崩溃的修改
查看>>
android 关于LCD背光调节渐变过程引起背光闪烁问题
查看>>
android 保存具有不同前缀的同一号码分别为A和B,用其中一个呼叫,通话记录一直显示另一个联系人名字的问题
查看>>
android 在手机中预置联系人/Service Number
查看>>
android 系统语言为英语时,Contacts联系人名字含有特殊前缀后缀(Dr. Mr. Lt等)时的相关问题处理
查看>>
android 短信下,添加联系人,进入联系人多选界面出现联系人电话号码晃动的问题
查看>>
android 对一个合并后的联系人选择编辑,手机屏幕会缓慢变暗后再进入编辑界面的问题
查看>>
正确学习javascript。困惑的指南
查看>>
SERO幻影社区的背景怎么样?几大主流隐私币种技术分析!
查看>>
SERO目前具备的十大技术特点
查看>>
Neo4j CQL语句学习Day3
查看>>