js高级学习笔记(详细)

写在前面:最近忙于找实习,所以恶补了一下js高级,温故而知新,收获还是很多的。
图床页出了点问题,笔记一直没有更新,接下来准备将笔记慢慢同步到csdn。

1.基础总结深入

1.数据类型

1.ES6中有8种数据类型:

基本数据类型:

  • String:任意字符串
  • Number:任意的数字
  • boolean:true/false
  • bigInt
  • undefined:undefined
  • null:null
  • Symbol

引用数据类型:

  • Object

普通对象,数组,正则,日期,Math数学函数等都属于Object;

2.判断
  • typeof:返回数据类型的字符串表达,首字母都是小写

可以判断:undefined,数值,字符串,布尔值,symbol(基本数据类型不能判断null,引用数据类型只能判断function)

不能判断:null和object object和array

//举个例子
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'

从上面例子,前6个都是基础数据类型。虽然typeof nullobject,但这只是JavaScript 存在的一个悠久 Bug,不代表null就是引用数据类型,并且null本身也不是对象

所以,nulltypeof之后返回的是有问题的结果,不能作为判断null的方法。如果你需要在 if 语句中判断是否为 null,直接通过===null来判断就好

同时,可以发现引用类型数据,用typeof来判断的话,除了function会被识别出来之外,其余的都输出object

如果我们想要判断一个变量是否存在,可以使用typeof:(不能使用if(a), 若a未声明,则报错)

  • instanceof:判断对象的具体类型

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

使用如下:

object instanceof constructor

object为实例对象,constructor为构造函数

构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数生成的对象

// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false

关于instanceof的实现原理,可以参考下面:

function myInstanceof(left, right) {
    // 这里先用typeof来判断基础数据类型,如果是,直接返回false
    if(typeof left !== 'object' || left === null) return false;
    // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
    let proto = Object.getPrototypeOf(left);
    while(true) {                  
        if(proto === null) return false;
        if(proto === right.prototype) return true;//找到相同原型对象,返回true
        proto = Object.getPrototypeof(proto);
    }
}

也就是顺着原型链去找,直到找到相同的原型对象,返回true,否则为false

  • ===
可以判断:undefined,null

Code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!--
1. 分类
  * 基本(值)类型
    * String: 任意字符串
    * Number: 任意的数字
    * boolean: true/false
    * undefined: undefined
    * null: null
  * 对象(引用)类型
    * Object: 任意对象
    * Function: 一种特别的对象(可以执行)
    * Array: 一种特别的对象(数值下标, 内部数据是有序的)
2. 判断
  * typeof:
    * 可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
    * 不能判断: null与object  object与array
  * instanceof:
    * 判断对象的具体类型
  * ===
    * 可以判断: undefined, null
-->
    <script>
        // 1.基本数据类型
        // typeof 返回数据类型的字符串表达
        var a
        console.log(a, typeof a, typeof a === 'undefined', a === undefined);//undefined 'undefined' true true
        console.log(undefined === 'undefined');//false
        console.log(undefined === 'undefined');//false
        a = 4
        console.log(typeof a === 'number');//true
        a = 'abc'
        console.log(typeof a === 'String');//false
        a = true
        console.log(typeof a === 'boolean');//true
        a = null
        console.log(typeof a, a === null);//object true
        console.log('****************************************************');

        // 2.对象
        var b1 = {
            b2: [1, 'abc', console.log],
            b3: function () {
                console.log('b3');
                return function () {
                    return 'return的函数'
                }
            }
        }

        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.b3, typeof b1.b3 === 'function');//function true
        console.log(typeof b1.b2[2], typeof b1.b2[2] === 'function');//function true
        b1.b2[2]('hello')//hello
        console.log(b1.b3()());//return的函数 
    </script>
</body>

</html>
3.typeof和instanceof的区别

typeofinstanceof都是判断数据类型的方法,区别如下:

  • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
  • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
  • typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断

可以看到,上述两种方法都有弊端,并不能满足所有场景的需求

如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]”的字符串

Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.call({})  // 同上结果,加上call也ok
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"

了解了toString的基本用法,下面就实现一个全局通用的数据类型判断方法

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {    // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
  }
  // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); 
}

使用如下

getType([])     // "Array" typeof []是object,因此toString返回
getType('123')  // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null)   // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined)   // "undefined" typeof 直接返回
getType()            // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判断,因此首字母小写
getType(/123/g)      //"RegExp" toString返回
4.相关问题
  1. undefined与null的区别?

    • undefined:代表定义了,未赋值

    • null:定义了,只是值为null

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

    • 初始赋值:表明将要赋值为对象
    • 结束前:让对象成为垃圾对象(被垃圾回收器回收)释放内存
  3. 严格区别变量类型与数据类型?

    • 数据的类型
      • 基本类型
      • 对象类型
    • 变量的类型(变量内存值的类型)
      • 基本类型:保存的就是基本类型的数据
      • 引用类型:保存的是地址值
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>01_相关问题</title>
</head>

<body>
  <script type="text/javascript">
    // 实例:实例对象
    // 类型:类型对象
    function Person(name, age) {//构造函数:类型对象
      this.name = name
      this.age = age
    }
    var p = new Person('tom', 12);//根据类型创建的实例对象
    // Person('jake',12) 语法上可以这样调用,但是原则上不行

    // 1. undefined与null的区别?
    var a
    console.log(a);//undefined
    a = null
    console.log(a);//null

    // 2. 什么时候给变量赋值为null呢?
    // 起始
    var b = null//初始赋值为null,表明将要赋值为对象
    // 确定对象就赋值
    b = ['hello', 12]
    // 最后  
    //b = null//让b指向的对象称为垃圾对象(被垃圾回收器回收),释放内存

    // 3.严格区别变量类型与数据类型?
    //c 保存的是function的地址值:地址值指向function
    var c = function () {

    }

    console.log(typeof c) // 'function'
  </script>
</body>

</html>

2.数据,变量,内存

切记:变量赋值是将内容赋值(拷贝)一份给另一个值

