wallleap / myblogs

blog, Issue
https://myblog.wallleap.cn
4 stars 1 forks source link

JavaScript高级 #43

Open wallleap opened 4 years ago

wallleap commented 4 years ago

title: 深入学习 JS 高级 date: 2020-04-15 19:33 updated: 2020-04-15 19:33 cover: //cdn.wallleap.cn/img/pic/cover/202302GKk2ms.jpg category: 技术杂谈 tags:

在 JS 基础上深入学习这个语言

一、基础总结

1、数据类型

分类

var obj = {
  name: 'TOM',
  age: 12
}
function test(){
  var a = 3
}
var arr = [3, 'abc']
arr[1]

判断

//1. 基本
// typeof返回数据类型的字符串表达
var a
// undefined
console.log(a, typeof a, typeof a==='undefined',a===undefined )  // undefined 'undefined' true true
console.log(undefined==='undefined') // false
// Number Sting Boolean
a = 4
console.log(typeof a==='number') // true
a = "abc123"
console.log(isNaN(a)) // true
a = 'string iiii'
console.log(typeof a==='string') // true
a = true
console.log(typeof a==='boolean') // true
// null
a = null
console.log(typeof a, a===null) // 'object' true

//2. 对象
var b1 = {
 b2: [1, 'abc', console.log],
 b3: function () {
  console.log('b3') // b3
  return function () {
   return 'funb1'
  }
 }
}
// Object Array Function
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'
// Function
console.log(typeof b1.b3==='function') // true
console.log(typeof b1.b2[2]==='function') // true

// 细节问题
b1.b2[2](4) // 4
console.log(b1.b3()()) // funb1
/*
b1.b3() ---> b3当前的function
b1.b3()() ---> return的function
*/

实例

实例:实例对象

类型:类型对象

function Person(name, age){ // 构造函数 类型
  this.name = name
  this.age = age
} 
var p = new Person('Tom', 12) // 根据类型创建的实例对象

其他问题

1、undefined 与 null 的区别?

var a
console.log(a)  // undefined
a = null
console.log(a) // null

2、什么时候给变量赋值为 null 呢?

//起始
var b = null  // 初始赋值为 null,表明将要赋值为对象
//确定对象就赋值
b = ['luwang', 12]
//最后
b = null // 让 b 指向的对象成为垃圾对象(被垃圾回收器回收)
// b = 2

3、严格区别变量类型与数据类型?

var c = function () {

}
console.log(typeof c) // 'function'

2、数据、变量与内存

1、什么是数据?

2、什么是内存?

3、什么是变量?

4、内存、数据、变量三者之间的关系

var age = 18
console.log(age)

var obj = {name: 'Tom'}
console.log(obj.name)

function fn () {
  var obj = {name: 'Tom'}
}

var a = 3
var b = a + 2

5、关于赋值与内存的问题

问题:var a = xxx,a 内存中到底保存的是什么?

var a = 3
a = function () {

}
var b = 'abc'
a = b
b = {}
a = b

image-20200815100153040

6、关于引用变量赋值问题

