声明: 个人学习使用,全文手敲, 由于语雀性能问题, 拆分上下文档

1. 底层知识点

1. 数据类型

基本类型在内存中保存key 跟value
引用类型在内存中保存一个地址 AAABBBFFF 然后这个地址指向创建的变量

序列 基本类型: 引用类型:
1 Number Object
2 String Array
3 Boolean Function
4 Null
5 Undefined
6 Symbol
7 BigInt

对象的属性名一定不是引用类型值,默认转为字符串
对象的toString为[object object]

2. 引擎 v8 webkit内核

GO VO AO EC ECStack

  1. 堆栈内存,
    1. 堆:存放引用类型, 函数等。。
    2. 栈: 执行上下文EC 存储基本类型
  2. GO global object 在浏览器端,会把全局对象赋值给window
  3. VO 变量对象 存储当前上下文中的变量
  4. AO 活动的变量对象
  5. JS引擎 想要执行代码,一定会创建执行栈, 然后进栈,执行完一般情况会出栈(存在闭包不会)

3. 变量赋值分为三步 声明及存储

1.创建变量 如果创建不用再次创建
2.创建值:基本值直接在栈中创建
3.让变量与值关联起来 定义(defined);

由于引用类型的值是一个复杂的结构,所以特殊处理 ,
专门为它开辟一个存储对象中的键值对(存储函数中的代码)的内存空间
成为堆内存
所有的堆内存都有一个可被后续查找的16进制地址比如AAAFFF111
后续关联赋值的时候是把堆内存的地址赋值给变量操作

把一个变量赋值为null 空对象指针

  1. let a = {
  2. n: 10
  3. }
  4. let b = a;
  5. b.m = b = {
  6. n: 20
  7. }
  8. var c = {
  9. n: 20,
  10. m: {
  11. n: 20
  12. }
  13. }
  14. console.log(a);
  15. console.log(b);

代码执行全过程
ECStack执行环境栈
全局执行上下文 进栈执行
浏览器window会指向GO global
创建变量,
创建值 如果基本类型直接在栈内创建 引用类型在开辟一个堆内存
赋值 把值关联给变量 指针指向变量

2. 变量提升

ES3/5规范

  1. 判断体合函数体不存在块级上下文, 上下文只有全局和私有
  2. 不论条件是否成立 带function的都要声明+定义

    ES6规范

  3. 存在快级作用域, 大括号{ … } 中出现 let/const/function 都会被认为是块级作用域

  4. 不论条件是否成立, 带function的只提前声明 ,但不会提前赋值
  5. 因为要兼容ES3/6 function如果在全局下声明过, 也在私有下/块级上下文处理过, 会把声明的值映射给全局一份数据, 以此兼容ES3, 但是后面的代码就与全局没有任何关系

    练习题

    1. var a = 12;
    2. if (true) {
    3. /*
    4. * 块级上下文
    5. * function a() {};
    6. */
    7. console.log(a); //=>函数
    8. a = 13;
    9. console.log(a); //=>13
    10. function a() {} //之前对a的操作映射给全局一份 全局a=13
    11. a = 14;
    12. console.log(a); //=>14
    13. }
    14. console.log(a); //=>13

    变量提升知识点.png

    8. 堆栈内存

    创建变量且赋值操作

    1. var a = 1 // 做了以下操作
  6. 创建一个值

    1. 基本类型值直接存储在栈内存中
    2. 引用数据类型的值需要先开辟一个堆内存, 把内存存储进去后把堆内存地址存放到栈,提供变量使用
  7. 创建一个变量(声明:declare) 存储在变量对象中(VO)
  8. 让变量和值进行关联 (指针指向的过程) (定义: defined)

    对象的存储

  9. 对象数据类型:由零到多组键值对(属性名和属性值)组成的

  10. 属性名的类型:

【说法一:属性名类型只能是字符串或者Symbol】
【说法二:属性名类型可以是任何基本类型值,处理中可以和字符串互通】
但是属性名绝对不能是引用数据类型,如果设置引用类型,最后也是转换为字符串处理的

  1. let sy = Symbol('AA');
  2. let x = {
  3. 0: 0
  4. };
  5. let obj = {
  6. 0: 12,
  7. true: 'xxx',
  8. null: 20
  9. };
  10. obj[sy] = '珠峰';
  11. obj[x] = 100; //=> obj['[object Object]']=100 会把对象变为字符串作为属性名
  12. for (let key in obj) {
  13. // FOR IN遍历中获取的属性名都会变为字符串
  14. // 并且无法迭代到属性名是SYMBOL类型的属性
  15. console.log(key, typeof key);
  16. }

连等赋值

  1. 运算符优先级相等时 从右到左
  2. 运算符优先级查看链接
  3. 优先级大的先执行 ```javascript // 从右到左 // 1. 创建一个值12 // 2. b=12 // 3. let a=12 let a = b = 12;

a.x = a = {}; // 相当于a = a.x = {}; // 因为成员访问 a.x 的优先级是很大的,所以不论怎么调换位置,都是先处理a.x={} */

  1. ```javascript
  2. // 练习题
  3. var a = { n: 1 }
  4. var b = a
  5. a.x = a = { n: 2 }
  6. console.log(a.x) // undefined
  7. console.log(b) // {n: 1, x: { n: 2 }}

图解: 对象连续赋值.png

常用运算符优先级

优先级相同, 从右往左执行

分组 () 21
成员访问 obj. 20
带参数new() new(*)
函数调用 fn()
可选链 ?.
不带参数new new xx 19
逻辑与 && 7
逻辑或 || 6

9. 函数的存储方式 及执行过程

  1. EC(stack) 执行环境
  2. 执行上下文, (全局EC(G)/私有)
  3. 执行上下文进栈运行, 执行完还可能出栈

函数创建

  1. 开辟一个堆内存: 16地址 AAAFFF000
  2. 声明当前函数的作用域, 在哪上下文创建的,他的作用域就是谁
  3. 函数体中代码当做字符串存储在堆内存中(创建一个函数, 存储的字符串, 如果不执行, 没有意义)
  4. 把函数的堆内存地址,提供函数名使用

堆内存存放返回地址指向
AAAFFF111 —转换为代码字符串
存储键值对对象
length 形参的个数
name 函数的名称
prototype 函数的原型

函数执行