1. 什么是数据?
  • 存储在内存中代表特定信息的‘东西’,本质上是0101
  • 数据的特点:可传递,可运算
  • 一切皆数据
  • 内存中所有操作的目标:数据
    • 算术运算
    • 逻辑运算
    • 内存中所有操作的目标:数据
      • 算术运算
      • 逻辑运算
      • 赋值
      • 运算函数
2.什么是内存?
  • 内存条通电以后产生的可存储数据的空间(临时的)

  • 内存的产生和死亡:内存条(电路板)>通电>产生内存空间==>存储数据==>处理数据==>断电==>内存空间和数据都消失

  • 内存的空间是临时的,而硬盘的空间是持久的

  • **分配内存:**声明变量和函数或创建对象时,js引擎会自动为此分配一定大小的内存来存放对应的数据

  • **释放内存:**清空内存中的数据,标识内存可以再分配使用(内存释放就不能复用)

    • 自动释放:栈空间的局部变量
    • 垃圾回收器回调:堆空间的垃圾对象
  • 一块小内存的2个数据:

    • 内部存储的数据
    • 地址值
  • 内存分类:

    • 栈:全局变量/局部空间
    • 堆:对象
3.什么是变量?
  • 可变化的量,由变量名和变量值组成
  • 每个变量都对应一块小内存,变量名用来查找对应的内存,变量值就是内存中的值
4.内存,数据, 变量三者之间的关系
  • 内存用来存储数据的空间
  • 变量是内存的标识,
1. 什么是数据?
  - 存储在内存中代表特定信息的‘东西’,本质上是0101
  - 数据的特点:可传递,可运算
  - 一切皆数据
  - 内存中所有操作的目标:数据
    - 算术运算
    - 逻辑运算
    - 内存中所有操作的目标:数据
      - 算术运算
      - 逻辑运算
      - 赋值
      - 运算函数
2. 什么是内存?
  - 内存条通电以后产生的可存储数据的空间(临时的)
  - 内存的产生和死亡:内存条(电路板)==>通电==>产生内存空间==>存储数据==>处理数据==>断电==>内存空间和数据都消失
  - 一块小内存的2个数据:
    - 内部存储的数据
    - 地址值
  - 内存分类:
    - 栈:全局变量/局部空间
    - 堆:对象
3. 什么是变量?
  - 可变化的量,由变量名和变量值组成
  - 每个变量都对应一块小内存,变量名用来查找对应的内存,变量值就是内存中的值
4. 内存,数据, 变量三者之间的关系
  - 内存用来存储数据的空间
  - 变量是内存的标识,
5.堆和栈

JavaScript中的变量分为基本类型和引用类型。

基本类型就是保存在栈内存中的简单数据段,而引用类型指的是那些保存在堆内存中的对象。

栈:

  • 基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。

堆:

  • 引用类型,值的大小不固定。栈内存中存放地址,地址指向堆内存中的对象,是按引用访问的。

如下图所示:栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。
这样,当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。

image-20220503201025790

6.深拷贝和浅拷贝:

基本类型数据保存在在栈内存中

引用类型数据对象保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。

浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;

1.浅拷贝

什么是浅拷贝?

把数组/对象 第一层的值,复制到新的数组/对象中。

这个数据有着原始数据属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址。即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

JavaScript中,存在浅拷贝的现象有:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制

实现方法:

在这里插入图片描述

区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制;

2.深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • 手写循环递归

_.cloneDeep():

const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

jQuery.extend()

在这里插入图片描述

const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

JSON.stringify()

var obj={
   name:'大雄',
   age:21
};
var obj1=JSON.parse(JSON.stringify(obj));

但是这种方式存在弊端,会忽略undefinedsymbol函数

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

循环递归
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

总结:

浅拷贝和深拷贝都创建出一个新的对象,但在复制对象属性的时候,行为就不一样

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

前提为拷贝类型为引用类型的情况下:

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
7.相关问题
问题1:var a = xxx, a内存中到底保存的是什么?
  <!--
  问题: var a = xxx, a内存中到底保存的是什么?
    - xxx是基本数据类型,保存的就是这个数据
    - xxx是对象,保存的是对象的地址值
    - xxx是一个变量,保存的xxx的内存内容(可能是基本数据,也可能是地址值)
-->
  <script type="text/javascript">
    var a = 3 //a保存的是3
    a = function () {
      //a保存的是对象的地址值 函数是一个对象
    }
    var b = 'abc'//b保存的是
    a = b //a保存的是b的值
    b = {}
    a = b //a保存的是b的地址
  </script>
问题2:关于引用变量赋值问题
  <!--
  关于引用变量赋值问题
    - 1: n个引用变量指向同一个对象,通过一个变量修改对象内部数据,其他所有变量看到的是修改之后的数据
    - 2: 2个引用变量指向同一个对象,其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象
-->
  <script type="text/javascript">
    // 1:
    // var obj1 = { name: 'Tom' }
    // var obj2 = obj1 //将obj1的内容保存给obj2,内容是obj1的地址值
    // obj1.name = 'jack'
    // console.log(obj2.name);//打印jack
    // var obj3 = obj2
    // obj1.name = 'abc'
    // console.log(obj3.name);//abc

    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

    // 2:
    var a = { age: 12 }
    var b = a
    a = { name: 'BOB', age: 13 }//此时a的地址指向改变(开辟了新的空间),b的没有改变
    console.log(b, a);//{age: 12} {name: 'BOB', age: 13}

    function fn2(obj) {//a作为形参传入 此时a,obj指向同一个地址
      obj = { age: 15 }//obj改变地址指向,a没改变,此时a和obj没有关系
    }
    console.log(a);//{name: 'BOB', age: 13}
  </script>
问题3:在js调用函数时传递变量参数时,是值传递还是引用传递
  <!--
问题: 在js调用函数时传递变量参数时, 是值传递还是引用传递
    - 理解1:都是值(基本值/地址值)传递,值可能是基本值或地址值
    - 理解2:可能是值传递,也可能是引用传递()