var obj1 = {name: 'Tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age)  // 12
function fn (obj) {
  obj.name = 'A'
}
fn(obj1)
console.log(obj2.name) //A

var a = {age: 12}
var b = a
a = {name: 'BOB', age: 13}
b.age = 14
console.log(b.age, a.name, a.age) // 14 Bob 13
function fn2 (obj) {
  obj = {age: 15}
}
fn2(a)
console.log(a.age) // 13

7、关于数据传递问题

问题:在 js 调用函数时传递变量参数时,是值传递还是引用传递

var a = 3
function fn (a) {
  a = a +1
}
fn(a)
console.log(a)  // 3

function fn2 (obj) {
  console.log(obj.name)
}
var obj = {name: 'Tom'}
fn2(obj)  // Tom

8、JS 引擎如何管理内存

问题:JS 引擎如何管理内存?

var a = 3
var obj = {}
obj = undefined

function fn () {
  var b = {}
}

fn() // b是自动释放, b所指向的对象是在后面的某个时刻由垃圾回收器回收

3、对象

1、什么是对象?

2、为什么要用对象?

3、对象的组成

4、如何访问对象内部数据?

var p = {
  name: 'Tom',
  age: 12,
  setName: function (name) {
    this.name = name
  },
  setAge: function (age) {
    this.age = age
  }
}

p.setName('Bob')
p['setAge'](23)
console.log(p.name, p['age'])

问题:什么时候必须使用 ['属性名'] 的方式?

  1. 属性名包含特殊字符: -空格

  2. 属性名不确定(是变量)

var p = {}
//1. 给p对象添加一个属性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])

//2. 属性名不确定
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value
console.log(p[propName])

4、函数

1、什么是函数?

2、为什么要用函数?

3、如何定义函数?

4、如何调用(执行)函数?

/*
编写程序实现以下功能需求:
  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)

function fn1 () { //函数声明
  console.log('fn1()')
}
var fn2 = function () { //表达式
  console.log('fn2()')
}

fn1()
fn2()

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

5、回调函数

  1. 什么函数才是回调函数? 1) 你定义的 2) 你没有调 3) 但最终它执行了(在某个时刻或某个条件下)
  2. 常见的回调函数?

    • DOM 事件回调函数 ==> 发生事件的 DOM 元素
    • 定时器回调函数 ===> window
    • ajax 请求回调函数
    • 生命周期回调函数
<button id="btn">测试点击事件</button>
<script type="text/javascript">
  document.getElementById('btn').onclick = function () { // dom事件回调函数
    alert(this.innerHTML)
  }
  //定时器
    // 超时定时器
    // 循环定时器
  setTimeout(function () { // 定时器回调函数
    alert('到点了'+this)
  }, 2000)
  /*var a = 3
  alert(window.a)
  window.b = 4
  alert(b)*/
</script>

6、IIFE

1、理解

2、作用

(function () { //匿名函数自调用
  var a = 3
  console.log(a + 3)
})()
var a = 4
console.log(a)
;(function () {
  var a = 1
  function test () {
    console.log(++a)
  }
  window.$ = function () { // 向外暴露一个全局函数
    return {
      test: test
    }
  }
})()
$().test() // 1.

7、函数中的 this

1、this 是什么?

2、如何确定 this 的值?

函数调用的时候确定

箭头函数没有自己的 this,箭头函数中所谓的 this,其实就是外层代码块的 this

function Person(color) {
  console.log(this)
  this.color = color;
  this.getColor = function () {
    console.log(this)
    return this.color;
  };
  this.setColor = function (color) {
    console.log(this)
    this.color = color;
  };
}

Person("red"); //this是谁? window

var p = new Person("yello"); //this是谁? p

p.getColor(); //this是谁? p

var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj

var test = p.setColor;
test(); //this是谁? window

function fun1() {
  function fun2() {
    console.log(this);
  }

  fun2(); //this是谁? window
}
fun1();

二、函数高级

超级重点,两大神兽:原型和闭包

1、原型和原型链

(1) 原型

函数的 prototype 属性

给原型对象添加属性(一般都是方法)

// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {//alt + shift +r(重命名rename)

}
console.log(Fun.prototype)  // 默认指向一个Object空对象(没有我们的属性)

// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)

//给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function () {
  console.log('test()')
}
var fun = new Fun()
fun.test()

(2) 显式原型与隐式原型

  1. 每个函数 function 都有一个 prototype,即显式原型(属性)
  2. 每个实例对象都有一个 __proto__,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 内存结构

总结:

//定义构造函数
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) 原型链

原型链

构造函数/原型/实体对象的关系

var o1 = new Object();
var o2 = {};

构造函数/原型/实体对象的关系2

function Foo(){  }
// var Foo = new Function()
// Function = new Function()
// 所有函数的__proto__都是一样的

// 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
/*
1. 所有函数都是Function的实例(包含Function)
*/
console.log(Function.__proto__===Function.prototype)
/*
1. Object的原型对象是原型链尽头
  */
console.log(Object.prototype.__proto__) // null

原型继承

原型属性问题

function Fn() {

}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a, fn1)

var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a, fn2)

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.setName = function (name) {
  this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1)

var p2 = new Person('Jack', 12)
p2.setName('Cat')
console.log(p2)
console.log(p1.__proto__===p2.__proto__) // true

(4) 探索 instanceof

instanceof 是如何判断的?

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

(5) 面试题

/*
测试题1
  */
function A () {

}
A.prototype.n = 1

var b = new A()

A.prototype = {
  n: 2,
  m: 3
}

var c = new A()
console.log(b.n, b.m, c.n, c.m) // 1 undefined 2 3

/*
  测试题2
  */
function F (){}
Object.prototype.a = function(){
  console.log('a()')
}
Function.prototype.b = function(){
  console.log('b()')
}

var f = new F()
f.a() // a()
// f.b() // Uncaught TypeError: f.b is not a function
F.a() // a()
F.b() // b()
console.log(f) // F {}
console.log(Object.prototype) // {a: ƒ, constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, …}
console.log(Function.prototype) // ƒ () { [native code] }