非严格模式下,会创建映射机制,(参数相互关联)严格模式下不会创建映射机制 ,
箭头函数没有arguments

  1. 会形成一个全新的私有上下文EC(xx) ()目的是供函数体中的代码执行),然后进栈执行
  2. 在私有上下文中有一个存放私有变量的变量对象AO(xx)
  3. 在代码执行之前要做的事情很多:
    1. 初始化它的作用域链<自己上的上下文,函数的作用域>
    2. 初始化THIS(箭头函数没有THIS)
    3. 初始化ARGUMENTS实参类数组集合(箭头函数没有ARGUMENTS)(非严格模式会跟形参建立连接关系)
    4. 形参赋值(形参变量是函数的私有变量,需要存储在AO中的)
    5. 变量提升(在私有上下文中声明的变量都是私有变量)
  4. 代码执行: 把之前在函数堆中存储的代码字符串, 执行
    1. 作用域链查找变量: 查找机制 => 在代码中, 遇到一个变量 ,首先查看是否为自己私有变量, 如果没有, 则会按照scope-chain 向上级上下文查找, 一直找, 直到找到EC(G) 为止, 如果没有则会返回undefined, 在哪上下文找到, 接下来所有操作都是操作对应上下文的变量
  5. 根据实际的情况确定当前上下文是否出栈释放
    1. 普通函数执行完会释放
    2. 只要当前上下文的某些内容, 被上下文以外的东西占用, 那么当前上下文不会释放

函数二次执行,会形成一个全新的私有上下文,把之前做过的事情,还是原封不动的再执行一次(所有的东西都是从头来一遍的),此时形成的上下文和上一次形成的上下文之间没有必然的联系

图解: 函数执行图解.png
练习题: 练习闭包作用域.js

//形参的值假如是引用类型,形参都是私有的AO对象里面
let x = {n:100};  //假如实参是对象,指向地址
~function(x){ 
    x.n =200;
    console.log(x); //200
}(x)
console.log(x); //200

let x = 100;
~function(x){ //假如实参是基本类型 返回值,改变不会影响传入的源值
    x =200;
    console.log(x); //100
}(x)
console.log(x); //200
console.log(x); //200
  1. let 创建的变量一定是全局变量,只不过不是全局属性
  2. 函数也是一个变量 和let var 创建的变量本质是一样的,
  3. function fn(){} 正常的创建函数
  4. let fn = function(){} 函数表达式创建函数
  5. (function(){})() 匿名自掉函数

    10. 闭包

    机制: 形成一个不被释放的上下文就会产生闭包
    闭包: 它是函数运行时所产生的一个机制, 函数执行会产生一个全新的私有上下文,可以保护里面的私有变量与外界互补干扰,
    产生: 函数当前上下文的成员被外部作用域链牵引着就行程闭包(基本类型不会产生闭包)
    优点: 保护/保存
    缺点: 占用堆内存

闭包的应用

  1. 阐述ECStack/EC/AO/VO/Scope/SCOPE-CHAIN/释放不释放, 垃圾回收
  2. 实战用途
  3. 高阶编程: 柯理化/ 惰性函数/ compose函数
  4. 源码分析
  5. 自己封装插件组件库

11. 内存优化

  1. 栈内存: 执行上下文
    1. 一般情况, 函数执行完,所形成的上下文会被出栈释放
    2. 特殊情况, 当前上下文中的某些内容被当前上下文以外的事物占用了, 此时不能出栈释放
    3. 全局上下文, 加载页面时创建, 也只是页面关闭才会被释放掉
  2. 堆内存: 浏览器的垃圾回收机制
    1. 引用计数: (以IE为主) 使用一次计数一次, 释放一次减少一次, 计数为0时释放堆内存
    2. 检测引用/标记清除 (以chrome为主) 浏览器再空闲的时候会依次检测所有的堆内存, 把没有被占用的内存释放, 以此来优化内存
  3. 手动释放内存,其实就是解除占用(手动赋值为null即可)

12. let的块级作用域与闭包应用

事件委托

循环添加事件

// 最好的方法是事件委托(性能是最好的)
var arr=['red','green','blue','black','pink'];
document.body.onclick=function(ev){
  let target=ev.target,targetTag=target.tagName;
  //当前点击的是五个按钮中的一个,target事件源就是点击的这个按钮
  if(targetTag ==="BUTTON"){
      var index=target.getAttribute('index');document.body.style.backgroundColor=arr[index];
  }
}

var与没有var区别

链式查找: 在当前上下文遇到一个变量,如果不是私有的,则向上级上下文中查找…一直找到EC(G)为止,如果EC(G)中也没有:

  1. 如果是获取变量的 值,则直接报错
  2. 如果是设置变量的值,则相当于给window(G0)设置一个属性

GO全局对象 和 VO(G)全局变量对象 的关系
1. 两者之间存在映射关系(创建一个全局变量,也相当于给GO设置一个属性)【刨除基于let/const创建的变量】

// 此时这个a只是GO的属性,相当于省略了window.,但是它不能理解为全局变量
a = 12;
console.log(a); // 12
console.log(window.a); // 12