-->
  <script type="text/javascript">
    // 1.
    var a = 3
    function fn(a) {
      a = a + 1//操作的是局部变量a
      return a
    }
    fn(a)//这里传的是值:3
    console.log(fn(a));//4
    console.log(a);//3 

    // 2.
    function fn2(obj){//
      console.log(obj.name);
      obj.name='A'
    }
    var obj = {name:'Tom'}
    fn2(obj)//将obj的内容(地址值)传给形参
    console.log(obj);//A
  </script>
问题4:JS引擎如何管理内存?
  <!--
问题: JS引擎如何管理内存?
1. 内存生命周期
    - 分配小空间,得到他的使用权
    - 存储数据,可以反复进行操作
    - 释放小空间,
2. 释放内存
    - 局部变量:函数执行完之后释放
    - 对象:成为垃圾对象==>垃圾回收器回收

-->
  <script type="text/javascript">
    var a = 3
    var obj = {}//此时存放三个内存空间
    obj = null//对象被回收,全局变量obj的内存没有被释放

/* ***************************************** */

    function fn() {
      var b = 4//局部变量b在函数被调用时创建,在函数执行完毕之后被释放(注意:释放不等于回收)
    }
    fn();//b自动释放,b指向的对象在某一时刻被垃圾回收器回收
  </script>

3.对象

  <!--
1. 什么是对象?
    - 多个数据的封装体
    - 用来保存多个数据的容器
    - 一个对象代表现实中的一个事务
2. 为什么要用对象?
    - 统一管理多个对象
3. 对象的组成
    - 属性:属性名(字符串)和属性值(任意类型)组成
    - 方法:一种特别的的属性(属性值是函数)
4. 如何访问对象内部数据?
    - .属性名      编码简单,又是不能用
    - ['属性名':]     编码麻烦,但是能通用
-->
  <script type="text/javascript">
    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);//Bob 23
  </script>

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

  <!--
问题: 什么时候必须使用['属性名']的方式?
    - 属性名包含特殊字符如:-   空格
    - 属性名不确定

-->
  <script type="text/javascript">
    var p = {}
    // 1.给p对象添加一个属性:conten-type:'text/json'
    // p.conten-type = 'text/json'//不能用
    p['conten-type'] = 'text/json'
    console.log(p['conten-type']);//text/json
    
    // 2.变量名不确定
    var propName = 'myAge'
    var value = 18
    // p.propName = value //不能用
    p[propName]=value
    console.log(p[propName]);//18
    console.log(p);//{conten-type: 'text/json', myAge: 18}
  </script>

4.函数

1.函数基础
<!--
1. 什么是函数?
  * 实现特定功能的n条语句的封装体
  * 只有函数是可以执行的, 其它类型的数据不能执行
2. 为什么要用函数?
  * 提高代码复用
  * 便于阅读交流
3. 如何定义函数?
  * 函数声明
  * 表达式
4. 如何调用(执行)函数?
  * test(): 直接调用
  * obj.test(): 通过对象调用
  * new test(): new调用
  * test.call/apply(obj): 临时让test成为obj的方法进行调用 (改变this指向)
-->
<script type="text/javascript">
  /*
  编写程序实现以下功能需求:
    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)

</script>
2.补充
函数声明和函数表达式的区别

详解Javascript 函数声明和函数表达式的区别 - JackWang-CUMT - 博客园 (cnblogs.com)

call()和apply()
<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			function fun(a,b) {
				console.log("a = "+a);
				console.log("b = "+b);
				//alert(this);
			}
			
			var obj = {
				name: "obj",
				sayName:function(){
					alert(this.name);
				}
			};

			/*
			 * call()和apply()
			 * 	- 这两个方法都是函数对象的方法,需要通过函数对象来调用
			 * 	- 当对函数调用call()和apply()都会调用函数执行
			 * 	- 在调用call()和apply()可以将一个对象指定为第一个参数
			 * 		此时这个对象将会成为函数执行时的this
			 * 	- call()方法可以将实参在对象之后依次传递
			 * 	- apply()方法需要将实参封装到一个数组中统一传递
			 * 
			 * 	- this的情况:
			 * 		1.以函数形式调用时,this永远都是window
			 * 		2.以方法的形式调用时,this是调用方法的对象
			 * 		3.以构造函数的形式调用时,this是新创建的那个对象
			 * 		4.使用call和apply调用时,this是指定的那个对象
			 */
			
			//fun.call(obj,2,3);
			fun.apply(obj,[2,3]);

			

			var obj2 = {
				name: "obj2"
			};

			/*fun.apply();
			fun.call();
			fun();*/

			//fun.call(obj);
			//fun.apply(obj);

			//fun();
			
			//obj.sayName.apply(obj2);
		</script>
	</head>

	<body>
	</body>

</html>
3.回调函数
<body>
  <button id="btn">测试点击事件</button>
  <!--
1. 什么函数才是回调函数?
  1). 你定义的
  2). 没有调用
  3). 但是最终执行了(在某个时刻或某个条件下)
2. 常见的回调函数?
  - dom事件回调函数==>发生事件的dom元素
  - 定时器回调函数==>window

  - ajax请求回调函数
  - 生命周期回调函数
-->
  <script type="text/javascript">
    document.getElementById('btn').onclick = function () {//dom事件回调函数
      alert(this.innerHTML)
    }

    // 定时器
      // 超时定时器
      // 循环定时器
    setTimeout(function () {
      alert('2s后弹出')
    }, 2000)
  </script>
</body>
4.IIFE(立即执行函数)
  <!--
1. 理解
  * 全称: Immediately-Invoked Function Expression(立即执行函数)
2. 作用
  - 隐藏实现
  - 不会污染外部匿名空间(一般都是全局)
  - y
-->
  <script type="text/javascript">
    // 匿名函数自调用 立即执行
    (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);
      }
      function test2(){
        console.log(++a);
      }
      // 相当于向外暴露一个全局函数 如果不暴露函数就隐藏了
      window.$ = function(){//给window对象绑定$属性,值为一个函数
        return{
          test:test
        }
      }
    })()

    // 这里$是一个函数,$执行后返回的是一个对象,此对象有一个test方法
    $().test()//2
  </script>
5.this指向问题
<body>
<!--
1. this是什么?
  - 任何函数本质上都是通过某个对象来调用的,没有直接指定就是window对象
  - 所有函数内部都有一个变量this
  - 它的值是调用函数的当前对象