2、执行上下文与执行上下文栈

(1) 变量提升与函数提升

变量声明提升

函数声明提升

问题:变量提升和函数提升是如何产生的?

/*
面试题:输出 undefined
*/
var a = 3
function fn () {
  console.log(a)
  var a = 4
}
fn() // undefined

console.log(b) //undefined  变量提升
fn2() //可调用  函数提升 fn2()
// fn3() //不能  变量提升 Uncaught TypeError: fn3 is not a function

var b = 3
function fn2() {
  console.log('fn2()')
}

var fn3 = function () {
  console.log('fn3()')
}

(2) 执行上下文

代码分类(位置)

全局执行上下文

// 全局声明的变量和函数都会在window中:window.a1、window.a2()
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

函数执行上下文

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) 执行上下文栈

  1. 在全局代码执行前,JS 引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
  3. 在函数执行上下文创建后,将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后,栈中只剩下 window
var a = 10
var bar = function (x) {
  var b = 5
  foo(x + b)
}
var foo = function (y) {
  var c = 5
  console.log(a + c + y)
}
bar(10)
// bar(10)

console.log('gb: '+ i)
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. 依次输出什么?
  gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1
2. 整个过程中产生了几个执行上下文?  5   */

(4) 面试题

测试题 1:先执行变量提升,再执行函数提升(先找 varfunction xxx(){}

function a() {}
var a
console.log(typeof a) // function

测试题 2:先提出去,window 中有 b,且未赋值

if (!(b in window)) {
  var b = 1
}
console.log(b) // undefined

测试题 3:针对变量名同名或函数名同名的情况。如果声明了同名的函数其定义会被后者覆盖,声明了同名的变量其值也会被后者覆盖

var c = 1
function c(c) {
  console.log(c)
  var c = 3
}
console.log(c) // 1
c(2) // 报错 Uncaught TypeError: c is not a function

// 再看一个
// 声明阶段
function x(){ //函数声明
 //console.log(5)此句会被下句代码覆盖
    console.log(3)
}
var x;//变量声明,因为x已经声明过了,此处不进行声明(忽略)
//执行阶段
console.log(x) // ƒ x(){//函数声明//console.log(5);此句会被下句代码覆盖console.log(3);}
console.log(x()) // 3
x=1 
x=100 //x的值被覆盖
console.log(x) // 100
console.log(x()) // Uncaught TypeError: x is not a function

3、作用域与作用域链

(1) 作用域

理解:

分类:

/*  //没块作用域
if(true) {
  var c = 3
}
console.log(c) // 3 有块作用域则报错*/

作用:

例如把如下代码分割

var a = 10,
  b = 20
function fn(x) {
  var a = 100,
    c = 300;
  console.log('fn()', a, b, c, x) 
  function bar(x) {
    var a = 1000,
      d = 400
    console.log('bar()', a, b, c, d, x)
  }
  bar(100) // bar() 1000 20 300 400 100
  bar(200) // bar() 1000 20 300 400 200
}
fn(10) // fn() 100 20 300 10

(2) 作用域与执行上下文

区别 1:

区别 2:

联系:

还是上面那串代码

(3) 作用域链

理解:

查找一个变量的查找规则:

  1. 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入 2
  2. 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入 3
  3. 再次执行 2 的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
var a = 1
function fn1() {
  var b = 2
  function fn2() {
    var c = 3
    console.log(c) // 3
    console.log(b) // 2
    console.log(a) // 1
    console.log(d) // Uncaught ReferenceError: d is not defined
  }
  fn2()
}
fn1()

(4) 面试题

var x = 10;
function fn() {
  console.log(x);
}
function show(f) {
  var x = 20;
  f();
}
show(fn); // 10

var fn = function () {
  console.log(fn)
}
fn() // f(){console.log(fn)}

var obj = {
  fn2: function () {
    console.log(fn2)
    //console.log(this.fn2) // ƒ () {// console.log(fn2) console.log(this.fn2)}
  }
}
obj.fn2() // Uncaught ReferenceError: fn2 is not defined

4、闭包

(1) 引入实例

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  /*// 无论点哪个都是 第4个 ——点击的时候循环已经执行完了
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    btn.onclick = function () {
      alert('第'+(i+1)+'个')
    }
  }*/
  /*
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    //将btn所对应的下标保存在btn上,利用这中方式可以实现
    btn.index = i
    btn.onclick = function () {
      alert('第'+(this.index+1)+'个')
    }
  }*/

  //利用闭包
  for (var i = 0,length=btns.length; i < length; i++) {
    (function (j) {
      var btn = btns[j]
      btn.onclick = function () {
        alert('第'+(j+1)+'个')
      }
    })(i)
  }
</script>

(2) 理解闭包

如何产生闭包?

闭包到底是什么?

产生闭包的条件?

function fn1 () {
  var a = 2
  var b = 'abc'
  function fn2 () { // 声明函数形式定义函数,会产生闭包
    console.log(a)  // 使用了外部函数的变量
  }
  // fn2()
}
fn1()

function fun1() {
  var a = 3
  var fun2 = function () { // 声明变量形式定义函数,不会产生闭包
    console.log(a)
  }
}
fun1()

(3) 常见的闭包

  1. 将函数作为另一个函数的返回值
  2. 将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值
function fn1() {
  var a = 2
  function fn2() {
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() // 3
f() // 4
/* f调用的是内部函数,fn1外部函数执行了一次,产生1个闭包 */

// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
  setTimeout(function () {
    alert(msg)
  }, time)
}
showDelay('test', 2000)

(4) 闭包的作用

  1. 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
function fn1() {
  var a = 2
  function fn2() {
    a++
    console.log(a)
    // return a
  }
  function fn3() {
    a--
    console.log(a)
  }
  return fn3
}
var f = fn1()
f() // 1
f() // 0

(5) 闭包的生命周期

  1. 产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
  2. 死亡:在嵌套的内部函数成为垃圾对象时
function fn1() {
  //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
  var a = 2
  function fn2 () {
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null // 闭包死亡(包含闭包的函数对象成为垃圾对象)

(6) 闭包的应用:自定义 JS 模块

模块:

例如:

文件 myModule1.js 就是一个模块

function myModule() {
  // 私有数据
  var msg = 'Hello This is Module1'
  // 操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

在其他文件中引入

<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>

下面这种更好用一点,可以不需要先执行那个函数(IIFE)

myModule2.js

(function () {
  //私有数据
  var msg = 'Hello This is Module1'
  //操作数据的函数
  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>

压缩代码时,会把变量改为 a、b、c 这种的,因此最好这样做

(7) 闭包的缺点及解决

缺点:

解决:

function fn1() {
  var arr = new Array[100000]
  function fn2() {
    console.log(arr.length)
  }
  return fn2
}
var f = fn1()
f()

f = null //让内部函数成为垃圾对象-->回收闭包

补充:内存溢出与内存泄露

(8) 面试题

//代码片段一
var name = "The Window"
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name
    }
  }
}
alert(object.getNameFunc()())  // the window

//代码片段二
var name2 = "The Window"
var object2 = {
  name2 : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name2;
    }
  }
}
alert(object2.getNameFunc()()) // my object

// 三
function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n)
    }
  }
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)//undefined,0,0,0

