JavaScript 高级笔记

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) // 10

function fun(){
    
    
	console.log(12)
}
window.fun() // 12
  • 变量声明提前
    • 使用 var 关键字声明的变量,会在所有的代码执行之前被声明
    • 但是如果声明变量时不使用 var 关键字,则变量不会被声明提前
    • 全局作用域中的变量都是全局变量
      • 页面的任意的部分都可以访问的到
console.log(a); // undefined

var 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 undefined
console.log(a) // 10
  • 函数作用域也有声明提前的特性
    • 使用 var 关键字声明的变量,会在函数中所有的代码执行之前被声明
    • 函数声明也会在函数中所有的代码执行之前执行
function fun3(){
    
    
	fun4()
	
	// console.log(a) // undefined
	var a = 32
	
	function fun4(){
    
    
		alert(12)
	}
}
fun3()
  • 在函数中,不使用 var 声明的变量都会成为全局变量
var c = 33

function fun5(){
    
    
	// console.log(c) // undefined
	// var c = 10

	console.log(c) // 33
	
	// d 没有使用 var 关键字,则会设置为全局变量
	d = 100
}

fun5()

// 在全局输出c
console.log(d) // 100
  • 定义形参就相当于在函数作用域中声明了变量
var e = 23
function 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 的原型中添加属性 a
MyClass.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); // true

console.log(mc.a) // 123
mc.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)
// 调用 fun2
fun2()

/*
	使用函数表达式来创建一个函数
	语法:
		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 执行上下文面试题
<!--
1. 依次输出什么?
  gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1
2. 整个过程中产生了几个执行上下文?  5
-->

console.log('gb: '+ i) // i为undefined
var i = 1
foo(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 a
console.log(typeof a) // 'function'

## 测试题2:
if (!(b in window)) {
    
    
  var b = 1
}
console.log(b) // undefined

## 测试题3:
var c = 1
function 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 = 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 
3.3.2 作用域与执行上下文
  • 区别1
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  • 区别2
    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  • 联系
    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域
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
3.3.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) // 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 闭包引入
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  /*
  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>
3.4.2 闭包理解
  • 如何产生闭包?
    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  • 闭包到底是什么?
    • 使用chrome调试查看
    • 理解一: 闭包是嵌套的内部函数(绝大部分人)
    • 理解二: 包含被引用变量(函数)的对象(极少数人)
    • 注意: 闭包存在于嵌套的内部函数中
  • 产生闭包的条件?
    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
function fn1 () {
    
    
  var a = 2
  var b = 'abc'
  function fn2 () {
    
     //执行函数定义就会产生闭包(不用调用内部函数)
    console.log(a)
  }
  fn2()
}
fn1() // 2

function 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() // 3
f() // 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() // 1
f() // 0
3.4.5 闭包的生命周期
  • 产生
    • 嵌套内部函数定义执行完时就产生了(不是在调用)
  • 死亡
    • 嵌套的内部函数成为垃圾对象时
function fn1() {
    
    
  //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
  var a = 2
  function fn2 () {
    
    
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() // 3
f() // 4
f = 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
  }
}
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>

================================================================

  • 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
  }
})()
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>
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,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

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"] = 789
var n = 'nihao'
console.log(obj["123"])
  • 属性值
    • JS 对象的属性值,可以是任意的数据类型
    • 甚至也可以是一个对象
  • in 运算符
    • 通过该运算符可以检查一个对象中是否含有指定的属性
      • 如果有则返回 true, 没有则返回 false
    • 语法
      • "属性名" in 对象
obj.test = true
obj.test = null
obj.test = undefined

// 创建一个对象
var obj2 = new Object()
obj2.name = 'zs'

// 将 obj2 设置为 obj 的属性
obj.test = obj2

console.log(obj.test.name) // zs
console.log(obj.test2) // undefined

// 检查 obj 中是否含有 test2 属性
console.log("test2", in obj) // false

4.4 对象字面量

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

// 使用对象字面量来创建一个对象
var obj = {
    
    }
// console.log(typeof obj) // object

obj.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 = 12
p.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对象并向它传递将在新线程中执行的脚本的URL
var 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 应用练习
  • 直接在主线程
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
  // 1 1 2 3 5 8    f(n) = f(n-1) + f(n-2)
  function fibonacci(n) {
     
     
    return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
  }
  // console.log(fibonacci(7))
  var input = document.getElementById('number')
  document.getElementById('btn').onclick = function () {
     
     
    var number = input.value
    var result = fibonacci(number)
    alert(result)
  }

</script>
  • 使用Worker在分线程
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
  var input = document.getElementById('number')
  document.getElementById('btn').onclick = function () {
     
     
    var number = input.value

    //创建一个Worker对象
    var worker = new Worker('worker.js')
    // 绑定接收消息的监听
    worker.onmessage = function (event) {
     
     
      console.log('主线程接收分线程返回的数据: '+event.data)
      alert(event.data)
    }

    // 向分线程发送消息
    worker.postMessage(number)
    console.log('主线程向分线程发送数据: '+number)
  }
  // console.log(this) // window

</script>
6.6.6 不足
  • worker内代码不能操作DOM(更新UI)
  • 不能跨域加载JS
  • 不是每个浏览器都支持这个新特性

猜你喜欢

转载自blog.csdn.net/qq_43645678/article/details/107879598