2. 如何确定this的值?
  - test():window
  - p.test():p
  - new test():新创建的对象
  - p.call(obj):obj
-->

<script type="text/javascript">
  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();
</script>
</body>

5.补充

1.js中关于语句分号的问题
<!--
1. js一条语句的后面可以不加分号
2. 是否加分号是编码风格问题, 没有应该不应该,只有你自己喜欢不喜欢
3. 在下面2种情况下不加分号会有问题
  * 1.小括号开头的前一条语句
  * 2.中方括号开头的前一条语句
4. 解决办法: 在行首加分号
5. 强有力的例子: vue.js库
6. 知乎热议: https://www.zhihu.com/question/20298345
-->
<script type="text/javascript">
  // 1.
  var a = 3
  ;(function () {

  })()
  /*
   不加分号编译器会错误理解:Uncaught TypeError: 3 is not a function
   var a = 3(function () {

   })();
  */

  // 2.
  var b = 4
  ;[1, 3].forEach(function () {

  })
  /*
  不加分号编译器会错误理解:Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')
   var b = 4[3].forEach(function () {

   })
   */

</script>

2.函数高级

1.原型与原型链

1.原型

函数的prototype属性(图):

image-20220504095546162

函数的prototype属性指向该函数的原型对象,此原型对象的constructor又指向该构造函数(即构造函数和原型对象有相互引用的关系),constructor是可以更改的

  <!--
1. 函数的prototype属性(图)
  * 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  * 原型对象中有一个属性constructor, 它指向函数对象
2. 给原型对象添加属性(一般都是方法)
  * 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
-->
  <script type="text/javascript">
    /* 1. */
    // 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 
    console.log(Date.prototype, typeof Date.prototype);
    function Fun() {

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

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

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

原型的内存结构(图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHJzQghI-1652681297448)(http://division-222.gitee.io/typora-user-images/image-20220504151512587.png)]

<body>
<!--
1. 每个函数function都有一个prototype,即显式原型
2. 每个实例对象都有一个__proto__,可称为隐式原型
3. 对象的隐式原型的值为其对应构造函数的显式原型的值
4. 内存结构(图)
5. 总结:
  * 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  * 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  * 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
-->
<script type="text/javascript">
  function Fn(){

  }
  // 1.每个函数function都有一个prototype,即显示原型
  console.log(Fn.prototype);
  // 2.每个实例对象都有一个__proto__,可称为隐式原型
  // 创建实例对象
  var fn = new Fn()
  console.log(fn.__proto__);
  // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  console.log(Fn.prototype===fn.__proto__);//true
  // 给原型添加方法
  Fn.prototype.test = function(){
    console.log('test');
  }
  // 通过实例调用原型的方法
  fn.test();
</script>
</body>
3.原型链

图解:

image-20220504163151593

<!--
1. 原型链(图解)
  * 访问一个对象的属性时,
    * 先在自身属性中查找,找到返回
    * 如果没有, 再沿着__proto__这条链向上查找, 找到返回
    * 如果最终没找到, 返回undefined
  * 别名: 隐式原型链
  * 作用: 查找对象的属性(方法)
2. 构造函数/原型/实体对象的关系(图解)
3. 构造函数/原型/实体对象的关系2(图解)
-->
<script type="text/javascript">
  // 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()

</script>

image-20220504160342769

image-20220504160415051

4.原型链补充
<script>
  /*
  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
</script>
5.原型链_属性问题
  <!--
1. 读取对象的属性值时: 会自动到原型链中查找
2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身
-->
  <script type="text/javascript">
    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 
    console.log();//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)

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

  </script>
6.Object和Function的关系:

结论:

- Function的__proto__指向自己的prototype:	Function.__proto__===Function.prototype
- Object原型的隐式原型为null:	Object.prototype.__proto__==null
- Object的__proto__指向Function的prototype: Object的Object的__proto__===Function.__proto__===Function.prototype
/* 
* 对应名称
* - prototype:原型 | 显式原型
* - __proto__:原型链(链接点) |  隐式原型

* 从属关系
  prototype -> 函数的一个属性:对象{}
  __proto__ -> 对象Object的一个属性:对象{}
  对象的__proto__保存着该对象的构造函数的prototype
*/

function Test(){}
console.log(Test.prototype);

const test = new Test()
console.log(test.__proto__);

console.log(Test.prototype===test.__proto__);//true

// 原型也是一个对象,也有__proto_-
console.log(Test.prototype.__proto__ === Object.prototype);//true
// 最顶层Object的原型的__proto__为null
console.log(Object.prototype.__proto__);//null



// Function和Object:既是函数也是对象
// Test是由Function构造出来的  const Test = new Function()
// 因此Test的__proto__指向Function的prototype 
console.log(Test.__proto__===Function.prototype);//true
// 同样的道理  Function也是一个函数,也是被构造函数Function构造出来的  因此Function也应该有自己的__proto__和prototype
console.log(Function.__proto__);
console.log(Function.prototype);
console.log(Function.__proto__===Function.prototype);//true   所有Function的__proto__指向Function的prototype

const obj = {}
// obj是由new Object()而来的,const obj = new Object()
// Object也是一个Function
console.log(typeof Object);//function
// 因此 Object也是由Function构造而来的,const Object = new Function()
// 所以Object的__proto__指向Function的prototype
console.log(Object.__proto__===Function.prototype);//true
// 又因为Function.__proto__===Function.prototype,所以Object.__proto__===Function.__proto__
console.log(Object.__proto__===Function.__proto__);//true
  • Function与函数

Function是JavaScript提供的一种引用类型,通过Function类型创建Function对象。
在JavaScript中,函数也是以对象的形式存在的,每个函数都是一个Function对象。

//字面量方式创建函数

var fun =function () {

    console.log(100)

};

//函数声明方式创建函数

function fn () {

    console.log(200)

};

/*     创建Funtion类型的对象

*       var 函数名 = new Function('参数',''函数体)*/

var f = new Function('a','console.log(a)');

f(2);//以函数方式调用

记住这个图:

img

JavaScript原型链以及Object和Function之间的关系_T Lai的博客-CSDN博客_function和object原型之间的关系

[JavaScript原型链以及Object,Function之间的关系 - 一抹夏忧☆ - 博客园 (cnblogs.com)](https://www.cnblogs.com/web-record/p/9661804.html#:~:text=Object也是个函数,而任何函数都是Function的实例对象,比如Array,String,当然Object也包括在内,它也是Function的实例,即: 同时,Function是个对象,它的原型是Function.proto,指向Function.prototype,并且这个原型链向上继续指向Object.prototype,即: 到底谁先谁后,谁主谁次?,关于这一点网上已经有很多解释,这里首先陈述我的观点,是先有Function,它是主;后有Object,是次。 以下是我个人的理解,可能并不准确。 要看待这个问题,我们可以从JavaScript创造世界开始想象: 我们知道Object.prototype是原型链的root。)

7.探索instanceof

案例:

<!--
1. instanceof是如何判断的?
  * 表达式: A instanceof B
            A是实例对象有隐式原型__propto,B是构造函数有显式原型prototype 
  * 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
2. Function是通过new自己产生的实例
3. Object
-->
<script type="text/javascript">

  /*
  案例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
</script>

牢记这些图!

image-20220504172800981

image-20220504172843912

8.面试题
  • 测试题1
    var A = function () {

    }
    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

图解:

image-20220504184047107

  • 测试题2
  var F = function(){};
  Object.prototype.a = function(){
    console.log('a()')
  };
  Function.prototype.b = function(){
    console.log('b()')
  };
  var f = new F();
  f.a()
  f.b()
  F.a()
  F.b()

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

1.变量提升和函数提升

  <!--
1. 变量声明提升
  * 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  * 值: undefined
2. 函数声明提升
  * 通过function声明的函数, 在之前就可以直接调用
  * 值: 函数定义(对象)
3. 问题: 变量提升和函数提升是如何产生的?
-->
  <script type="text/javascript">
    /* 
    面试题:输出undefined
    */
    var a = 3
    function fn() {
      console.log(a)
      var a = 4
    }
    fn()//undefined
  /*
    相当于如下代码:
    var a = 3
    function fn(){
      var a 
      console.log(a)
      a = 4
    }
    fn()
    */


    console.log(b);//undefined
    fn2()//可以调用 函数提升 必须是函数声明的方式
    fn3()//error:fn3 is not a function

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

    var fn3 = function fn3(){
      console.log('fn3()');
    }
  </script>
2.执行上下文

**执行上下文的次数采用n+1原则:**n为调用函数的次数,1为window

  <!--
1. 代码分类(位置)
  * 全局代码
  * 函数代码
2. 全局执行上下文
  * 在执行全局代码前将window确定为全局执行上下文
  * 对全局数据进行预处理
    * var定义的全局变量==>undefined, 添加为window的属性
    * function声明的全局函数==>赋值(fun), 添加为window的方法
    * this==>赋值(window)
  * 开始执行全局代码
3. 函数执行上下文
  * 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
  * 对局部数据进行预处理
    * 形参变量==>赋值(实参)==>添加为执行上下文的属性
    * arguments==>赋值(实参列表), 添加为执行上下文的属性
    * var定义的局部变量==>undefined, 添加为执行上下文的属性
    * function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    * this==>赋值(调用函数的对象)
  * 开始执行函数体代码
-->
  <script type="text/javascript">
    // 全局执行上下文
    // console.log(a1,window.a1)
    // window.a2()
    // console.log(this)

    // var a1 = 3
    // function a2(){
    //   console.log('a2()');
    // }
    console.log('**********************');
    // 函数执行上下文
    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)
  </script>
3.执行上下文和执行上下文栈
<!--
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window
-->
<script type="text/javascript">
  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)
</script>

过程图:

image-20220504202302600

4.面试题
1.上下文栈:后进先出
  <!--
1. 依次输出什么?
  gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1
2. 整个过程中产生了几个执行上下文?  5
-->
  <script type="text/javascript">
    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)
  </script>