var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2

var c = fun(0).fun(1)
c.fun(2)
c.fun(3) // undefined,0,1,1

三、面向对象高级

1、对象创建模式

有如下五种方式:

(1) Object 构造函数模式

/*
一个人: name:"Tom", age: 12
  */
// 先创建空Object对象
var p = new Object()
p = {} //此时内部数据是不确定的
// 再动态添加属性/方法
p.name = 'Tom'
p.age = 12
p.setName = function (name) {
  this.name = name
}

//测试
console.log(p.name, p.age)
p.setName('Bob')
console.log(p.name, p.age)

(2) 对象字面量模式

var p = {
  name: 'Tom',
  age: 12,
  setName: function (name) {
    this.name = name
  }
}

//测试
console.log(p.name, p.age)
p.setName('JACK')
console.log(p.name, p.age)

var p2 = {  //如果创建多个对象代码很重复
  name: 'Bob',
  age: 13,
  setName: function (name) {
    this.name = name
  }
}

(3) 工厂模式

function createPerson(name, age) { //返回一个对象的函数===>工厂函数
  var obj = {
    name: name,
    age: age,
    setName: function (name) {
      this.name = name
    }
  }
  return obj
}

// 创建2个人
var p1 = createPerson('Tom', 12)
var p2 = createPerson('Bob', 13)

// p1/p2 是 Object 类型

function createStudent(name, price) {
  var obj = {
    name: name,
    price: price
  }
  return obj
}
var s = createStudent('张三', 12000)
// s 也是 Object

(4) 自定义构造函数模式

//定义类型
function Person(name, age) {
  this.name = name
  this.age = age
  this.setName = function (name) {
    this.name = name
  }
}
var p1 = new Person('Tom', 12)
p1.setName('Jack')
console.log(p1.name, p1.age)
console.log(p1 instanceof Person)