练习题: 202020年04期在线JS高级(第一周作业

14. 函数执行双私有上下文

  1. Block(块级上下文)
  2. Local(私有上下文)

image.png

需满足条件: 函数执行就有两个上下文

  1. 有形参赋值默认值
  2. 函数体中有声明过自己的私有变量(必须是var/ let / const)声明 , function只有声明的名字和形参中的名字相同,才会单独产生块级上下文

练习题:

// 题目1

var x = 1;
function func(x, y = function anonymous1() {x = 2}) {
    x = 3;
    y();
    console.log(x); //=>2
}
func(5);
console.log(x); //=>1


// 题目2
var x = 1;
function func(x, y = function anonymous1() {x = 2}) {
    var x = 3; //把EC(BLOCK)中的x改为3
    y(); //EC(BLOCK)没有私有变量y,所以找EC(FUNC)中的y执行
    console.log(x); //=>输出的是EC(BLOCK)中的x  3
}
func(5);
console.log(x); //=>1



// 题目3
var x = 1;
function func(x, y = function anonymous1() {x = 2}) {
    var x = 3;
    var y = function anonymous2() {x = 4};
    y();
    console.log(x);
}
func(5);
console.log(x);

15. 闭包作用域

arguments相关

知识点:

  1. 函数执行预先初始化ARGUMENTS, 值为传递参数的类数组集合,
  2. 不管有没有形参都会初始化(箭头函数没有),
  3. 非严格模式下形参赋值完成, 会和ARGUMENTS中的每一项建立映射机制, 一个改另外一个也会跟着改, 但是在严格模式下不会(‘use strict’)
  4. 形参与arguments是在函数体执行之前建立连接, 如果没有建立连接, 则不会映射: 参考题目2

练习题:

// 题目1
var a = 4;
function b(x, y, a) {
    console.log(a); // =>3
    arguments[2] = 10;
    console.log(a); // =>10
    x=20;
    console.log(arguments[0]); // =>20
}
a = b(1, 2, 3); // a=undefined 因为b函数执行没有返回值(主要看return)
console.log(a); // =>undefined


// 题目2
var a = 4;
function b(x, y, a) {
    a = 2
    arguments[2] = 10;
    console.log(arguments[2]); // undefined
}
a = b(1, 2); // 传递2个参数

匿名函数具名化

知识点:

  1. 匿名函数具名化只能在函数内部使用, 外部无法使用,
  2. 在函数内部默认是不允许修改具名的值, 除非这个函数体重被重新声明过(function/var/let/const), 如果重新声明后, 名称的值会按照声明的为主

练习题:

// 要点1
var b = 10
(function b(){
    b = 20
  console.log(b) // 函数本身
})()
console.log(b) // 10


// 要点2
var b = 10
(function b(){
    var b = 20
  console.log(b) // 20
})()
console.log(b) // 10

16. 高阶编程思想

1.单例设计模式

单例设计模式: 用单独的实例来管理当前事务的相关特征, 类似于实现一个分组的特点
个人理解: 闭包包起来,返回方法

//不能使用~function(){}(),只能使用下面的方式,才有返回值
let Module = (function(){
    let addFun = function(){
        //操作....
    }
    return {
        addFun
    }
})()
//别人使用的时候可以采用Module.addFun

2.惰性函数 有些判断只做一次判断

//惰性函数思想   利用闭包减少判断的次数
function emit(element,type,func){
    //兼容性判断只判断一次
    //如果支持addEventListener 全局emit重写方法
    if(element.addEventListener){
        emit = function(element,type,func){
            element.addEventListener(element,type,func)
        };
    }
    else if(element.attachEvent){
        emit  = function(element,type,func){
            element.attachEvent("on"+element,type,func)
        }
    }
    else{
        emit = function(element,type,func){
            element["on"+ type] = fun;
        }
    }
    //重写完后执行一遍
    emit(element,type,func);
}

3.柯理化函数

珠峰理解: 一个大函数,返回一个小函数
柯理化函数的思想:利用闭包的机制,把一些内容事先存储和处理了,等到后期需要用的时候直接用即可
call/apply 都会把函数立刻执行, 而bind不会立刻执行函数,属于预先存储一些内容

//柯理化函数思想,大函数里面返回小函数,利用闭包机制
/*
 * bind :预先处理内容
 * @param func 要执行的函数,
 * @param content    要更改的this指向
 * @param args    给函数传递的参数
 * @return 返回一个代理函数
 */
function bind(func, content, ...args) {
    return function proxy() {
        //call会立刻执行 bind 不兼容ie678
        func.call(content, ...args);
    }
}
//********************************************
let obj = {
    x : 100
}
function fn(y) {
    this.x += y;
    console.log(this);
}
//一秒后把执行返回的小函数
//执行顺序,运行函数bind立刻执行,返回proxy给定时器,5秒后执行小函数
setTimeout(bind(fn, obj, 200), 5000);

4.函数调用扁平化 compose

let fun1 = function(x) {
    return x + 10;
}
let fun2 = function(x) {
    return x * 10;
}
let fun3 = function(x) {
    return x / 10;
},,
//需求 拿到fun1的返回值传入fun2,  以上的返回值传入fun1 以上的返回值传入fun3  操作的函数量未知
const result = fun3(fun1(fun2(fun1(5))))//16
/**
 * 函数调用扁平化
 * @param {funs} 函数集合 
 * @param {args}  传入参数
 * @return 返回汇总
 */
function compose(...funs) {
    //有柯理化函数思想
    //直接返回代理函数
    return function proxy(...args) {
        let len = funs.length;
        //如果函数个数为0直接返回传参
        if (len === 0) return args;
        if (len === 1) return funs[0](...args);
        //利用数组reduce函数实现扁平化
        return funs.reduce((x, y) => {
            return typeof x === "function" ? y(x(...args)) : y(x);
        })
        //补充知识点 如果reduce 只传入第一个函数参数,x是第一个,y是第二索引
        //如果reduce传入第二个参数,x就是第二个参数,y就是第一个索引
        //reduce必须有返回值,下一次循环才有x的值否则为undefined
    }
}
const x = compose(fun1, fun2, fun1, fun3)(5);
console.log(x);//16

17. let const var 区别

变量提升

在当前上下文代码自上而下执行之前, 会把所有带var/function关键字的进行提前声明/定义(var 只是声明, function 是声明+定义)

  1. var 存在变量提升
  2. let/const 不存在变量提升

    重复声明

    服务器获取到代码后浏览器需要进行词法解析

  3. var 允许重复声明

  4. let/const 不允许重复声明

    块级作用域(重点)

  5. const/let 存在块级作用域

  6. var 没有块级作用域

更改指针

  1. var / let 允许变量更改指针
  2. const创建的变量是不能更改变量的指针

经典练习题:
要点: 循环一共创建N+1个执行上下文

// var
for (var i = 0 ; i<5; i++) {
    // ... 
}
console.log(i) // 5



// let 
for (let i = 0 ; i<5; i++) {
    // ... 
}
console.log(i) // Uncaught ReferenceError: i is not defined

// 循环一共创建N+1个执行上下文 
// 父上下文控制循环
// 子上下文:私有为循环体

暂时性死区

// 输出一个没有定义的变量会报错
conole.log(a) // Uncaught ReferenceError: a is not defined 

// 但是检测一个没有定义的变量类型不会报错
type a !== 'undefined' // false

18. 闭包作用域练习题

function fun(n, o){
  console.log(o)
  return  {
    fun: function(m){
        return fun(m, n)
    }
  }
}

var c = fun(0).fun(1)
c.fun(2)
c.fun(3)

19. jQuery 源码思想

1. 环境判断

通过暂时性死区特点判断对应属性是否存在, 从而判断运行环境属于浏览器还是node环境

(function (global, factory) {
    "use strict";
    if (typeof module === "object" && typeof module.exports === "object") {
        // 此条件成立说明当前运行代码的环境支持CommonJS规范
        // (浏览器端不支持/NODE端是是支持的)
        module.exports = global.document ?
            factory(global, true) :
            function (w) {
                if (!w.document) {
                    throw new Error("jQuery requires a window with a document");
                }
                return factory(w);
            };
    } else {
        // 浏览器端运行
        factory(global);
    }

})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
    // ...
});