上下文栈:后进先出

image-20220504204353225

2.变量提升函数提升的优先级

变量提升函数提升的优先级:函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。

<script type="text/javascript">

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

</script>
    console.log(a);    // f a() {console.log(10)}
    a()
    var a = 3;

    function a() {
      console.log('a被调用') //a被调用
    }
    console.log(a)   //3
    a = 6;
    console.log(a());  //a is not a function;

    /*
    相当于:
      var a = function(){
        console.log(10);
      }
      var a;

      console.log(a);// f a(){console.log(10);}
      a() //函数不会被变量声明覆盖
      a = 3 //函数会被会被变量赋值之后覆盖
      console.log(a)   //3
      a = 6;
      console.log(a());  //a is not a function;  
    */
3.上下文和变量提升
  /*
   测试题3:
   */
  if (!(b in window)) {
    var b = 1
  }
  console.log(b) // undefined

    /*
     测试题4:
     */
    var c = 1
    function c(c) {
      console.log(c)
      var c = 3
    }
    c(2) // 报错

    /*
    相当于:
      var c
      function c(c) {
      console.log(c)
    }
      c = 1
      c(2)//报错
    */

3.作用域与作用域链

1.基本理解

**判断作用域采用n+1原则:**n为定义的n个函数,1全局作用域

image-20220505094355055

在当前作用域中找不到某一变量时,向外层作用域寻找(就近原则)

<!--
1. 理解
  * 就是一块"地盘", 一个代码段所在的区域
  * 它是静态的(相对于上下文对象), 在编写代码时就确定了
2. 分类
  * 全局作用域
  * 函数作用域
  * 没有块作用域(ES6有了)
3. 作用
  * 隔离变量,不同作用域下同名变量不会有冲突
-->
<script type="text/javascript">
/*
    //没有块作用域   
    if(true){
    var c = 3
  }
  console.log(c);//3 
*/
2.作用域与执行上下文
  • 区别1

    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文是在全局作用域确定之后, js代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  • 区别2

    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 上下文环境是动态的。函数执行上下文在调用函数时创建, 函数调用结束时上下文环境就会被自动释放
  • 联系

    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域