function Student (name, price) {
  this.name = name
  this.price = price
}
var s = new Student('Bob', 13000)
console.log(s instanceof Student)

var p2 = new Person('JACK', 23)
console.log(p1, p2)

方法两者都有,且相同,不需要单独拥有,可放到原型中(方法一般放到原型中)——>

(5) 构造函数+原型的组合模式

function Person(name, age) { //在构造函数中只初始化一般函数
  this.name = name
  this.age = age
}
Person.prototype.setName = function (name) {
  this.name = name
}

var p1 = new Person('Tom', 23)
var p2 = new Person('Jack', 24)
console.log(p1, p2)

2、继承模式

继承,在 JS 中有三种方式:

(1) 原型链继承

套路:

关键:

//父类型
function Supper() {
  this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function () {
  console.log(this.supProp)
}

//子类型
function Sub() {
  this.subProp = 'Sub property'
}

// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型的原型的constructor指向子类型
Sub.prototype.constructor = Sub
Sub.prototype.showSubProp = function () {
  console.log(this.subProp)
}

var sub = new Sub()
sub.showSupperProp()
// sub.toString()
sub.showSubProp()

console.log(sub)  // Sub

(2) 借用构造函数继承

套路:

关键:

function Person(name, age) {
  this.name = name
  this.age = age
}
function Student(name, age, price) {
  Person.call(this, name, age)  // 相当于: this.Person(name, age)
  /*this.name = name
  this.age = age*/
  this.price = price
}

var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)

(3) 组合继承

  1. 利用原型链实现对父类型对象的方法继承

  2. 利用 super() 借用父类型构建函数初始化相同属性

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.setName = function (name) {
  this.name = name
}

function Student(name, age, price) {
  Person.call(this, name, age)  // 为了得到属性
  this.price = price
}
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student // 修正 constructor 属性
Student.prototype.setPrice = function (price) {
  this.price = price
}

var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)

ES 6 中的继承:使用 class 类的 extends 关键字实现继承

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  setName(name) {
    this.name = name
  }
}
class Student extends Person {
  constructor(name, age, price) {
    super(name, age)
    this.price = price
  }
  setPrice(price) {
    this.price = price
  }
}

var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)

四、线程机制与事件机制

1、进程与线程

(1) 进程(process)

(2) 线程(thread)

线程与进程

应用程序必须运行在某个进程的某个线程上

一个进程中至少有一个运行的线程:主线程,进程启动后自动创建

一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的

一个进程内的数据可以供其中的多个线程直接共享

多个进程之间的数据是不能直接共享的

线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用

(3) 相关问题

何为多进程与多线程?

比较单线程与多线程?

JS 是单线程还是多线程?

浏览器运行是单线程还是多线程?

浏览器运行是单进程还是多进程?

2、浏览器内核

3、定时器引发的思考

定时器真是定时执行的吗?

定时器回调函数是在分线程执行的吗?

定时器是如何实现的?

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++) {

  }
}

4、JS 是单线程执行的

如何证明 js 执行是单线程的?

为什么 js 要用单线程模式,而不用多线程模式?

代码的分类:

js 引擎执行代码的基本流程

setTimeout(function () {
  console.log('timeout 2222')
  alert('22222222')
}, 2000)
setTimeout(function () {
  console.log('timeout 1111')
  alert('1111111')
}, 1000)
setTimeout(function () {
  console.log('timeout() 00000')
}, 0)
function fn() {
  console.log('fn()')
}
fn()

console.log('alert()之前')
alert('------') // 暂停当前主线程的执行,同时暂停计时,点击确定后,恢复程序执行和计时
console.log('alert()之后')

5、浏览器的事件循环(轮询)模型

(1) 模型原理图

所有代码分类

js 引擎执行代码的基本流程:

模型的 2 个重要组成部分:

模型的运转流程

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()

(2) 相关重要概念

执行栈 execution stack

浏览器内核 browser core

运行原理图

上面这三个在同一个 callback queue

事件轮询 event loop

事件驱动模型 event-driven interaction model

请求响应模型 request-response model

6、H5 Web Workers(多线程)

(1) 介绍

(2) 相关API

(3) 使用

创建在分线程执行的 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")  

(4) 应用练习

直接在主线程

<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在分线程

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
function fibonacci(n) {
  return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
}

console.log(this)
this.onmessage = function (event) {
  var number = event.data
  console.log('分线程接收到主线程发送的数据: '+number)
  //计算
  var result = fibonacci(number)
  postMessage(result)
  console.log('分线程向主线程返回数据: '+result)
  // alert(result)  alert是window的方法, 在分线程不能调用
  // 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
}

(5) 不足