2. 函数暴露与缓存

假如需要把函数暴露给全局使用(闭包) 怕与别的插件/类重名,可以暴露一个方法给外部调用,变量命名谦让

function factory(){
    "use strict";

    var version = "3.5.1",
        jQuery = function (selector, context) {
            // ...
        };

    // 在导入JQ(但并没有把自己的jQuery/$暴露给全局),首先会把现有全局中叫做$/jQuery的存储起来,防止自己后期设置的$/jQuery会替换全局现有的
    var _jQuery = window.jQuery,
        _$ = window.$;

    // 基于noConflict转移JQ对$/jQuery的使用权
    jQuery.noConflict = function (deep) {
        if (window.$ === jQuery) {
            window.$ = _$;
        }
        if (deep && window.jQuery === jQuery) {
            window.jQuery = _jQuery;
        }
        return jQuery;
    };

    // 在闭包中把一些私有的信息暴露到全局使用:RETURN/WINDOW.XXX=XXX
    if (typeof noGlobal === "undefined") {
        window.jQuery = window.$ = jQuery;
    }
    return jQuery;
}

20. This指向

THIS:执行主体(谁把他执行的)和函数在哪执行和创建没有必然的关系

  1. 函数执行,看函数前看是否有“点”,有“点”它前面是谁THIS就是谁,没有“点”THIS就是window(非严格模式)/undefined(严格模式)
    + 自执行函数中的THIS一般都是window/undefined
    + 回调函数中的THIS一般也是window/undefined(除非某个函数内部给回调函数做了特殊的处理,这样回调函数中的THIS有自己的特殊情况)

  2. 给当前元素的某个事件行为绑定方法,当事件行为触发,方法中的THIS是当前操作的元素(特殊:IE6~8中基于DOM2事件绑定attachEvent,方法中的this不是元素)

  3. 箭头函数中/私有块级上下文,没有自己的THIS,所用到的THIS都是其上级上下文中的THIS(也就是没有初始化THIS这一步)

  4. 构造函数中的THIS一般是当前类的实例

  5. 基于call/apply/bind可以强制改变THIS

练习题: 闭包tihs指向图解.png