<script type="text/javascript">
  var a = 10,
    b = 20
  function fn(x) {
    var a = 100,
      c = 300;
    console.log('fn()', a, b, c, x)//fn() 100 20 300 10
    function bar(x) {
      var a = 1000,
        d = 400
      console.log('bar()', a, b, c, d, x)//bar() 1000 20 300 400 100 | bar() 1000 20 300 400 200
    }

    bar(100)
    bar(200)
  }
  fn(10)
</script>
3.作用域链
<!--
1. 理解
  * 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  * 查找变量时就是沿着作用域链来查找的
2. 查找一个变量的查找规则
  * 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  * 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  * 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
-->
<script type="text/javascript">
  var a = 1
  function fn1() {
    var b = 2
    function fn2() {
      var c = 3
      console.log(c)
      console.log(b)
      console.log(a)
      console.log(d)
    }
    fn2()
  }
  fn1()
</script>
4.面试题
  • 题1
  var x = 10;
  function fn() {
    console.log(x);
  }
  function show(f) {
    var x = 20;
    f();
  }

// 作用域在函数定义时(编码时)就确定了
  show(fn);//10

  • 题2
  var fn = function () {
    console.log(fn)
  }
  fn()//输出fn函数

  var obj = {
    fn2: function () {
     console.log(fn2)
     //console.log(this.fn2)
    }
  }
  obj.fn2()//报错:fn2 is not defined

调用fn2时先在函数内部作用域找,没有fn2,再去外部作用域找,注意js没有块级作用域,es6之后使用let可以创建块级作用域,因此这里的对象的{}不是作用域,直接到全局作用域中找fn2,报错。

  var obj = {
    fn2: function () {
     //console.log(fn2)
     console.log(this.fn2)
    }
  }
  obj.fn2()//打印函数fn2

4.闭包

鲁迅说:js有两大神兽,原型和闭包

1.闭包的基本理解
  • 如何产生闭包?

    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包,创建函数
  • 闭包到底是什么?

    • 使用chrome调试查看
    • 理解一: 闭包是嵌套的内部函数(绝大部分人)
    • 理解二: 包含被引用变量(函数)的对象(极少数人)
    • 注意: 闭包存在于嵌套的内部函数中,在创建函数对象的时候产生
  • 产生闭包的条件?

    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
<!--
1. 如何产生闭包?
  * 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
2. 闭包到底是什么?
  * 使用chrome调试查看
  * 理解一: 闭包是嵌套的内部函数(绝大部分人)
  * 理解二: 包含被引用变量(函数)的对象(极少数人)
  * 注意: 闭包存在于嵌套的内部函数中
3. 产生闭包的条件?
  * 函数嵌套
  * 内部函数引用了外部函数的数据(变量/函数)
-->
<script type="text/javascript">
  function fn1 () {
    var a = 2
    var b = 'abc'
    function fn2 () { //执行函数s就会产生闭包(不用调用内部函数)
      console.log(a)
    }
    fn2()
  }
  fn1()

</script>
2.常见的闭包
  <!--
1. 将函数作为另一个函数的返回值
2. 将函数作为实参传递给另一个函数调用
-->
  <script type="text/javascript">
    // 1. 将函数作为另一个函数的返回值
    function fn1() {
      var a = 2
      function fn2() {
        a++
        console.log(a)
      }
      return fn2
    }
    var f = fn1()//将函数fn2赋给f

    f() //3
    /* 
    a是局部变量,在函数调用结束后就要被释放
    但是因为产生了闭包,a没有释放,还可以被f2引用
    */
    f() //4

    // 2. 将函数作为实参传递给另一个函数调用
    // 产生了闭包,setTimeout第一个参数为函数,该函数引用了外部的msg变量
    function showDelay(msg,time){
      setTimeout(function(){
        alert(msg)
      },time)
    }
    showDelay('hello',2000)//hello
  </script>
3.闭包的作用
  • 1.使函数内部的变量在函数执行完后,仍然存在内存中(延长了局部变量的生命周期)
  • 2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)
<!--

问题:
  1. 函数执行完后, 函数内部声明的局部变量是否还存在?
     - 一般是不存在的,存在于闭包中的变量才可能存在
  2. 在函数外部能直接访问函数内部的局部变量吗? 
     - 不能,但是通过闭包可以让外部操作局部变量
-->
<script type="text/javascript">
  function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
      // return a
    }
    function fn3() {
      a--
      console.log(a)
    }
    return fn3
  }
  var f = fn1()//执行完此句,a存在(因为存在于闭包里),fn2成为垃圾对象,fn3被释放,但是fn3的地址值返回给了f
  f() // 1
  f() // 0
</script>
4.闭包的生命周期
  <!--
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时
-->
  <script type="text/javascript">
    function fn1() {
      // 此时闭包就已经产生了(函数声明提升,内部函数对象已经创建)
      var a = 2
      function fn2() {
        a++
        console.log(a);
      }
      return fn2
    }
    var f = fn1()
    f()//3
    f()//4
    f = null //闭包死亡(包含的闭包的函数对象成为垃圾对象)
  </script>
5.闭包的应用(定义js模块)

定义JS模块:

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包信n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能

myMoudle1.js

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

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

<!--
闭包的应用2 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包信n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
<script src="./myModule.js"></script>
<script>
  var moudule = myModule()
  moudule.doSomething()
</script>

myMoudle2.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
  }
})()
  <!--
闭包的应用2 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包信n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
  <script src="./myModule2.js"></script>
  <script>
    myModule2.doSomething()
  </script>
6.内存溢出与内存泄露:
  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>_内存溢出与内存泄露</title>
</head>

<body>
  <script type="text/javascript">
    // 1. 内存溢出
    var obj = {}
    for (var i = 0; i < 10000; i++) {
      obj[i] = new Array(10000000)
      console.log('-----')
    }

    // 2. 内存泄露
    // 1.意外的全局变量
    function fn() {
      a = new Array(10000000)
      console.log(a)
    }
    fn()

    // 2.没有及时清理的计时器或回调函数
    var intervalId = setInterval(function () { //启动循环定时器后不清理
      console.log('----')
    }, 1000)
    // clearInterval(intervalId)

    //3. 闭包
    function fn1() {
      var a = 4
      function fn2() {
        console.log(++a)
      }
      return fn2
    }
    var f = fn1()
    f()
  // f = null

  </script>
</body>

</html>
7.闭包的缺点(内存溢出与内存泄露)

1.缺点:

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 容易造成内存泄露

2.解决:

  • 能不用闭包就不用
  • 及时释放

2. 解决
  * 能不用闭包就不用
  * 及时释放
-->
<script type="text/javascript">
  function fn1() {
    var arr = new Array[100000]
    function fn2() {
      console.log(arr.length)
    }
    return fn2
  }
  var f = fn1()
  f()

  f = null //让内部函数成为垃圾对象-->回收闭包
</script>
8.面试题
<script type="text/javascript">
  //代码片段一:没有产生闭包
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());  //输出the window
  /* 
  getNameFunc是通过object调用的。object.getNameFunc()得到的是getNameFunc内部的一个函数。
  这里就相当于直接调用这个内部的函数,因此this指向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
  /* 
   getNameFunc是通过object2调用的,此时this指向onject2,将this赋值给that。
   object2.getNameFunc()得到的是getNameFunc内部的一个函数。
   返回object2.name2
  */
</script>
9.终极面试题

3.面向对象高级

1.对象创建模式

1.Object构造函数模式
  • 套路: 先创建空Object对象, 再动态添加属性/方法
  • 适用场景: 起始时不确定对象内部数据
  • 问题: 语句太多
  /*
  一个人: name:"Tom", age: 12
   */
  var person = new Object()
  person.name = 'Tom'
  person.age = 12
  person.setName = function(name){
    this.name = name 
  }

  //测试
  person.setName = 'Jack'
  console.log(person.name,person.age);
2.对象字面量模式
  • 套路: 使用{}创建对象, 同时指定属性/方法

  • 适用场景: 起始时对象内部数据是确定的

  • 问题: 如果创建多个对象, 有重复代码

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

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

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

3.工厂模式
  • 套路: 通过工厂函数动态创建对象并返回

  • 适用场景: 需要创建多个对象

  • 问题: 对象没有一个具体的类型, 都是Object类型

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

    return obj
  }

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

  // p1/p2是Object类型

  function createStudent(name, price) {
    var obj = {
      name: name,
      price: price
    }
    return obj
  }
  var s = createStudent('张三', 12000)
  // s也是Object
4.自定义构造函数模式
  • 套路: 自定义构造函数, 通过new创建对象

  • 适用场景: 需要创建多个类型确定的对象

  • 问题: 每个对象都有相同的数据, 浪费内存

<!--
方式四: 自定义构造函数模式
  * 套路: 自定义构造函数, 通过new创建对象
  * 适用场景: 需要创建多个类型确定的对象
  * 问题: 每个对象都有相同的数据(如果set方法), 浪费内存
-->
<script type="text/javascript">
  function Person(name,age){
    this.name = name
    this.age = age
    this.setName = function(name){
      this.name = name
    }
  }
  var p1 = new Person('Tom',12)
  p1.setName('Jack')
  console.log(p1.name,p1.age);//Jack 12
  console.log(p1 instanceof Person);//true



  function Student(name,price){
    this.name = name 
    this.price = price
  }
  var s = new Student('Bob',1212)
  console.log(s.name,s.price);//Bob 1212
  console.log(s instanceof Student);//true
  </script>
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);

image-20220505155735438

2.继承模式

1.原型链继承
<!--
方式1: 原型链继承
  1. 套路
    1. 定义父类型构造函数
    2. 给父类型的原型添加方法
    3. 定义子类型的构造函数
    4. 创建父类型的对象赋值给子类型的原型
    5. 将子类型原型的构造属性设置为子类型
    6. 给子类型原型添加方法
    7. 创建子类型的对象: 可以调用父类型的方法
  2. 关键
    1. 子类型的原型为父类型的一个实例对象
-->
<script type="text/javascript">
  //父类型
  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
</script>

image-20220505165201403

2.借用构造函数继承(假继承)
  <!--
方式2: 借用构造函数继承(假的)
1. 套路:
  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造
2. 关键:
  1. 在子类型构造函数中通用call()调用父类型构造函数
-->
  <script type="text/javascript">
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    function Student(name, age, price) {
      Person.call(this, name, age)//相当于:this.Person(name,age)
      this.price = price
    }

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

  </script>
3.组合继承

总结:使用原型链继承方法,使用call继承属性

<!--
方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用call()借用父类型构建函数初始化相同属性
-->
<script type="text/javascript">
  function Person(name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }

  function Student(name, age, price) {
    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)

</script>

4.线程机制与事件机制

1.进程与线程

  • 进程(process)

    	程序的一次执行, 它占有一片独有的内存空间
    	可以通过windows任务管理器查看进程
    
  • 线程(thread)

    	是进程内的一个独立执行单元
    	是程序执行的一个完整流程
    	是CPU的最小的调度单元
    
  • 图解:

image-20220507224431202

  • 相关知识:

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

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

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

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

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

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

  • 相关问题:

相关问题:
1.何为多进程与多线程?
多进程运行: 一应用程序可以同时启动多个实例运行
多线程: 在一个进程内, 同时有多个线程运行
2.比较单线程与多线程?

​ 多线程

  	优点:能有效提升CPU的利用率
  	缺点:
  		创建多线程开销
  		线程间切换开销
  		死锁与状态同步问题
  单线程
  	优点:顺序编程简单易懂
  	缺点:效率低

3.JS是单线程还是多线程?
js是单线程运行的
但使用H5中的 Web Workers可以多线程运行
4.浏览器运行是单线程还是多线程?
都是多线程运行的
5.浏览器运行是单进程还是多进程?
有的是单进程:firefox,老版IE
有的是多进程:chrome,新版IE

6.如何查看浏览器是否是多进程运行的呢?

​ 任务管理器–>进程

2.浏览器内核

浏览器内核是支撑浏览器运行的最核心的程序,不同的浏览器可能不一样。

如:Chrome, Safari : webkit,firefox : Gecko,IE : Trident