var num = 10
var obj = {
  num: 20
}
obj.fn = (function (num) {
  this.num = num * 3
  num++
  return function (n) {
    this.num += n
    num++
    console.log(num)
  }
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)

21. 面向对象编程(OOP)

三个概念

  1. 对象: 一种泛指 万物皆对象
  2. 类:对象的细分 ,可以细分很多个类
  3. 实例:类中具体的事物

备注:
所有类的基类都是Object
每一个实例都可以调用所属类(原型链)中的属性和方法
具备Symbol(Symbol.iterator)属性的, 说明它是可被迭代的数据结构, 最直观是可以使用es6的for of循环

JS中的内置类

  1. 【数据类型】

    1. Number String Boolean (Symbol BigInt)
    2. Object :Object Array RegExp Date …
    3. Function
    4. 每一个数据类型都是自己所属类的一个实例
  2. 【每一个元素对象都有一个自己所属的类】

    1. 每一个元素都是Object的实例
    2. div < HTMLDivElement < HTMLElement < Element < Node< EventTarget < Object
  3. 【元素/节点/样式集合】

    1. HTMLCollection / NodeList / CSSStyleDeclaration…

22. 构造函数执行的细节与知识点

new XXX做了哪些事情?

  1. 创建一个空的实例对象 : 当前类的实例空对象
  2. 初始化this , 把this指向创建的空对象
  3. 执行普通函数该做的事情: (作用域链/arguments/形参赋值/变量提升/代码执行)
  4. 返回创建的对象,

    1. 只有return 引用类型才会返回对应引用类型的,
    2. 否则 全部返回创建的实例对象: return基本类型 或者 不return


    构造函数执行.png

验证

  1. in 判断 : 不论是私有还是公有属性, 只要在原型链上找到, 就返回true
  2. hasOwnProperty 判断: 只有在它的私有属性, 结果才为true
  3. instanceof 判断 : 判断某某是不是某某的实例 ```javascript function Func(x, y) { let total = x + y; this.result = total; this.say = function () {}; }

let f1 = new Func(10, 20); console.log(f1); console.log(f1.total); //=>undefined total只是上下文中的私有变量,和实例没有必然的关系,只有this.xxx=xxx才是给实例设置的私有属性 console.log(f1.result); //=>30

console.log(‘say’ in f1); //=>true console.log(‘toString’ in f1); //=>true console.log(f1.hasOwnProperty(‘say’)); //=>true console.log(f1.hasOwnProperty(‘toString’)); //=>false

<a name="g1jvV"></a>
#### 对象相关
```javascript
Object.assign()//合并对象
Object.create(Obj) //创建一个空对象把__proto__指向Obj
Object.defineProperty(obj,key,options)//监听一个对象某个属性
Object.entries()//获得一个对象的所有键值对
Object.keys()//获取键
Object.values()//获取值
//const创建的变量是不能更改变量的指针
Object.freeze()//冻结一个对象不能所有操作增删改 只限于指定层
Object.is()//传入两个实参,判断两个值是否相等,比===更准确

23. 原型/原型链

核心理解:

  1. 每一个函数(构造函数[类])都天生具备一个属性“prototype原型”,属性值是一个对象:存储当前类供实例调用的公共属性和方法
  2. 在原型对象上有一个内置的属性“constructor构造函数”,存储的值是当前函数本身,所以我们把类称为构造函数
  3. 每一个对象都天生具备一个属性“proto隐式原型/原型链”,属性指向自己所属类的原型对象
    1. 实例.proto===所属类.prototype

函数类型:

  1. 普通函数
  2. 构造函数(类)
  3. 内置类

对象类型:

  1. 普通对象/数组对象/正则对象/日期对象…
  2. prototype/proto
  3. 类的实例也是对象(排除基本数据类型值的特殊性)
  4. 函数也是对象
  5. 万物皆对象

原型链机制:
访问对象的某个成员, 首先查看是否为私有的, 如果是私有的, 则找私有的, 如果没有, 则基于porto找所属类的.prototype上的公共成员(属性/方法), 如果还是没有, 再基于proto 继续往上查找, 直到Object.prototype为止, 如果都没有则返回属性(undefined)/方法(报错)

图解: 内置类的原型图.png
练习: 题目2图解.png

// 1
console.log(
  Array.prototype.hasOwnProperty('push')
)



// 2
function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
Fn.prototype.getY = function () {
    console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();

链式写法:为什么可以实现链式写法
上一个方法执行返回的结果依然是当前类的实例;

25. new方法重写

  1. 创建指向传入函数原型的空对象: Object.create()
  2. call执行
  3. 判断call执行返回的数据类型

关联22标题

技巧:

  1. 创建一个纯空对象 const obj = Object.create(null) ```javascript function Dog(name) { this.name = name; } Dog.prototype.bark = function() { console.log(“wangwang~~”); } Dog.prototype.sayHi = function() { console.log(this.name); } /**
    • 自己实现的new
    • @param {func} 操作的类
    • @param {args} 参数 */ function new(func, …args) { // //默认创建一个实例对象,(属于当前这个类的实例) // let obj = {} // //把创建的原型等于操作类的原型(继承) // obj.proto = func.prototype; //以上IE大部分都不允许我们直接操作_proto //解决方案 let obj= Object.create(func.prototype); //也会把类当做普通函数执行 //执行的时候要保证函数中的this指向创建的实例obj let result = func.call(obj, …args); //判断用户是否有自己的引用类型返回值,是引用类型返回用户,否者返回新对象 if (result !== null && /^(object|function)$/.test(typeof result)) { return result; } return obj; }

let sanmao = _new(Dog,”sanmao”); sanmao.bark();//wangwang~~ sanmao.sayHi();//sanmao

<a name="vssDb"></a>
#### Object.create重写
```javascript
/* 
  * Object.create([OBJECT])
    *   创建一个空对象x,并且把[OBJECT](这个值需要是一个对象)作为新对象的原型指向
    *   x.__proto__=[OBJECT]
    * 
    * Object.create(null):创建一个没有原型/原型链的空对象(不是任何类的实例)
*/

// 我们写的这个方法不支持null的处理
Object.create = function create(prototype) {
  if (prototype === null || typeof prototype !== "object") {
    throw new TypeError(`Object prototype may only be an Object: ${prototype}`);
  }
  // 创建一个类,创建这个类的实例,实例.__proto__=类.prototype;而我们让类.prototype等于传递的prototype;
  function Temp() {}
  Temp.prototype = prototype;
  return new Temp;
};

26. 内置类原型扩展注意点

  1. 为了防止自己设定的方法覆盖内置的方法, 我们设置的方法需加前缀
  2. 优势: 使用起来方便, 和内置方法类似, 直接让实例调用即可
  3. 优势: 方法中的this一般是当前要操作的实例, 也就不需要传递操作对象
  4. 只要保证方法的结果还是当前类的实例, 我们就可以使用链式操作 调用内置的方法

29. Function与Object之间的关系

  1. Object座位一个类/函数, 它是Function的一个实例
  2. Function虽然是类/函数, 但是函数也是对象.所以也是Object的一个实例
  3. Js中任何实例(除开基本类型) 最后都可以基于自己的proto 找到Object.prototype 也就是说所有的值都是Object的实例 —> 万物皆对象 ```javascript // 大写函数是大写函数的实例 console.log( Function instanceof Function // true )

// 自己的原型链等于自己的原型 console.log( Function.prototype === Function.porto // true )

console.log( Object.proto.proto === Object.prototype // true )

图解: [Function与Object的关系.png](https://www.yuque.com/attachments/yuque/0/2021/png/1665156/1636953334585-ef5ea4f5-0711-4de7-a4be-228c4a33f5ac.png?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fwww.yuque.com%2Fattachments%2Fyuque%2F0%2F2021%2Fpng%2F1665156%2F1636953334585-ef5ea4f5-0711-4de7-a4be-228c4a33f5ac.png%22%2C%22name%22%3A%22Function%E4%B8%8EObject%E7%9A%84%E5%85%B3%E7%B3%BB.png%22%2C%22size%22%3A306686%2C%22type%22%3A%22image%2Fpng%22%2C%22ext%22%3A%22png%22%2C%22status%22%3A%22done%22%2C%22taskId%22%3A%22u07303ee1-68d1-4c66-bdcd-a63c6e5c106%22%2C%22taskType%22%3A%22upload%22%2C%22id%22%3A%22u01003aab%22%2C%22card%22%3A%22file%22%7D)

<a name="zwhxI"></a>
## 30. 原型重定向细节

| 方法 | 缺点 | 优点 |
| --- | --- | --- |
| <br />1. 向内置的原型上扩展方法<br /> | 编写繁琐,代码不通用 | 不会影响原来的原型 |
| <br />2. 直接对象赋值原型<br /> | 旧原型对象的方法全部没了 | 代码便捷,美观 |
| <br />3. 使用Object.assign<br /> | 如果成员重名, 会丢失一个 | 不重名前提下,没啥毛病 |
| <br />4. Object.create跟Object.assign结合<br /> | 无 | 技能合并方法, 重名也不会丢失 |

```javascript
function Func() {}
Func.prototype.XX = function () {};


方法一
// 2.设置别名
Func.prototype.A = function () {};
Func.prototype.B = function () {};
let proto = Func.prototype;
proto.A = function () {};
proto.B = function () {}; 



// 方法二
// 一般想往原型上批量设置属性方法,都是基于重定向的方式
// 1.缺失了constructor
// 2.也缺失了原始原型对象的上的属性和方法
Func.prototype = {
    A: function () {},
    B: function () {}
};



// 方法三
// 两个原型对象合并,用新的原型对象替换原始的原型对象(问题:如果新老有个属性方法相同,则新的值会替换老的值)
Func.prototype = Object.assign(Func.prototype, {
    A: function () {},
    B: function () {}
});



// 方法四
// 3.把老的原型对象作为新原型对象的上级原型
let protoNew = Object.create(Func.prototype);
protoNew = Object.assign(protoNew, {
    A: function () {},
    B: function () {}
});
Func.prototype = protoNew;

练习题:

function Fn() {
    let a = 1;
    this.a = a;
}
Fn.prototype.say = function () {
    this.a = 2;
}
Fn.prototype = new Fn;
let f1 = new Fn;
Fn.prototype.b = function () {
    this.a = 3;
};
console.log(f1.a);
console.log(f1.prototype);
console.log(f1.b);
console.log(f1.hasOwnProperty('b'));
console.log('b' in f1);
console.log(f1.constructor == Fn);

图解: 原型重定向.png

32.函数的三种角色

函数与对象之间的关系

  1. 函数与类都有prototype

image.png

函数的角色

1 . 普通函数 : 作用域/作用域链
2 . 构造函数 : 类 / 实例 / 原型/ 原型链
3 . 普通对象 : 键值对操作

主要角色: 函数本身

typeof Array // 'function'
typeof Object // 'function'

练习题:
考点: 变量提升, 作用域链, this指向, 函数的角色, 运算符优先级

function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

图解: 练习题图解.png

33. 多种继承方式

原型继承

核心: Child.prototype = new Parent
特点:

  1. 父类中私有和公有的成员, 最后都变为子类实例公有的成员
  2. 和其他语言不同的是, 原型继承并不会把父类的成员”拷贝”给子类, 而是通过原型链方式建立连接关系
  3. 原型继承图解.png

call继承

核心: Parent.call(this) 在子类构造函数中call执行父类方法,(普通方法执行)
特点:

  1. 能把父类的私有属性’拷贝’为子类的私有属性
  2. 父类的公共成员与子类没有关系(缺点)
  3. call继承图解.png

寄生组合式继承

核心: call继承+ 另类原型继承

function Parent() {
    this.x = 100
}

Parent.prototype.getx = function getx() {
    return this.x
}

function Child() {
    Parent.call(this) // 核心1
    this.y = 200
}

Child.prototype = Object.create(Parent.prototype) // 重点
Child.prototype.constructor = Child // 锦上添花
Child.prototype.getY = function getY() {
    return this.y
}
let c1 = new Child
console.log(c1)

特点:

  1. 父类的私有成员’拷贝’为子类的私有成员
  2. 父类的共有成员’链式指向’为子类的共有成员
  3. 寄生组合式继承.png

Es6方式extends继承

核心: extends super()
特点:

  1. 与Es5寄生式组合继承很相似 ```javascript class Parent { constructor() { this.x = 100 }

    getX() { return this.x } }

class Child extends Parent { constructor() { // 一定要超链方法调用 super() this.y = 200 }

getY() { return this.y }

}

const c1 = new Child

console.log(‘c1 :’, c1)

<a name="Q1M7J"></a>
## 34. 面向对象练习题
<a name="HUUkw"></a>
#### 1. toArray
接收未知个参数, 最后输出数组 toArray
```javascript
// 单例
let utils = (function () {
    /*
     * toArray:转换为数组的方法
     *   @params  不固定数量,不固定类型
     *   @return  [Array] 返回的处理后的新数组
     * by zhufengpeixun on 2020
     */
    function toArray() {
        // return Array.from(arguments);
        // return [...arguments];

        // 把类数组转换为数组
        // var arr = [];
        // for (var i = 0; i < arguments.length; i++) {
        //     arr.push(arguments[i]);
        // }
        // return arr;

        // 只要两个实例结构类似,那么大部分操作操作的他们的代码都可以公用,无外乎就是THIS指向的问题
        return [].slice.call(arguments);
    }
    return {
        toArray
    };
})();
let ary = utils.toArray(10, 20, 30); //=>[10,20,30]
console.log(ary);
ary = utils.toArray('A', 10, 20, 30); //=>['A',10,20,30]
console.log(ary);

2. 深克隆

接收一个参数, 进行深克隆
解决方式一:
普通深克隆暴力法 JSON.parse(JSON.stringify(obj))
缺点:

属性类型 使用方式一现象

1. 正则
变为空对象

2. 函数
直接消失

3. 日期
直接转为字符串

4. Symbol
直接消失

5. BigInt
直接报错

6. undefined
直接消失

解决方式二
基本满足,
未处理: 循环引用会死循环, 还有很多类型没处理(Promise、Buffer、Set、Map …

/**
 * 思路:
 *      1. 基本类型直接返回
 *      2. 处理日期/正则
 *      3. 普通对象for in 循环, 递归克隆属性
 * @param obj
 * @returns {*}
 */
const cloneDeep = function (obj) {
  const constructor = obj.constructor
  // 基本类型
  if (obj == null || typeof obj !== 'object') {
    return obj
  }
  // 日期, 正则
  if (/^(RegExp|Date)$/i.test(constructor.name)) {
    return new constructor(obj)
  }
  // 对象
  let clone = new constructor
  for (const key in obj) {
    // 循环私有成员
    if (!obj.hasOwnProperty(key)) break
    clone[key] = cloneDeep(obj[key])
  }
  return clone
}

3. toType

js中的数据类型检测

  1. typeof 检测普通基本类型值有效
    1. 缺点: null、数组、对象、正则、日期 ==> ‘object’
  2. instanceof 检测xxx是不是xxx的实例
    1. 缺点: 只要出现在实例的原型链上的类检测, 结果就为true
    2. 例子: arr1 instanceof Object ==> true
  3. constructor 通过判断实例的构造函数是否等于xxx类
    1. 缺点: 构造函数是可以更改的
    2. 例子: arr.construtor = {} ; arr.constructor === Array ==> false
  4. Object.prototype.toString.call(xxx)
    1. 缺点: 写太长了 - -!
/**
 * 思路:
 *      1. 先使用typeof判断 如果是不是object类型 直接返回
 *      2. 否则使用Object.prototype.toString.call 判断类型
 *      3. 截取object 后的类型
 * @param obj  任意类型
 * @returns {"undefined"|"boolean"|"number"|"string"|"function"|"symbol"|"bigint"|string}
 */
const toType = function(obj){
    let _typeOf = typeof obj
    if (_typeOf !== 'object') return _typeOf
    const _toStringType = Object.prototype.toString.call(obj)
    const [_, type] =  /^\[object ([a-z]+)\]$/i.exec(_toStringType)
    return type.toLowerCase()
}

4. 深判断 isEqual

/**
 * 功能: 传递两个参数 判断两个参数是否完全一样,深判断
 * 思路:
 *      1. 基本类型值使用 === 判断
 *      2. 函数, 日期, 正则 toString()后比较
 *      3. 对象, 挨个比较每个属性跟属性值
 * @param val1
 * @param val2
 * @returns {boolean}
 * @private
 */
const _isEqual = function (val1, val2) {
  const type1 = val1 === null ? 'null' : typeof val1
  const type2 = val2 === null ? 'null' : typeof val2
  const toStringJudge = val1.toString() === val2.toString()
  // 函数 => toString()
  if (type1 === 'function' && type2 === 'function') {
    return toStringJudge
  }

  // 对象
  if (type1 === 'object' && type2 === 'object') {
    const ct1 = val1.constructor
    const ct2 = val2.constructor

    const isRegExp = ct1 === RegExp && ct2 === RegExp
    const isDate = ct1 === Date && ct2 === Date
    // 日期, 正则
    if (isRegExp || isDate) {
      return toStringJudge
    }
    // 其他对象
    const keys1 = Object.keys(val1)
    const keys2 = Object.keys(val2)
    if (keys1.length !== keys2.length) return false
    for (let i = 0; i < keys1; i++) {
      const key1 = keys1[i]
      const key2 = keys2[i]
      if (key1 !== key2) return false
      const item1 = val1[key1]
      const item2 = val1[key2]
      // 递归
      const flag = _isEqual(item1, item2)
      if (!flag) return false
    }
    return true
  }
  // 基本类型
  return val1 === val2
}

37. instanceof 重写

规则: 只要在实例的原型链上找到类的原型, 就会返回true


/**
 * 思路:
 *      1. 使用while循环, 每次往上查找原型链
 *      2. 如果找到原型链 === null  已经到尽头退出
 *      3. 如果原型链 === 类的原型 返回true
 * @param example 实例
 * @param classFun 类
 * @returns {boolean}
 * @private
 */
const _instanceOf = function (example, classFun) {
  const classPrototype = classFun.prototype
  let proto = Object.getPrototypeOf(example)
  while (true) {
    if (proto === null) return false
    if (proto === classPrototype) return true
    proto = Object.getPrototypeOf(proto)
  }
}

38. 原型上扩展方法事项

优点:

  1. 可以直接让实例调用,而此时方法执行,方法中的THIS是调用的实例(调取比较的方便)
  2. 可以实现链式写法:上一个方法执行返回的结果,可以继续调取本结果所属类原型上的方法

注意:

  1. 我们自己扩展的方法不要覆盖内置的方法(最好设置的方法带个前缀)

要点:

  1. 创建值的方式分为两种,
    1. 字面量方式 const num = 10
    2. 构造函数方式 const num = new Number(10)
  2. 创建基本类型的值时
    1. 字面量方式创造的基本类型的值、
    2. 构造函数创造出来的是引用数据类型的值
  3. 在不改变this的前提下, this一般是一个对象(除了null/undefined)
  4. 基本类型在运算时会先调取.valueOf获取原始值[[PrimitiveValue]]

练习题:

  1. 原型添加方法, 链式操作 let m = n.plus(10).minus(5); console.log(m); //=>15(10+10-5) ```javascript // 处理参数 function initParams(num) { num = Number(num); return isNaN(num) ? 0 : num; } Number.prototype.plus = function plus(num) { // this -> 永远是对象数据类型的值(除了null/undefin ed) // 对象+数字:不一定都是字符串拼接 // =>对象本身是想转换为数字再进行运算的 // =>对象转换为数字,并不是先toString,他需要先.valueOf获取原始值[[PrimitiveValue]],如果有原始值(原始值是基本类型的值),直接基于原始值处理,没有原始值的才去toString\ num = initParams(num); return this + num; }; Number.prototype.minus = function minus(num) { num = initParams(num); return this - num; };

let n = 10; let m = n.plus(10).minus(5);


2. queryUrlParams 查询  根据传入key 查询url的参数值
```javascript
// 利用正则捕获
String.prototype.queryUrlParams = function (key) {
  const obj = {}
  this.replace(/([^?=&#]+)=([^?=&#]+)/g, (_, key, value) => obj[key] = value)
  this.replace(/#([^?=&#]+)/g, (_, hash) => obj['_HASH'] = hash)
  return key ? obj[key] : obj
}
  1. 使用Es6+编写一个类

    class Model {
    constructor(x, y) {
     // 给实例添加私有属性
     this.x = x
     this.y = y
    }
    // Es7+ 给实例设置私有属性
    z = 100
    // 给其原型添加方法 (不能设置属性)
    getX() {
     console.log('this.x :', this.x)
    }
    getY() {
     console.log('this.y :', this.y)
    }
    // Es7+
    // 把Model 当为普通对象身份添加键值对(静态属性方法)
    // 跟实例没有关系
    static n = 200
    static setNumber(n) {
     this.n = n
    }
    }
    

    39. 面向对象的变相应用

    练习题:

  2. var a = ? 的时候 if ( a == 1 && a==2 && a ==3 )

    1. 方案一: 对象转换为数字, 需要调取valueof/ toString(如果对象中有私有toString, 则不会往上查找)
    2. 方案二: 通过数据劫持 ```javascript // 方案一, 把转换方法私有化 var a = { i: 0, toString() { return ++this.i } } // 或者 var a = [1,2,3] a.toString = a.shift

// 方案二: 数据劫持 var i = 1 Object.defineProperty(window, ‘a’, { get() { return ++i } })

// 题目 // var a = ? if(a == 1 && a == 2 && a == 3) { console.log(‘Ok’) }

<a name="IJF6Y"></a>
## 40. call 重写
思路: 

1. 判断传入第一个参数是否为null/undefined, 若是this指向window
1. 判断传入第一参数是否为Object/Function,若不是第一参数转为对象  
1. 创建Symbol,第一个参数的Symbol对象赋值为this
1. 调用第一参数的Symbol方法传入剩余参数return返回值, 删除Symbol属性
```javascript
/**
 * @param _thisObj 要改变this的指向对象
 * @param params  传入的剩余参数
 */
Function.prototype.myCall = function myCall(_thisObj, ...params) {
  _thisObj = _thisObj == null ? window : _thisObj
  const _thisObjType = typeof _thisObj
  if (!/^(object|function)$/i.test(_thisObjType)) {
    // _thisObj = new _thisObj.constructor(_thisObj) // 不适合Symbol和bigInt类型
    _thisObj = Object(_thisObj)
  }
  let result, key = Symbol('KEY')
  // this指向点前的调用者
  _thisObj[key] = this
  result = _thisObj[key](...params)
  // 处理完需要删除添加的对象
  delete _thisObj[key]
  return result
}


// 栗子
function fun(x, y) {
  const sum = x + y
  console.log('sum :', sum, this)
}
// 传入 对象, 方法, 字符串, 数字, 数组, BigInt, Symbol, 布尔
const any = {}
fun.call(any , 1, 2)
fun.myCall(any, 1, 2)

练习题:

function A(x, y) {
  console.log(x + y, this.name)
}
function B(x, y) {
  console.log(x - y, this.name)
}
B.call(A, 10, 20) // -10 , A
B.call.call.call(A, 10, 20) //  NaN undefined
B.call.call.call.call.call.call(A, {name: '杨明'}, 20, 10) // 30 杨明
Function.prototype.call(A, 10, 20) // 不输出 匿名空函数
Function.prototype.call.call.call(A, 10, 20) //NaN undefined

.call.call.call....其实都是call本身
// 一次call就是调用call前面的方法  
// 多次call就是调用传递第一个参数

1.png 图解

41. bind 重写

call/apply:立即执行函数并且修改里面的THIS
bind:cccc

  1. 利用柯理化函数的编程思想,
  2. 预先把 “需要处理的函数/改变的THIS/传递的实参” 等信息存储在闭包中,
  3. 后期到达条件(事件触发/定时器等),先执行返回的匿名函数,cc
  4. 在执行匿名函数的过程中,再去改变THIS等 =>THIS和参数的预处理 ```javascript Function.prototype.myBind = function bind(context, …params) { // this -> 处理的函数 func // context -> 要改变的函数中的THIS指向 obj // params -> 最后给函数传递的实参 [10,20] let _this = this return function anonymous(…args) { // args -> 可能传递的事件对象等信息 [MouseEvent] // this -> 匿名函数中的THIS是由当初绑定的位置触发决定的(总之不是func要处理的函数) _this.call(context, …params.concat(args)) } }

const obj = { name: ‘zhufeng’ } document.body.onclick = func.myBind(obj, 10, 20)

<a name="YUv3H"></a>
## 43. 防抖和节流
<a name="WqEq3"></a>
#### 1. 防抖
概念:  在用户频繁触发的时候,我们只识别一次(识别第一次/识别最后一次)

栗子: 第一次点击,没有立即执行,等待500MS,看看这个时间内有没有触发第二次,有触发第二次说明在频繁点击,不去执行我们的事情(继续看第三次和第二次间隔...);如果没有触发第二次,则认为非频繁点击,此时去触发;

```javascript
/**
 * 防抖函数
 * 思路:
 *      1. 利用柯理化思想 缓存timer定时器
 *      2. 每次触发事件时, 清掉上一次定时器的函数
 *      3. 假如定时器函数执行过, timer = null 作为下次防抖的依据
 *      3. 通过 immediate 控制立刻执行还是末尾执行
 * @param func 要执行的函数
 * @param wait 防抖的时间
 * @param immediate 是否为立刻执行
 * @return {anonymous}
 */
const debounce = function (func, wait, immediate) {
  let timer = null
  return function anonymous(...params) {
    const now = immediate && !timer
    clearTimeout(timer)
    timer = setTimeout(_ => {
      timer = null
      !immediate ? func.call(this, ...params) : null
    }, wait)
    now ? func.call(this, ...params) : null
  }
}

2. 节流

概念: 函数频繁触发, 每一段时间内执行一次 (目的为了缩减频率)


/**
 * 函数节流
 * 功能: 频繁触发, 每wait时间内只执行一次,
 * 目的: 降低频率
 * 思路:
 *      1. 柯理化思想
 *      2. 记录上一次执行的时间 跟 下一次时间比较,
 *      3. 通过判断剩余时间小于0时且timer不存在, 立刻执行, 否则 添加定时器等待执行
 *      4. 执行完函数记录当前执行的时间来往返判断
 * @param func 节流执行的函数
 * @param wait 时间间隔
 * @return {anonymous} 
 */
function throttle(func, wait = 500) {
  let timer = null
  //记录上一次操作时间
  let previous = 0
  return function anonymous(...params) {
    //当前操作的时间
    let now = new Date()

    const remaining = wait - (now - previous)
    if (remaining <= 0) {
      // 两次间隔时间超过频率:把方法执行即可
      clearTimeout(timer)
      timer = null
      previous = now
      func.call(this, ...params)
    } else if (!timer) {
      // 两次间隔时间没有超过频率,说明还没有达到触发标准呢,设置定时器等待即可(还差多久等多久)
      timer = setTimeout(() => {
        clearTimeout(timer)
        timer = null
        previous = new Date()
        func.call(this, ...params)
      }, remaining)
    }
  }
}

44. 闭包, 面向对象 综合

1. 闭包

  1. 什么是闭包, 以及它的产生
    1. 堆栈内存
    2. ECStack、 EC、 VO、 AO、 Scope、 ScopeChain
    3. 浏览器垃圾回收机制, 引用计数(ie) 标记清除(chrome)
  2. 闭包的优缺点以及作用
    1. 优点: 保护, 保存
    2. 缺点: 占用内存, 上下文栈不释放
  3. 应用场景
    1. 循环事件绑定
    2. let 块级作用域
    3. 单例设计模式
    4. 高阶函数: 惰性函数, 柯理化函数, compose函数
  4. 源码分析
    1. JQuery源码
    2. 函数的防抖和节流
    3. bing
    4. ….
  5. 自我开发的插件

2. 面向对象

  1. 基础理论
    1. 面向对象和面向过程的区别
    2. 类与实例, (包含类的封装继承多态)
    3. new构造函数, new的原理
    4. 原型与原型链 ( proto 和 prototype )
  2. 应用场景
    1. 应用他的思想 借用数组的方法 实现类数组的处理
    2. 数据类型检测
    3. 重写原型
    4. 插件组件的封装
  3. 源码分析
    1. JQuery
    2. Lodash
    3. Vue