360,搜狗等国内浏览器: Trident + webkit

内核由很多模块组成

  • 主线程

    • js引擎模块 : 负责js程序的编译与运行
    • html,css文档解析模块 : 负责页面文本的解析
    • DOM/CSS模块 : 负责dom/css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
  • 分线程

    • 定时器模块 : 负责定时器的管理
    • DOM事件响应模块 : 负责事件的管理
    • 网络请求模块 : 负责ajax请求

3.定时器引发的思考

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

  • 定时器并不能保证真正定时执行

  • 一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)

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

  • 在主线程执行的, js是单线程的

3. 定时器是如何实现的?

  • 事件循环模型(后面讲)
    document.querySelector('#btn').onclick = function () {
      var start = Date.now()
      console.log('启动定时器前');
      setTimeout(function () {
        console.log('定时器执行了', Date.now() - start);
      }, 200)
      console.log('启动定时器后');
      // 做一个长时间的工作   时间间隔变长
      for (let i = 0; i < 1000000000; i++) {
        
      }
    }

4.js是单线程的

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

  • setTimeout()的回调函数是在主线程执行的

  • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

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

  • JavaScript的单线程,与它的用途有关。

  • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。

  • 这决定了它只能是单线程,否则会带来很复杂的同步问题

3. 代码的分类:

  • 初始化代码

  • 回调代码

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

  • 先执行初始化代码: 包含一些特别的代码 回调函数(异步执行)

    • 设置定时器(定时器的回调函数先不执行)

    • 绑定监听(事件触发才执行)

    • 发送ajax请求

  • 后面在某个时刻才会执行回调代码

    setTimeout(function () {
      console.log('timeout 2222')
      alert('222222222')
    }, 5000)
    setTimeout(function () {
      console.log('timeout 1111')
      alert('1111111')
    }, 4000)

    setTimeout(function () {
      console.log('timeout 0000')

    }, 0)

    function fn() {
      console.log('fn()')
    }
    fn()

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

5.事件循环模型

2分钟了解 JavaScript Event Loop | 面试必备

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_事件循环模型</title>
</head>
<body>
<button id="btn">测试</button>
<!--
1. 所有代码分类
  * 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
  * 回调执行代码(异步代码): 处理回调逻辑
2. js引擎执行代码的基本流程:
  * 初始化代码===>回调代码
3. 模型的2个重要组成部分:
  * 事件(定时器/DOM事件/Ajax)管理模块
  * 回调队列
4. 模型的运转流程
  * 执行初始化代码, 将事件回调函数交给对应模块管理
  * 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
  * 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
-->
<script type="text/javascript">
  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()
</script>
</body>
</html>

6.H5的Web Worker 多线程

  • H5规范提供了js分线程的实现, 取名为: Web Workers

  • 相关API

    • Worker: 构造函数, 加载分线程执行的js文件
    • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
    • Worker.prototype.postMessage: 向另一个线程发送消息
  • 不足

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

**场景:**编写一个程序,实现:输入n,计算出斐波那契数列的第n个(从1开始),1、1、2、3、5、8、13、21…

<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)  //递归调用
  }

  var input = document.getElementById('number')
  document.getElementById('btn').onclick = function () {
    var number = input.value
    var result = fibonacci(number)
    alert(result)
  }

这个程序不难实现,但是递归有一个问题,当n很大时,浏览器会卡顿,导致不能在浏览器实行别的操作,知道函数执行完。

可以用Web Workers实现多线程,将一些大计算量的代码交给Web Workers运行而不冻结用户界面

<body>
<!--
1. H5规范提供了js分线程的实现, 取名为: Web Workers
2. 相关API
  * Worker: 构造函数, 加载分线程执行的js文件
  * Worker.prototype.onmessage: 用于接收另一个线程的回调函数
  * Worker.prototype.postMessage: 向另一个线程发送消息
3. 不足
  * worker内代码不能操作DOM(更新UI)
  * 不能跨域加载JS
  * 不是每个浏览器都支持这个新特性
-->

<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')//参数是Url
    // 绑定接收消息的监听
    worker.onmessage = function (event) {
      console.log('主线程接收分线程返回的数据: '+event.data)
      alert(event.data)
    }

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

</script>
</body>

多线程的不足:

  1. 不能跨域加载JS
  2. worker内代码不能访问DOM(更新UI)
  3. 不是每个浏览器都支持这个新特性
测试 ```

6.H5的Web Worker 多线程

  • H5规范提供了js分线程的实现, 取名为: Web Workers

  • 相关API

    • Worker: 构造函数, 加载分线程执行的js文件
    • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
    • Worker.prototype.postMessage: 向另一个线程发送消息
  • 不足

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

**场景:**编写一个程序,实现:输入n,计算出斐波那契数列的第n个(从1开始),1、1、2、3、5、8、13、21…

<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)  //递归调用
  }

  var input = document.getElementById('number')
  document.getElementById('btn').onclick = function () {
    var number = input.value
    var result = fibonacci(number)
    alert(result)
  }

这个程序不难实现,但是递归有一个问题,当n很大时,浏览器会卡顿,导致不能在浏览器实行别的操作,知道函数执行完。

可以用Web Workers实现多线程,将一些大计算量的代码交给Web Workers运行而不冻结用户界面

<body>
<!--
1. H5规范提供了js分线程的实现, 取名为: Web Workers
2. 相关API
  * Worker: 构造函数, 加载分线程执行的js文件
  * Worker.prototype.onmessage: 用于接收另一个线程的回调函数
  * Worker.prototype.postMessage: 向另一个线程发送消息
3. 不足
  * worker内代码不能操作DOM(更新UI)
  * 不能跨域加载JS
  * 不是每个浏览器都支持这个新特性
-->

<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')//参数是Url
    // 绑定接收消息的监听
    worker.onmessage = function (event) {
      console.log('主线程接收分线程返回的数据: '+event.data)
      alert(event.data)
    }

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

</script>
</body>

多线程的不足:

  1. 不能跨域加载JS
  2. worker内代码不能访问DOM(更新UI)
  3. 不是每个浏览器都支持这个新特性
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值