个人总结

29 阅读13分钟

1、数据类型


// number数字类型:NaN 非有效数字、Infinity 无穷大的值
//   console.log(Number.MAX_SAFE_INTEGER); //9007199254740991 最大安全数字
//   Number.MIN_SAFE_INTEGER  最小安全数字
//   NaN!==NaN  isNaN([value]):检测[value]是否为有效数字
//   Object.is(NaN,NaN):这样的结果是TRUE
// string字符串类型:'' 、 "" 、 `` 包起来的都是字符串
// boolean布尔类型:true、false
// null  
// undefined   吴签{男},他的男朋友是undefined、他的女朋友是null

// symbol唯一值类型
//    console.log(Symbol() === Symbol()); //false 创建了两个唯一值
//    console.log(Symbol('AA') === Symbol('AA')); //false
//    @1情况一:给对象设置唯一的属性「对象的属性名类型:字符串 & Symbol类型」
/* let obj = {
    name: 'zhufeng',
    age: 12,
    [Symbol()]: 100
};
console.log(obj[Symbol()]); //undefined  获取的时候,是获取一个新的唯一值的属性值 
*/

/* let key = Symbol();
let obj = {
    name: 'zhufeng',
    age: 12,
    [key]: 100
};
console.log(obj[key]); //100 */

/* let obj = {
    name: 'zhufeng',
    age: 12,
    [Symbol()]: 100
};
let symbolKeys = Object.getOwnPropertySymbols(obj); //获取当前对象所有Symbol类型的私有属性,结果数组
symbolKeys.forEach(key => {
    console.log(obj[key]);
}); */

/* let a = {
    name: 'zhufeng'
};
let b = {
    name: 'web'
};
let obj = {};
obj[a] = 100; //obj["[object Object]"]=100
obj[b] = 200; //obj["[object Object]"]=200
console.log(obj[a]); //200 */
//   @2 redux/vuex公共状态管理的时候,派发的行为标识就可以基于Symbol类型进行宏管理
//   @3 Symbol.hasInstance\Symbol.toStringTag\Symbol.toPrimitive\Symbol.iterator...很多JS底层的处理机制,就是基于这些属性方法实现的

// BigInt大数类型:数字后面加个n即使大数类型
//    + 10   Number类型
//    + 10n  BigInt类型
//    @1 JS中在进行数学运算的时候,如果计算的数值超过最大/最小安全数字,计算出来的结果很可能是不准确的「如果遇到这样的需求,则一定会出问题:服务器端数据库存储,是有longInt类型,服务器返回给客户端的值也是超过安全数字的{一般会以字符串的方式返回,这样保证返回途中数字的安全性},此时我们去进行数学运算,结果可能就”崩“了」
//     解决办法:把服务器返回的值变为bigint格式的,然后进行运算「保证了运算的准确性{进行运算的另外一个值也应该是bigint类型的}」;把运算的结果,再次变为字符串,发送给服务器即可!!

// 对象类型
//    + 标准普通  {num:100}
//    + 标准特殊  [10,20]  /\d+/   new Date()   new Error() ... 
//    + 非标准特殊  new Number(10) ...  原始值类型对应的对象类型实例
//    + 函数对象  function fn(){}

//=======================
// 检测数据类型
//   + typeof
//     + 返回结果是一个字符串,字符串中包含了对应的数据类型
//     + typeof检测未被声明的变量,不会报错,结果是”undefined“
//       + 封装中会用到
//     + typeof null -> "object"  ?
//     + typeof 不能细分是啥对象「排除函数对象」,返回结果是 ”object/function“  ?
//     所有的数据类型值,在计算机底层中都是以2进制形式存储的{64位}
//     而typeof就是基于二进制检测的:它把000开始的都识别为对象,而null全是0,所以也被识别为对象了...但是内部识别为对象后,会再次检测这个对象有没有内部实现[[call]],如果实现了,结果是”function“,没有实现就是”object“....
//     好处:检测处理的性能是很高的

//   + instacnceof
//   + constructor
//   + Object.prototype.toString.call([value])
//   ----
//   + Array.isArray([value])
//   + ...
// 底层机制「优缺点」 ->  重写/面试题  ->  JQ源码/Lodash源码  ->  自己写一套直接项目中使用的检测方法库

/* // 需求:验证 val 是否为一个对象
if (val !== null && /^(object|function)$/i.test(typeof val)) {
    // ...
} */

/* 
// 支持浏览器导入 && 支持NODE端运行{CommonJS规范}
(function () {
    let utils = {
        // ...
    };

    /!* 暴露API *!/
    if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
    if (typeof window !== "undefined") window.utils = utils;
})(); 
*/

2、堆栈以及变量的存储

1.png

3、Symbol

/* xxx[Symbol.toPrimitive](hint){
    // hint:'number' / 'string' / 'default'
    //   + number:获取当前对象的数字类型的原始值
    //   + string:获取当前对象的字符串类型的原始值
    //   + default:根据操作获取数字或者字符串类型的原始值
} */

/* let obj = {
    name: 'zhufeng',
    age: 12,
    [Symbol.toPrimitive](hint) {
        let result;
        switch (hint) {
            case 'number':
                result = 0;
                break;
            case 'string':
                result = JSON.stringify(obj);
                break;
            default:
                result = "";
        }
        return result;
    }
};
console.log(Number(obj)); // hint:"number"
console.log(String(obj)); // hint:"string"
console.log(10 + obj); // hint:"default"
console.log(10 - obj); // hint:"number" */


//===================
/* 
  parseInt([val {string}], [radix])
    + 在[val]字符串中,从左到右找到符合[radix]进制的内容,然后把这些内容当做[radix]进制,转换为10进制的数字
    + [radix]的范围:2~36  不在这个范围内,最后结果都是NaN
    + [radix]不写或者写零:默认值是10「特殊:如果[val]字符串是以0x开始的,默认值是16」
*/
/* console.log(parseInt('10px')); //=>10
//  parseInt('10px',10)
//  在字符串中找到所有符合10进制的内容 -> '10'
//  把找到的 '10' 看做十进制转换为十进制 => 10  */

/* console.log(parseInt('1030px', 2));
//  在字符串中,找到所有符合二进制的内容 -> '10'
//  把'10'看做二进制,转换为十进制?  如何把其它机制的值转换为十进制 “按权展开求和”
//    个位数权重0  十位数权重1  百位数权重2 ...
//    1*2^1 + 0*2^0 => 2 */

/* let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr); */
/* 
  arr[0] = parseInt(27.2,0);
    parseInt('27.2',10)
    找到符合十进制的内容 -> '27'
    把'27'看做十进制转换为十进制 => 27

  arr[1] = parseInt(0,1);
    parseInt('0',1) => NaN  一进制不在进制范围内

  arr[2] = parseInt('0013',2);
    找到所有符合二进制的内容 -> '001'
    把'001'看做二进制转换为十进制
       0*2^2 + 0*2^1 + 1*2^0 => 1

  arr[3] = parseInt('14px',3);
    找到所有符合三进制的内容 -> '1'
    把'1'看做三进制转换为十进制
       1*3^0 => 1

  arr[4] = parseInt(123,4);
    parseInt('123',4)
    找到所有符合四进制的内容 -> '123'
    把'123'看做四进制转换为十进制
       1*4^2 + 2*4^1 + 3*4^0
         16  +   8   +   3 => 27
*/

//=====================
// 把对象obj转换为字符串
//   + String(obj):Symbol.toPrimitive -> valueOf -> toString  浏览器默认隐式转换用的是String(obj)
//   + obj.toString() :直接调用这个方法转字符串,不会在执行以上的规则

//----------
// “+”有两边,其中一边如果是对象obj,则会 
//   @1 调用obj[Symbol.toPrimitive]('default')
//   @2 没有这个属性,则再次调用valueOf
//   @3 valueOf获取的不是原始值,则继续toString,此时获取的结果是字符串,“+”就变为字符串拼接了
/* console.log(10 + [10]);
// 没有Symbol.toPrimitive -> valueOf获取的也不是原始值 -> 调用toString "10"  => "1010"
console.log(10 + {});
// 没有Symbol.toPrimitive -> valueOf获取的也不是原始值 -> 调用toString  "[object Object]"  => "10[object Object]"
console.log(10 + new Date());
// 调用日期的Symbol.toPrimitive('default') => "10Sun Jul 25 2021 11:28:37 GMT+0800 (中国标准时间)"
console.log(10 + new Number(10));
// 没有Symbol.toPrimitive -> valueOf 10 => 20
console.log(10 + new String('10'));
// 没有Symbol.toPrimitive -> valueOf "10" => "1010" */
// “+”有两边,其中一边是字符串,妥妥的字符串拼接
// “+”有两边,剩下的情况一般都是数学运算了

//----------
// “+”只有一边,例如:+xxx
// 一般都是把xxx转换为数字「基于:Number(xxx)」


/* let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
// 122.2 + undefined = NaN
// NaN + "Tencent" = "NaNTencent"
// 在往后都是字符串拼接
console.log(result); // "NaNTencentnull9false" */


//=====================
// 把其它类型值转换为布尔,只有 “0/NaN/空字符串/null/undefined” 五个值是false,其余都是true「哪怕是把对象转换为布尔,也不再调用Symbol.toPrimitive那套机制了,直接看是否是五个中的一个即可」

/* console.log([] == false);
// 都转换为数字  Number([]) => 0    Number(false) => 0
// TRUE

console.log(![] == false);
// 需要先算  ![] => false
// false == false  => TRUE */


// 解决方案一:利用 == 比较的时候,会把对象转换为数字 Number(a)
//   + Symbol.toPrimitive
//   + valueOf
//   + toString
//   + 把字符串变为数字
/* 
var a = {
    i: 0
};
a[Symbol.toPrimitive] = function toPrimitive() {
    // this -> a
    return ++this.i;
};
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
} 
*/

/* 
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
} 
*/

// 解决方案二:在全局上下文中,基于 var/function 声明的变量,并不是给VO(G)设置的全局变量「基于let/const声明的变量才是」,而是给GO(window)全局对象设置的属性      var a=?  ==> window.a=?
// 我们基于数据劫持完成对应的操作
var i = 0;
Object.defineProperty(window, 'a', {
    get() {
        return ++i;
    }
});
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}

4、全局变量以及局部变量

/*
 GO:全局对象「堆内存中分配的一块空间 0x000」,存储浏览器的内置API
    x -> 12
    z -> 14

 EC(G)全局执行上下文
    VO(G)全局变量对象:存储全局上下文中声明的变量的{排除基于var/function声明的}
      window  --->  0x000
      y -> 13
 */
// console.log(a); //首先会到VO(G)查找,看是否为全局变量,如果不是,则再去GO中找,看是否为全局对象的一个属性,如果还不是,则报错 Uncaught ReferenceError: a is not defined
// console.log(window.a); //直接去GO中查找是否存在a这个成员,如果没有则不会报错,值是undefined

/* 
debugger;
var x = 12;
let y = 13;
z = 14; //window.z=14;
console.log(x, y, z); //12,13,14
console.log(window.x, window.y, window.z); //12 undefined 14 
*/

//======================
let x = [12, 23];
const fn = function fn(y) {
    y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
};
fn(x);
console.log(x);

1.鍑芥暟杩愯鏈哄埗.png

5、全局变量以及局部变量2

/*
 EC(G)
   VO(G) 或者 GO 
     a -> 12

   变量提升:
     var a;
 */
/* 
console.log(a); //undefined
var a = 12;
console.log(b); //Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 12;
//==只有带var/function存在变量提升,带let/const的不存在,所以不能再声明之前使用这个变量「体现出ES6这版本的语法规范更加的严谨」 
*/

/*
 EC(G)
   VO(G)/GO
     fn  -> 0x001 [[scope]]:EC(G)
         -> 0x002 [[scope]]:EC(G)
         -> 12
     
   变量提升
     function fn(){ console.log(1); }  声明+定义{赋值}
     var fn;  声明
     function fn(){ console.log(2); }  声明+定义{赋值}
 */
/* console.log(fn); //0x002
function fn(){ console.log(1); } //变量提升阶段已经处理过了,直接跳过即可
console.log(fn); //0x002
var fn = 12;
console.log(fn); //12
function fn(){ console.log(2); } //跳过
console.log(fn); //12 */


/* // 因为变量的提升的机制,我们可以把函数执行放在创建函数的代码之前「不严谨」
fn();
function fn() {
    console.log(1);
} */

/* // 真实项目中,推荐大家使用函数表达式「逻辑更加严谨」
// fn(); //Uncaught ReferenceError: Cannot access 'fn' before initialization
const fn = function fn() {
    console.log(1);
};
fn(); */

/*
 EC(G)
    VO(G)/GO
      a   => window.a=undefined
    变量提升:
      var a;
 */
/* console.log(a); //undefined
if (!('a' in window)) { // attr in obj:检测attr是否为obj对象的一个属性(成员),如果是对象的属性,结果是true
    // 'a' in window => true
    var a = 13;
}
console.log(a); //undefined */

3鍑芥暟鏄釜娓g敺.png

6、函数的变量提升

/* //忽略报错的影响
console.log(a);
// console.log(b);
var a = 12;
let b = 13;
if (1 == 1) {
    console.log(a);
    // console.log(b);
    var a = 100;
    let b = 200;
    console.log(a);
    console.log(b);
}
console.log(a);
console.log(b); */


// 函数是个渣男
/* debugger;
console.log(foo);
if (1 === 1) {
    console.log(foo);
    function foo() {}
    foo = 1;
    console.log(foo);
}
console.log(foo); */


/*
 EC(G)
   VO(G) / GO
      f -> 0x000 [[scope]]:EC(G)  “return true”
      g -> 0x001 [[scope]]:EC(G)  “return false”
   变量提升:- -
 */
/* f = function () {return true;};
g = function () {return false;};
(function () {
    /!*
     EC(AN) 
       AO(AN)
          g
       作用域链:<EC(AN),EC(G)>
       初始THIS:window / undefined
       初始ARG:...
       形参赋值:--
       变量提升:function g;  只声明不定义了「因为其出现在判断体中」
     *!/
    if (g() && [] == ![]) {  //Uncaught TypeError: g is not a function
        f = function () {return false;}
        function g() {return true;}
    }
})();
console.log(f());
console.log(g()); */

6、var与let

/* let x = 5;
const fn = function fn(x) {
    return function (y) {
        console.log(y + (++x));
    }
};
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x); */

/* let a = 0,
    b = 0;
let A = function A(a) {
    A = function (b) {
        alert(a + b++);
    };
    alert(a++);
};
A(1);
A(2); */


/* 
   LET 和 VAR 的区别?
      @1 变量提升: let不存在变量提升、而var是具备变量提升的
      @2 重复声明: let不允许重复声明「当前上下文中,不论当前变量基于何种方式声明过,都不允许再用let声明了」,一但重复声明,AST词法阶段都过不了,啥代码都不会执行;var不会重复声明,但是不会报错;
      @3 和GO的关系: 在全局上下文中,基于var/function声明的变量是放在GO中的「可以基于window.xxx去访问、也可以直接获取」;但是基于let/const声明的变量是放在VO(G)中的,和GO没关系!!
      @4 块级上下文: 在除函数/对象的大括号外,如果括号中出现let/const/function会产生块级私有上下文,而且声明的变量也是块中的私有变量;但是var既不会产生块上下文,块上下文也不会对其产生影响!!
      @5 暂时性死去问题

   LET 和 CONST 的区别?
      LET声明的是变量「对」
      CONST声明的是常量「不准确」:CONST声明的也是变量,只不过不允许重新关联其它的值(不允许指针重新指向)
 */
/* const obj = {
    name: 'zhufeng'
};
obj.name = '哈哈哈';
console.log(obj); //{name:'哈哈哈'} */

// const m = 20;
// m = 30; //Uncaught TypeError: Assignment to constant variable. 不允许重新关联

// const m; //Uncaught SyntaxError: Missing initializer in const declaration 必须赋值初始值


/* 
// console.log(typeof n); //undefined 基于typeof检测一个未被声明的变量,结果不会报错,而是“undefined”

// console.log(typeof n); //Uncaught ReferenceError: Cannot access 'n' before initialization
// let n = 20; 
*/

/* // 在词法分析阶段报错 Uncaught SyntaxError: Identifier 'n' has already been declared
console.log('OK');
let n = 10;
var n = 20; */

/* // console.log(n); //Uncaught ReferenceError: Cannot access 'n' before initialization 词法分析阶段,我们就知道未来在全局上下文中会基于let声明一个n的变量,所以此时报错是:不允许在声明之前使用他...
console.log(m); //undefined
let n = 10;
var m = 20; */


7、闭包初识

1.png

2.png


// 闭包:保护 & 保存
// 形成一个闭包(不被释放的上下文),我们就可以“预先”存储一些东西,而这些东西可以供其下级上下文“后期”调取使用 ->柯理化函数思想「预先存储的思想」

/* const fn = function fn(...params) {
    // params:[1,2]
    return function proxy(...args) {
        // args:[3]
        params = params.concat(args);
        return params.reduce(function (result, item) {
            return result + item;
        });
    };
}; */
/* const fn = (...params) => (...args) => params.concat(args).reduce((result, item) => result + item);
let res = fn(1, 2)(3);
console.log(res); //=>6  1+2+3  */

/* let arr = [10, 20, 30, 40];
// 数组中常用的迭代方法:forEach、map、filter、find、findIndex、every、some、reduce、reduceRight...
// reduce实现数组的迭代,并且可以实现结果的累计

// 第一轮:result 10  从数组第二项开始迭代  item=20  index=1  回调函数返回值30
// 第二轮:result 30(获取上一轮回调函数执行的返回结果)  item=30 index=2  返回值60
// 第三轮:result 60  item=40 index=3  返回值100
// res等于最后返回的值  100
let res = arr.reduce((result, item, index) => {
    return result + item;
});

// 第一轮  result=0  reduce第二个参数传递的值就是给result的初始值,如果不传,会把数组第一项作为初始值 , 接下来从数组第一项开始迭代  item=10 index=0 
// ...
res = arr.reduce((result, item, index) => {
    return result + item;
}, 0); */

//================
const curring = function curring() {
    let params = [];
    const add = (...args) => {
        // 每一次执行函数都把传递的值存储起来
        params = params.concat(args);
        // 每一次执行函数都返回add,这样就可以一直无限次执行下去了
        return add;
    };
    // 每一次输出add函数或者拿着个函数进行运算,都需要先把其转换为字符串/数字
    add[Symbol.toPrimitive] = () => {
        // 把每一次传递的值进行求和
        return params.reduce((result, item) => result + item);
    };
    return add;
};

let add = curring();
let res = add(1)(2)(3);
console.log(+res); //->6

add = curring();
res = add(1, 2, 3)(4);
console.log(+res); //->10

add = curring();
res = add(1)(2)(3)(4)(5);
console.log(+res); //->15


/* const fn = function fn(x, y) {
    return x + y;
};
fn[Symbol.toPrimitive] = function () {
    return 10;
};

// alert(fn); //先把fn变为字符串,然后再输出  String(fn) : Symbol.toPrimitive->valueOf->toString
// console.log(fn); //也是要把fn变为字符串再输出的,控制台会在字符串前面加一个 ƒ,代表这是函数字符串 「新版谷歌浏览器修改了机制:log输出也会执行Symbol.toPrimitive等方法,但是不论方法最后返回啥,还是输出的函数字符串」 */


//==========最原始的面试题,执行curring函数需要指定执行的次数的
/* 
const curring = function curring(count) {
    let params = [],
        n = 0;
    const add = (...args) => {
        params = params.concat(args);
        n++;
        if (n >= count) {
            // 求和
            return params.reduce((result, item) => result + item);
        }
        return add;
    };
    return add;
};

let add = curring(3);
let res = add(1)(2)(3);
console.log(res); //->6

add = curring(2);
res = add(1, 2, 3)(4);
console.log(res); //->10

add = curring(5);
res = add(1)(2)(3)(4)(5);
console.log(res); //->15 
*/

8、compose


/* 
    在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
    const add1 = (x) => x + 1;
    const mul3 = (x) => x * 3;
    const div2 = (x) => x / 2;
    div2(mul3(add1(add1(0)))); //=>3
​
    而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
    const operate = compose(div2, mul3, add1, add1)
    operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
    operate(2) //=>相当于div2(mul3(add1(add1(2))))
​
    简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请你完成 compose函数的编写 
*/
const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;

const compose = function compose(...funcs) {
    let len = funcs.length;
    if (len === 0) return x => x;
    if (len === 1) return funcs[0];
    return function operate(x) {
        return funcs.reduceRight((result, item) => {
            return typeof item === "function" ? item(result) : result;
        }, x);
    };
};

/* // redux源码中提供的compose函数
const compose = function compose(...funcs) {
    if (funcs.length === 0) return arg => arg;
    if (funcs.length === 1) return funcs[0];
    // funcs:[div2, mul3, add1, add1]
    //  a:div2  b:mul3  ->  x=>div2(mul3(x))
    //  a:x=>div2(mul3(x)) b:add1 -> x=>a(add1(x)) -> x=>div2(mul3(add1(x)))
    //  a:x=>div2(mul3(add1(x))) b:add1 -> x=>a(add1(x)) -> x=>div2(mul3(add1(add1(x))))
    return funcs.reduce((a, b) => {
        return x => {
            return a(b(x));
        };
    });
}; */

// console.log(compose(div2, mul3, add1, add1)(0)); //=>3
// console.log(compose()(0)); //=>0
// console.log(compose(add1)(0)); //=>1


/* JS高阶编程技巧:惰性思想「懒」 */

// window.getComputedStyle([element],[?伪类]) //获取当前元素所有经过浏览器计算的样式对象「但凡这个元素经过浏览器渲染了,所有的样式都可以获取到{含:自己写的、浏览器默认的...}」
//   getComputedStyle(box).width
// 不兼容IE6~8,在低版本浏览器中,需要使用  box.currentStyle.width

/* const css = function css(elem, attr) {
    if ('getComputedStyle' in window) { // in 检测某个属性是不是对象的属性「不论私有还是公有」
        return getComputedStyle(elem)[attr];
    }
    return elem.currentStyle[attr];
}; */

// 基于函数重构实现惰性思想
let css = function (elem, attr) {
    if ('getComputedStyle' in window) {
        css = function (elem, attr) {
            return getComputedStyle(elem)[attr];
        };
    } else {
        css = function (elem, attr) {
            return elem.currentStyle[attr];
        };
    }
    return css(elem, attr);
};

console.log(css(box, 'width'));
console.log(css(box, 'left'));
console.log(css(link, 'padding'));
// ... 打开页面后,当前某个方法会被执行1到多次

9、防抖截流初识


/* // 最简单的防抖处理:加标识判断即可
let submit = document.querySelector('#submit');
submit.isLoading = false;
submit.onclick = function () {
    if (this.isLoading) return;
    this.isLoading = true;
    console.log(`数据请求发送...`);
    setTimeout(() => {
        console.log(`数据请求成功...`);
        this.isLoading = false;
    }, 5000);
}; */

const clearTimer = function clearTimer(timer) {
    if (timer) clearTimeout(timer);
    return null;
};

// 具备公共性的防抖函数处理:在用户频繁操作「频繁的规则自己设定」的场景中,我们只识别一次操作即可「识别第一次、识别最后一次」
const debounce = function debounce(func, wait, immediate) {
    // 参数格式校验 & 默认值处理
    if (typeof func !== 'function') throw new TypeError('func is not a function~');
    if (typeof wait === 'boolean') immediate = wait;
    if (typeof wait !== 'number') wait = 300;
    if (typeof immediate !== "boolean") immediate = false;
    let timer = null;
    return function operate(...params) {
        let now = !timer && immediate,
            result;
        timer = clearTimer(timer);
        timer = setTimeout(() => {
            // 让方法执行的时候,THIS是实参和没有debounce之前是一样的
            // 最后执行「结束边界」
            if (!immediate) func.call(this, ...params);
            // 清除最后一次设定的定时器
            timer = clearTimer(timer);
        }, wait);
        // 立即执行「开始边界」
        if (now) result = func.call(this, ...params);
        return result;
    };
};

const handle = function handle() {
    console.log(`数据请求发送...`);
    setTimeout(() => {
        console.log(`数据请求成功...`);
    }, 3000);
};
submit.onclick = debounce(handle, 300, true);
/* // submit.onclick = function operate(ev) {
    // 疯狂点击的按钮的时候,浏览器会疯狂的触发operate函数执行;我们只需要在operate中,控制handle只执行一次即可!!
// } */

10、封装防抖


const clearTimer = function clearTimer(timer) {
    if (timer) clearTimeout(timer);
    return null;
};

// 具备公共性的防抖函数处理:在用户频繁操作「频繁的规则自己设定」的场景中,我们只识别一次操作即可「识别第一次、识别最后一次」
const debounce = function debounce(func, wait, immediate) {
    if (typeof func !== 'function') throw new TypeError('func is not a function~');
    if (typeof wait === 'boolean') immediate = wait;
    if (typeof wait !== 'number') wait = 300;
    if (typeof immediate !== "boolean") immediate = false;
    let timer = null;
    return function operate(...params) {
        let now = !timer && immediate,
            result;
        timer = clearTimer(timer);
        timer = setTimeout(() => {
            // 最后执行「结束边界」
            if (!immediate) func.call(this, ...params);
            timer = clearTimer(timer);
        }, wait);
        // 立即执行「开始边界」
        if (now) result = func.call(this, ...params);
        return result;
    };
};

// 具备公共性的节流函数处理:在用户频繁操作的场景中,我们降低触发的频率
const throttle = function throttle(func, wait) {
    if (typeof func !== 'function') throw new TypeError('func is not a function~');
    if (typeof wait !== 'number') wait = 300;
    let timer = null,
        previous = 0;
    return function operate(...params) {
        let now = +new Date(),
            remaining = wait - (now - previous),
            result;
        if (remaining <= 0) {
            // 两次触发的间隔时间超过wait,我们让函数立即执行即可「例如:第一次...」
            result = func.call(this, ...params);
            // 记录当前触发的时间,作为下一次比较的上一次时间
            previous = +new Date();
            timer = clearTimer(timer);
        } else if (!timer) {
            // 如果没有设置过定时器,而且两次触发的间隔时间也不足wait,此时我们设置一个去等待执行即可
            timer = setTimeout(() => {
                func.call(this, ...params);
                previous = +new Date();
                timer = clearTimer(timer);
            }, remaining);
        }
        return result;
    };
};


const handle = function handle() {
    console.log('OK');
};
// window.onscroll = handle; //默认的触发频率:浏览器最快的反应时间 「例如谷歌浏览器是5~7ms」
window.onscroll = throttle(handle);
/*
  window.onscroll = function operate() {
      //operate函数会在滚动中,间隔5~7ms触发一次;而在这个函数中,我们基于一些逻辑运算,控制handle间隔300ms触发一次!!
  }
 */


// 谈谈你对闭包的理解「开放性问题」
// 把握住几个维度:概念「基础理论知识」、结合实战、高阶应用「含源码阅读」、插件/组件/类库封装
// 禁止背书式回答「技巧:设定一个场景,加一些铺垫词汇」
// 禁止事无巨细「技巧:只需要回答出核心关键,或者抛出一个“敏感”词汇,剩下的引导面试官去继续问自己」
// 孔子:温良恭俭让
// --------
// ***** 作业:“剧本精神”  把这个问题的话术进行整理,大概整理3min左右的,写下来,后期面试之前背一下...

11、面向对象

/* 
   面向对象:object oriented programming OOP编程/设计思想
      + JS
      + JAVA
      + Python
      + PHP
      + C#
      + C++
      + GO
      + ...
   面向过程:procedure oriented programming POP
      + C语言

   面向对象是基于类和实例来管理一门语言的;
      @1 在JS中所有要学习和研究的内容都可以被称为“对象”、JS把其划分为很多类、我们平时就是创造这些类的实例进行开发的;
      @2 每个实例之间有一些自己的私有属性和方法,也具备类赋予他们的公共的属性和方法;
      @3 JS语言本身就是基于 “面向对象” 这种思想设计的语言,所以我们学习和开发也应该按照这种思想来进行处理!!

   JS中的内置类
      @1 数据类型所属内置类
         + Number 数字类  每一个数字都是他的一个实例「含NaN」
         + String
         + Boolean
         + Symbol
         + BigInt
         + Object
         + Function
         + Array
         + RegExp
         + Date
         + Error
         + ...
     @2 DOM元素对象也有自己所属的内置类
         + 每一种HTML标签都有自己所属的内置类  HTMLDivElement / HTMLAnchorElement ...  -> HTMLElement -> Element -> Node -> EventTarget -> Object
         + document实例 -> HTMLDocument「和 XMLDocument」 -> Document -> Node ...
         + window实例 -> Window ->  WindowProperties -> EventTarget -> Object
         + 元素集合 -> HTMLCollection -> Object
         + 节点集合 -> NodeList -> Object
     ......


    JS中的自定义类:自己创建的类「“类”有一个专业的名字“构造函数”」

 */
/* const Fn = function Fn(x, y) {
    let sum = 10;
    this.total = x + y;
    this.say = function () {
        console.log(`我计算的和是:${this.total}`);
    };
}; */
/* let res1 = Fn(10, 20); //普通函数执行
console.log(res1); //undefined
console.log(window); //window.total、window.say */

/* let f1 = new Fn(10, 20); //构造函数执行「面向对象思维处理:创造类和实例  基于NEW执行,Fn是类,f1是实例」
console.log(f1);
let f2 = new Fn(100, 200);
console.log(f2);
console.log(f1.say === f2.say); //false  构造函数体中 this.xxx=xxx 给实例设置的私有属性方法  */

/* // Fn() //普通函数执行
// Fn; //代表函数Fn的堆内存地址 没执行
let f1 = new Fn(10, 20); //把构造函数执行,可以传递实参  有参数列表NEW  优先级:20
let f2 = new Fn; //不加小括号也可以执行的,只不过不允许传递实参  优先级:19
console.log(f1, f2); */



/* 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(); */


/* function C1(name) {
    if (name) {
        this.name = name;
    }
}

function C2(name) {
    this.name = name;
}

function C3(name) {
    this.name = name || 'join';
}
C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';
alert((new C1().name) + (new C2().name) + (new C3().name));
// 'Tom' + undefined + 'join' => "Tomundefinedjoin" */


鏋勯€犲嚱鏁板拰鏅€氬嚱鏁版墽琛岀殑鍖哄埆.png

12、内置原型


/* 
// 基于内置类原型扩展方法「实例可以直接调用」:设置的名字不要和内置的方法名冲突{一般自己会设置前缀 myXxx}
Array.prototype.unique = function unique() {
    // this -> 我们要处理的数组
    return Array.from(new Set(this));
};

let arr = [1, 3, 2, 4, 3, 2, 2, 3, 4, 2, 5, 6, 3, 2, 1, 4, 5];
arr = arr.unique().sort((a, b) => a - b);
console.log(arr);

// 调用所属类原型上的内置属性方法  实例.方法()
//   + 方便
//   + 实现链式写法「执行完一个方法,返回的结果还是当前类的实例,这样就可以继续调用,类为其提供的其它方法执行」
// arr.sort((a, b) => a - b).map(item => item * 10).filter(item => item < 50).push('zhufeng'); 
*/


/* 
// 原始值类型的特殊性
//   10 是个数字,它本身是不具备键值对的,但是它是Number类的实例,可以调用Number.prototype上的方法
//      (10).toFixed(2)  装箱的操作:浏览器首先会把原始值变为对象类型的实例「new Number(10) 或者 Object(10)」,然后再继续调用toFixed等方法...
//   new Number(10) 是Number类的实例,它的结果是对象类型的,所以可以调用Number.prototype上的方法,但是它可以进行数学运算
//      let n=new Number(10);
//      n+10  拆箱的操作:把对象类型的实例变为原始值类型的值{Number(对象)}  Symbol.toPrimitive -> valueOf -> toString -> 变为数字 
*/

(function (proto) {
    // 检测num是否是合法数字
    const checkNum = num => {
        num = +num;
        return isNaN(num) ? 0 : num;
    };

    proto.plus = function plus(num) {
        num = checkNum(num);
        return this + num;
    };

    proto.minus = function minus(num) {
        num = checkNum(num);
        return this - num;
    };
})(Number.prototype);

let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //=>15(10+10-5)

鍘熷瀷鍜屽師鍨嬮摼.png

13、检测当前属性是否为对象的 私有/公有属性

/* 检测当前属性是否为对象的 私有/公有属性 */

/* // 思路一:是它的属性,但是还不是私有的,这样只能是公有的属性
// BUG:如果这个属性即是私有的也是公有的,这种方式检测就不准确了
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
    // this -> 要检测的对象  attr -> 检测的属性
    return (attr in this) && !this.hasOwnProperty(attr);
}; */

// 思路二:跳过私有的查找「无论私有有没有,都无所谓」,直接找公共的「所属类的原型  先找自己所属类的原型,没有基于__proto__再继续向上找,直到Object.prototype为止;如果中间找到返回true,找到头都没有就是false!」
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
    // this -> 要检测的对象  attr -> 检测的属性
    let proto = Object.getPrototypeOf(this);
    while (proto) {
        if (proto.hasOwnProperty(attr)) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};

let arr = [10, 20, 30];
arr.push = 'push';
console.log(arr.hasPubProperty('0')); //false
console.log(arr.hasPubProperty('push')); //true
console.log(arr.hasPubProperty('AAA')); //false


Array.png

14、函数的多种角色


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

1.png

15、函数的多种角色2


function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}

/* // Ctor:constructor缩写 想创在哪一个构造函数的实例
// params:实参集合  未来传递给构造函数的实参
function _new(Ctor, ...params) {
    // @1 创造当前类的一个实例{空实例对象}
    //   实例对象.__proto__ 指向 构造函数.prototype
    let obj = {};
    obj.__proto__ = Ctor.prototype;

    // @2 像普通函数执行一样把构造函数执行,让函数中的THIS指向创建的实例对象
    let result = Ctor.call(obj, ...params);

    // @3 看函数的返回值,如果返回的是个对象,则以自己返回的为主,否则把创建的实例对象返回
    if (result !== null && (typeof result === "object" || typeof result === "function")) return result;
    return obj;
} */

/* 重写内置NEW */
const _new = function _new(Ctor, ...params) {
    // 对Ctor的格式做校验:必须是个函数 & 必须具备prototype属性 & 不能是生成器函数 & 排除Symbol/BigInt ...
    if (Ctor === Symbol || Ctor === BigInt || Object.prototype.toString.call(Ctor) !== "[object Function]" || !Ctor.prototype) throw new TypeError(`Ctor is not a constructor`);
    // 核心处理
    let obj,
        result;
    obj = Object.create(Ctor.prototype);
    result = Ctor.call(obj, ...params);
    if (result !== null && /^(object|function)$/.test(typeof result)) return result;
    return obj;
};

let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true

2.png

16、this

/*
  THIS:函数的执行主体「谁把它执行的,和函数在哪执行以及在哪定义都没有直接的关系」
     全局上下文中的THIS是window/global
     块级私有上下文没有自己的THIS,所用到的THIS都是继承其上级上下文中的「宿主环境中的」
     所以平时我们研究的都是函数中的THIS指向
  
  @1 DOM元素事件绑定:给当前元素的某个事件行为绑定方法,当事件行为触发,方法执行,方法中的THIS是当前DOM元素
    document.body.onclick = function () {
        // 点击执行:this -> body
    };
    document.body.addEventListener('click', function () {
        // 点击执行:this -> body
    });


  @2 普通函数执行,看方法名前面是否有“点”;
      + 有“点”,那么它前面是谁THIS就是谁;
      + 没有“点”,THIS是window{非严格模式}/undefined{严格模式};
    const fn = function fn() {console.log(this);};
    let obj = {
        name: 'zhufeng',
        fn
    };
    fn(); //this->window
    obj.fn(); //this->obj
    
    arr.push(100):arr基于__proto__,找到Array.prototype.push方法,把push方法执行,实现数组新增;此时push中的this->arr;
    arr.__proto__.push(100):直接找到Array.prototype.push这个方法并且执行,此时push中的this->arr.__proto__;
    ==>内置push方法实现了“方法中的THIS是谁,就把100加入到谁的末尾”

    一般情况下,匿名函数「自执行函数 & 回调函数」执行,方法中的THIS一般是window/undefined,除了做过特殊的处理!!
      (function(){
          //this -> window/undefined
      })();
      arr.forEach(function(){
          //this -> window/undefined
      });
      // forEach等数组迭代方法,传递第二个参数的目的:修改数组每一次迭代执行的回调函数中的THIS指向「forEach内部特殊的处理」
      arr.forEach(function(){
          //this -> obj
      }, obj);


   @3 构造函数执行「NEW执行」,构造函数体中的THIS指向当前类的实例
      class Demo{
        constructor(){
            //this->d1
        }
      }
      let d1 = new Demo;


   @4 箭头函数中没有自己的THIS「类似于块级上下文」,在函数中用到的THIS是所处上下文中的;
    let obj = {
        name: 'zhufeng',
        fn() {
            // this->obj

            let self = this;
            setTimeout(function () {
                console.log(this); //this->window
                self.name = '珠峰培训';
            }, 1000);

            setTimeout(() => {
                this.name = "第二次处理"; //obj.nme='...'
            }, 1000);
        }
    };
    obj.fn();


   @5 我们可以基于call/apply/bind三个方法,简单粗暴的改变函数中的THIS指向 
       这三个方法都在Function.prototype上,每个函数都是Function类的实例,所以可以调用这些方法执行

    fn.call(obj,10,20)
      把fn执行,传递10/20两个实参,让函数中的THIS指向obj!!
      详细描述:首先fn看自己的静态私有属性方法中是否有call等方法,如果有则执行这个方法;如果没有,则默认基于__proto__去找Function.prototype上的call方法;把call方法执行的时候:
        + call中的this->fn  函数中要修改的this->obj  传递10&20
        + call方法内部,会把fn「call中的this」执行,让函数中的this指向obj,传递10&20实参!!
    fn.apply(obj,[10,20]):和call只有一个区别「从第二个参数开始,会把所有需要传递给函数的实参,以数组的形式进行处理」
    ----在三个及以上参数的情况下,call的性能要大于apply的性能

    const fn=function(n,m){};
    document.body.onclick=fn; //this->body   n->ev  m->undefined
    // 基于call/apply改变THIS的时候,会同时需要执行的把函数立即执行「立即执行函数、立即改变THIS」;而bind不会立即改变this,也不会立即把函数执行,而是预先把传递的obj、10、29以及最后要执行的方法fn先存储起来!!
    document.body.onclick=fn.bind(obj,10,20)
 */

/* Function.prototype.call = function call(context, ...params) {
    // this->fn  context->obj  params->[10,20]
    // 把fn执行,让fn中的this指向obj,并且把10/20传递给fn,接收其返回值然后再还回
    // 如果context是原始值类型的值,则需要把其变为对象类型「原因:原始值设置属性属性是无效的」
    if (context == null) context = window;
    if (!/^(object|function)$/.test(typeof context)) context = Object(context);
    let key = Symbol('KEY'), //新增的属性不能和context原始属性冲突
        result;
    context[key] = this; //让两者有关联
    result = context[key](...params); //把方法执行,而且方法中的this就是context
    delete context[key]; //把新增的属性移除掉
    return result; //把函数执行的返回值返回
};

const fn = function fn(x, y) {
    console.log(this, x, y);
    return x + y;
};
let obj = {
    name: 'zhufeng',
    xxx: 100
};
// fn(); //this->window
// obj.fn(); //Uncaught TypeError: obj.fn is not a function
let res = fn.call(1000, 10, 20);
console.log(res); */


// =================
Function.prototype.bind = function bind(context, ...params) {
    // this->fn  context->obj  params->[10,20]
    let self = this;
    // 执行bind返回一个代理函数,把代理函数绑定给元素的事件;事件触发,先执行代理函数,在代理函数中再把我们需要做的事情处理了!
    return function proxy(...args) {
        // args->[ev]
        params = params.concat(args);
        return self.call(context, ...params);
    };
};

const fn = function fn(x, y, ev) {
    console.log(this, x, y, ev);
};
let obj = {
    name: 'zhufeng'
};
// document.onclick = fn; //this->document  x->MouseEvent  y->undefined  ev->undefined
// document.onclick = fn.call(obj, 10, 20); //这样处理不行,call是立即执行函数,也就是还没等待点击,fn就执行了

document.onclick = fn.bind(obj, 10, 20); //this->obj  x->10  y->20  ev->MouseEvent
/* document.onclick = function proxy(ev) {
    // this->document  ev->MouseEvent
    // 核心目的是执行fn
    fn.call(obj, 10, 20, ev);
}; */


17、检测数据类型


/* 
 JS中检测数据类型的办法
    typeof [value]:检测数据类型的运算符
        @1 返回结果是一个字符串,其次字符串中包含对应的数据类型,例如:“number”、“object”、“function”...
           typeof typeof typeof [1,2,3] => "string"
        @2 弊端
           + typeof null -> “object”
           + typeof 对象 -> 除函数对象被识别为“function”,其余的对象都是返回“object”
           + typeof 未被声明的变量 -> 不会抱错,而是“undefined”
           除了这些以外,用typeof检测原始值类型「或者函数类型」还是非常的方便、准确的
        @3 检测的原理
           + 所有的数据类型值在计算机底层都是按照二进制来进行存储的,而typeof检测类型,也是按照二进制值来进行检测的「特点:性能好」;所有以 000 开始的,都是对象类型,再排查一下有没有实现call,实现的是函数对象,没实现的就是其余的对象...而null这个值存储的二进制都是零,所以也被识别为“object”!!

    instanceof:检测当前实例是否属于这个类「临时拉来做数据类型检测」
        @1 实例 instanceof 构造函数,返回true/false
        @2 可以做一些数据类型的检测「实现对typeof的补充,可以实现对象的细分」
        @3 弊端
           + 无法基于instanceof检测是否为标准普通对象「纯粹对象:直接是Object类的实例」;因为所有对象都是Object类的实例,基于“xxx instanceof Object” 检测的时候,返回结果都是true;
           + 无法检测原始值类型的值;因为基于instanceof检测的时候,默认不会进行“装箱”操作!!
             例如:10 instanceof Number -> false
           + 检测的结果不一定严谨;因为,原型链以及所属构造函数是谁,是可以用户自己去改变的!!
           + ...
        @4 当我们基于 “[value] instanceof [Ctor]” 运算符进行数据类型的检测的时候
           + 首先调用 [Ctor][Symbol.hasInstance]([value]) 这个函数,如果存在这个函数,则直接基于这个函数处理即可
             当代浏览器基本都有,因为Symbol.hasInstance在Function.prototype中,每一个构造函数都是Function的实例,都可以调用Function.prototype[Symbol.hasInstance]这个方法
           + 如果不存在这个函数,浏览器会按照当前[value]原型链一层层向上查找,直到找到Object.prototype为止;查看[Ctor].prototype是否出现在它的原型链中,如果出现了,则结果是true,说明[value]是[Ctor]的实例,反之则为false...
        @5 分析instanceof的优缺点和底层实现机制,并且重写instanceof
        
    constructor:获取当前实例所属的构造函数「临时」
        @1 [value].constructor 获取其构造函数,验证是否为我们想检测的类
           例如:[value].constructor===Array
        @2 相比较于instanceof来讲
           + 可以检测原始值类型的值「排除null/undefined」
           + 检测是否为标准普通对象 [value].constructor===Object 
           + 和instanceof一样,检测的结果仅供参考「constructor这个值是可以被肆意修改的」
        
    Object.prototype.toString.call([value]):专门检测数据类型的办法
        @1 调用Object.prototype.toString方法,让方法中的this指向检测的值,就是检测当前值的数据类型;
           返回结果是一个字符串 “[object ?]”
           例如:Object.prototype.toString.call(10) -> "[object Number]"
                Object.prototype.toString.call(new Number(10)) -> "[object Number]"
           它是所有检测数据类型的办法中,最强大、最稳定...的方式{除了写起来麻烦一些}
        @2 返回结果是 “[object ?]”,“?” 会是啥呢?
           + 首先获取[value][Symbol.toStringTag]属性值,如果存在这个属性,则这个属性值是啥,“?”就是啥!
           + 如果没有这个属性,一般“?”是当前实例所属的构造函数!!
           Symbol.prototype & BigInt.prototype & Math & GeneratorFunction.prototype & Promise.prototype & Set.prototype & Map.prototype ... 这些类的原型上,都有Symbol.toStringTag这个属性!!

        Object.prototype.toString这个方法是用来检测数据类型的,而且方法内部规定:方法中的this是谁,我们就检测谁的类型,所以我们基于call方法去改变this指向!!!

 -------
 Array.isArray:检测是否为数组
 isNaN:检测是否为有效数字
 */

// alert({
//     name: 'xxx'
// });  //=>“[object Object]”   alert会把编写的值转换为字符串宰输出,而对象toString的时候,调用的是Object.prototype.toString这个方法,而这个方法是检测数据类型的

// 真正转换标准普通对象为字符串
//   + JSON.stringify 把对象变为 JSON 格式字符串   ->JSON.parse
//   + Qs.stringify 依托Qs第三方库,我们把对象变为 urlencoded 格式字符串
// 前后端数据通信的时候,我们经常需要把对象变为指定的字符串格式,传递给服务器;或者把服务器返回的指定格式字符串,变为对象!!
/* 
let obj = {
    name: 'zhufeng',
    age: 12,
    teacher: 'zhou'
};
// console.log(JSON.stringify(obj)); //'{"name":"zhufeng","age":12,"teacher":"zhou"}'
console.log(Qs.stringify(obj)); //'name=zhufeng&age=12&teacher=zhou' */


//-------------Object.prototype.toString.call
// let obj = {},
//     toString = obj.toString; //->Object.prototype.toString  基本上所有的数据类型,所属构造函数的原型上都有toString方法,一般都是用来转换为字符串的,只有Object.prototype.toString是用来检测数据类型的
// console.log(toString.call(10)); //"[object Number]"
// console.log(toString.call(new Number(10))); //"[object Number]"
// console.log(toString.call("zhufeng")); //"[object String]"
// console.log(toString.call(true)); //"[object Boolean]"
// console.log(toString.call(null)); //"[object Null]"
// console.log(toString.call(undefined)); //"[object Undefined]"
// console.log(toString.call(Symbol())); //"[object Symbol]"
// console.log(toString.call(10n)); //"[object BigInt]"
// console.log(toString.call({})); //"[object Object]"
// console.log(toString.call([])); //"[object Array]"
// console.log(toString.call(/^$/)); //"[object RegExp]"
// console.log(toString.call(function () {})); //"[object Function]"
// console.log(toString.call(new Date())); //"[object Date]"
// console.log(toString.call(new Error())); //"[object Error]"
// console.log(toString.call(Math)); //"[object Math]"
// console.log(toString.call(function* () {})); //"[object GeneratorFunction]"
// console.log(toString.call(Promise.resolve())); //"[object Promise]"
// 即使constructor值被修改 或者 基于Object.setPrototypeOf重定向实例的原型指向,结果也是不变的!!所以 toString.call 这种办法检测的结果是非常可靠的!!


/* 
// const Fn = function Fn() {};
// let f = new Fn;
// console.log(toString.call(f)); //“[object Object]”
// 需求:期望自己写自定义构造函数,所创建出来的实例在检测数据类型的时候,可以返回的是“[object 自己的构造函数]”

const Fn = function Fn() {};
Fn.prototype[Symbol.toStringTag] = "Fn";
let f = new Fn;
console.log(toString.call(f)); //“[object Fn]” 
*/


//--------------constructor
// let arr = [];
// let reg = /^$/;
// let num = 10;

// console.log(arr.constructor === Array); //true
// console.log(arr.constructor === Object); //false
// console.log(reg.constructor === Array); //false
// console.log(num.constructor === Number); //true


//--------------instanceof
// let obj = {};
// let arr = [];
// let reg = /^$/;
// let num = new Number(10);

// console.log(arr instanceof Array); //true 
// console.log(obj instanceof Array); //false
// console.log(reg instanceof Array); //false
// console.log(typeof num); //"object"
// console.log(num instanceof Number); //true 说明num是Number类的一个实例「原始值对应的对象类型结果」

// console.log(arr instanceof Array); //true 
// console.log(arr instanceof Object); //true  
// console.log(reg instanceof Object); //true  
// console.log(obj instanceof Object); //true  
// console.log(num instanceof Object); //true  

/* const Fn = function Fn() {};
Fn.prototype = Array.prototype;
let f = new Fn;
console.log(f); //从结构来看,f一定不是数组「数组的结构:数字索引、逐级递增、length属性...」
console.log(f instanceof Array); //true */


/* const Fn = function Fn() {
    this.name = 'zhufeng';
};
Fn.prototype.sayName = function () {};
Fn.xxx = 'xxx';
Fn[Symbol.hasInstance] = function () { //这样设置是无效的
    console.log(1);
    return false;
};
Function.prototype[Symbol.hasInstance] = function () { //这样设置也是无效的
    console.log(1);
    return false;
};
let f = new Fn;
console.log(f instanceof Fn); */


/* class Fn {
    name = 'zhufeng';
    sayName() {}
    // 当做对象,设置静态私有的属性方法「这样设置是有用的,所以重构“构造函数”的Symbol.hasInstance,只支持ES6中class创建的类,ES5中创建的构造函数不支持这样重构」
    static xxx = 'xxx';
    static[Symbol.hasInstance](obj) {
        console.log(obj);
        return false;
    }
}
let f = new Fn;
console.log(f instanceof Fn); //false */


// instance_of:检测value是否为Ctor的实例
// value:要检测的实例
// Ctor:要检测的构造函数
const instance_of = function instance_of(value, Ctor) {
    // 保证Ctor的格式也是正确的
    if (typeof Ctor !== "function") throw new TypeError('Right-hand side of instanceof is not callable');
    if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check');

    // 不支持原始值类型值的检测
    if (value === null) return false;
    if (!/^(object|function)$/.test(typeof value)) return false;

    // 支持Symbol.hasInstance方法的直接按照这个处理
    if (typeof Ctor[Symbol.hasInstance] === "function") return Ctor[Symbol.hasInstance](value);

    // 不支持的则按照原型链一层层的查找即可  Object.getPrototypeOf(value):获取value所属构造函数的原型对象
    let proto = Object.getPrototypeOf(value);
    while (proto) {
        // Ctor.prototype出现在了value的原型链上「value是Ctor的实例对象」:直接返回true & 结束查找
        if (proto === Ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};

宸ュ巶璁捐妯″紡.png

18、jquery


(function (global, factory) {
    "use strict";
    if (typeof module === "object" && typeof module.exports === "object") {
        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) {
    "use strict";
    var arr = [];
    var slice = arr.slice;
    var push = arr.push;
    var indexOf = arr.indexOf;

    //-----
    var jQuery = function (selector, context) {
        return new jQuery.fn.init(selector, context);
    };
    jQuery.fn = jQuery.prototype = {
        jquery: "3.6.0",
        constructor: jQuery,
    };

    //-----
    var rootjQuery = jQuery(document),
        rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
        init = jQuery.fn.init = function (selector, context, root) {
            var match, elem;
            if (!selector) return this;
            root = root || rootjQuery; //$(document)
            if (typeof selector === "string") {
                if (selector[0] === "<" &&
                    selector[selector.length - 1] === ">" &&
                    selector.length >= 3) {
                    match = [null, selector, null];
                } else {
                    match = rquickExpr.exec(selector);
                }
                if (match && (match[1] || !context)) {
                    if (match[1]) {
                        context = context instanceof jQuery ? context[0] : context;
                        jQuery.merge(this, jQuery.parseHTML(
                            match[1],
                            context && context.nodeType ? context.ownerDocument || context : document,
                            true
                        ));
                        if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
                            for (match in context) {

                                // Properties of context are called as methods if possible
                                if (isFunction(this[match])) {
                                    this[match](context[match]);

                                    // ...and otherwise set as attributes
                                } else {
                                    this.attr(match, context[match]);
                                }
                            }
                        }

                        return this;

                        // HANDLE: $(#id)
                    } else {
                        elem = document.getElementById(match[2]);
                        if (elem) {
                            this[0] = elem;
                            this.length = 1;
                        }
                        return this;
                    }
                } else if (!context || context.jquery) {
                    return (context || root).find(selector);
                } else {
                    return this.constructor(context).find(selector);
                }
            } else if (selector.nodeType) {
                this[0] = selector;
                this.length = 1;
                return this;
            } else if (isFunction(selector)) {
                return root.ready !== undefined ?
                    root.ready(selector) :
                    selector(jQuery);
            }
            return jQuery.makeArray(selector, this);
        };
    init.prototype = jQuery.fn;

    // 创造数组或者类数组
    jQuery.makeArray = function (arr, results) {
        var ret = results || []; //当前案例:JQ实例对象
        if (arr != null) {
            if (isArrayLike(Object(arr))) {
                jQuery.merge(ret,
                    typeof arr === "string" ? [arr] : arr
                );
            } else {
                push.call(ret, arr);
            }
        }
        return ret;
    };

    // 合并两个数组或者类数组「把第二个集合,合并在第一个集合中」
    jQuery.merge = function (first, second) {
        var len = +second.length,
            j = 0,
            i = first.length;
        for (; j < len; j++) {
            first[i++] = second[j];
        }
        first.length = i;
        return first;
    };

    /* 暴露API */
    if (typeof define === "function" && define.amd) {
        define("jquery", [], function () {
            return jQuery;
        });
    }
    if (typeof noGlobal === "undefined") window.jQuery = window.$ = jQuery;
    return jQuery;
});

/* 
JQ选择器:$([selector]) 后者 jQuery([selector])  => 把私有jQuery方法当做普通函数执行 
   + new jQuery.fn.init/init(selector, context) 创在了init类的实例“@A”
   + @A.__proto__===init.prototype  而 init.prototype===jQuery.prototype
   + @A.__proto__===jQuery.prototype
   + 说明JQ选择器获取的结果是jQuery类的实例对象
   面试题:把一个方法当做普通函数执行,能否获取到当前这个构造函数的实例对象吗?

JQ对象:JQ的实例对象「可以调用jQuery.fn上的属性和方法,但是浏览器内置方法无法调取」
   它是一个类数组集合,集合中每一项存储的就是DOM元素对象
DOM元素对象:基于JS内置属性和方法,获取的DOM对象「不是JQ实例,不能调用jQuery.fn上的属性和方法,但是可以调用浏览器内置方法」

[selector]的各种情况:
   + 传递的是“假「false/0/NaN/""/null/undefined」”:返回空的
   + 传递的是字符串:
        新创建的HTML字符串:按照HTML字符串动态创建对应的DOM的元素,最后插入到页面在
        样式选择器字符串,就是为了获取指定DOM元素
   + 传递的是DOM元素对象:把DOM元素对象,变为JQ实例对象
   + 传递的是个函数:$(function(){}) <=> $(document).ready(function(){})  监听DOMContentLoaded事件「而且是基于DOM2事件绑定监听的」,所以:它会等待页面结构一加载完(DOM TREE生成完),触发回调函数执行!!而且同一个页面可以使用多次!!
   + 传递的是其它值
        数组或者类数组:把传递进来的数组和类数组和JQ实例对象合并,最后返回的还是JQ实例对象
        不是数组或者类数组:把传递进来的值,直接添加到JQ实例的末尾
   不管传递啥,最后返回的都是JQ实例对象
*/

19、异步任务分析一

/* 
console.log(1);
while (1) {} //循环操作是同步编程:如果设置了死循环,当前循环这个事情永远结束不了,后期的所有其他任务也都无法执行「避免出现死循环」
console.log(2); 
*/

/* 
setTimeout(() => {
    console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
    console.log(3);
}, 10);
console.log(4);
for (let i = 0; i < 90000000; i++) {
    // do soming  预估需要90ms循环才可以结束
}
console.log(5);
setTimeout(() => {
    console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
    console.log(8);
}, 15);
console.log(9); 
*/

//==============
/* 
  实例:
    [[PromiseState]]:状态 pending/fulfilled/rejected
    [[PromiseResult]]:值 undefined
    new Promise的时候会立即执行executor函数 

  实例.then(onfulfilled,onrejected)
    @1 首先观察实例的状态,如果此时已经知道实例是成功或者失败的状态
       + 创建一个异步“微任务”「放在WebAPI中去监听,监听的时候知道它的状态,所以直接把执行的哪个方法,挪至到EventQueue中排队等着」
       + 状态是成功,后期执行的是onfulfilled
       + 状态是失败,后期执行的是onrejected
    @2 如果实例此时的状态还是pending
       + 把onfulfilled/onrejected先存储到指定的容器中 「放在WebAPI中监听状态的改变」
       + 当后期执行resolve/reject的时候
         + 立即修改实例的状态和值「同步」
         + 创建一个异步微任务,后面让指定容器中的方法执行 「挪至到EventQueue中排队等着」

  new Promise产生的实例,状态是成功还是失败,由 executor执行是否报错{执行报错,则实例是失败态}、以及resolve还是reject执行决定
  Promise.resolve(100):直接创建状态是成功的实例
  Promise.reject(100):直接创建状态是失败的实例
  ---
  “实例.then”会返回一个全新的promise实例「p2」,这个实例的成功失败,由p1管控的onfulfilled或者onrejected不论哪个方法执行决定
     + 方法执行是否返回新的promise实例,如果没有返回:
       + 方法执行只要不报错,则p2就是成功的,值就是函数返回值
       + 执行报错,则p2是失败的,值是报错原因
     + 如果返回的是新的promise实例「new-promise」,则new-promise的状态和值决定了p2的状态和值
*/
/* let p1 = Promise.resolve(100);
let p2 = p1.then(value => {
    console.log('成功:', value);
    return Promise.reject(value * 10);
}, reason => {
    console.log('失败:', reason);
    return reason / 10;
});
// 因为p1管理的onfulfilled方法还在EventQueue中排队等待执行,所以此时p2的状态还是pending
//  + 把onfulfilled/onrejected存储起来,放在WebAPI监听,监听p2状态的改变「把p1管控的onfulfilled执行就知道了」
//  + 当p1对应的onfulfilled执行后,把p2变为失败,此时把p2对应的onrejected挪至到EventQueue中排队等着执行...
p2.then(value => {
    console.log('成功:', value);
}, reason => {
    console.log('失败:', reason);
}); */

/* console.log(1);
let p1 = new Promise(resolve => {
    console.log(2);
    setTimeout(() => {
        console.log(3);
        resolve(100);
        console.log(4);
    }, 1000);
    console.log(5);
});
console.log(6);
p1.then(value => {
    console.log('成功:', value);
}, reason => {
    console.log('失败:', reason);
});
console.log(7);
// 答案:1 2 5 6 7 ...过1S后... 3 4 成功:100  */

/* 
console.log(1);
let p1 = new Promise(function executor(resolve, reject) {
    console.log(2);
    resolve(100); //立即修改实例的状态和值
    // reject(0);
    console.log(3);
});
console.log(4);
p1.then(function onfulfilled(value) {
    console.log('成功:', value);
}, function onrejected(reason) {
    console.log('失败:', reason);
});
console.log(5);
// 答案:1 2 3 4 5 成功:100 
*/

//============
/* 
async/await:是promise+generator的语法糖 
  + async修饰的函数,默认返回值会变为一个promise实例 
  + 必须在函数中使用await,而且当前函数需要经过async修饰
*/

/* let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(100);
    }, 1000);
}); */
/* p1.then(value => {
    console.log('成功:', value);
}).catch(reason => { //.catch === .then(null,onrejected)
    console.log('失败:', reason);
}); */

/* (async function () {
    // await 后面放置的是promise实例「如果不是,则默认变为状态是成功,值是这个值的promise实例」
    //   + 把当前上下文中,await“下面的代码”作为一个“异步微任务”,放在WebAPI中去监听,监听实例的状态
    //   + 如果状态是成功,把其“下面的代码执行”挪至到EventQueue中排队等着 
    //   + 如果状态是失败,则下面代码不会执行
    let value = await p1;
    console.log('成功:', value);
})(); */

/* (async function () {
    // 捕获await后面实例是失败的?
    try {
        let value = await p1;
        console.log('成功:', value);
    } catch (err) {
        console.log('失败:', err);
    }
})(); */

//--------
// async function fn() {
//     return 1;
// }
// console.log(fn()); //promise实例: 状态fulfilled 值1

// async function fn() {
//     console.log(num);
//     return 1;
// }
// console.log(fn()); //状态rejected 值是报错原因

// async function fn() {
//     return Promise.reject('NO');
// }
// console.log(fn()); //返回值本身是promise实例,则以自己返回为主


1.png

20、异步任务分析二

console.log("script start");
setTimeout(() => {
    console.log("setTimeout");
}, 1000);
Promise.resolve()
    .then(function () {
        console.log("promise1");
    })
    .then(function () {
        console.log("promise2");
    });
async function errorFunc() {
    try {
        await Promise.reject("error!!!");
    } catch (e) {
        console.log("error caught");
    }
    console.log("errorFunc");
    return Promise.resolve("errorFunc success");
}
errorFunc().then((res) => console.log("errorFunc then res"));
console.log("script end");


2.png

3.png

4.png

5.png

6.png

7.png

21、promise

(function () {
    "use strict";
    // 检测是否为函数
    var isFunction = function isFunction(obj) {
        return typeof obj === "function" && typeof obj.nodeType !== "number" &&
            typeof obj.item !== "function";
    };

    // 关于onfulfilled/onrejected执行返回值x的处理
    var resolvePromise = function resolvePromise(promise, x, resolve, reject) {
        if (promise === x) throw new TypeError('Chaining cycle detected for promise #<Promise>');
        if (x !== null && /^(object|function)$/.test(typeof x)) {
            var then;
            try {
                then = x.then;
            } catch (err) {
                reject(err);
            }
            if (typeof then === "function") {
                // 是个promise实例
                var called = false;
                try {
                    then.call(
                        x,
                        function onfulfilled(y) {
                            if (called) return;
                            called = true;
                            resolvePromise(promise, y, resolve, reject);
                        },
                        function onrejected(r) {
                            if (called) return;
                            called = true;
                            reject(r);
                        }
                    );
                } catch (err) {
                    if (called) return;
                    called = true;
                    reject(err);
                }
                return;
            }
        }
        resolve(x);
    };

    // 验证是否为promise实例
    var isPromise = function isPromise(x) {
        if (x !== null && /^(object|function)$/.test(typeof x)) {
            var then;
            try {
                then = x.then;
            } catch (err) {
                return false;
            }
            if (typeof then === "function") {
                return true;
            }
        }
        return false;
    };

    /* 核心部分 */
    function Promise(executor) {
        if (!isFunction(executor)) throw new TypeError("executor is not a function");
        if (!(this instanceof Promise)) throw new TypeError("undefined is not a promise");
        var self = this;
        self.state = "pending";
        self.result = undefined;
        self.onfulfilledCallbacks = [];
        self.onrejectedCallbacks = [];
        var change = function change(state, result) {
            if (self.state !== "pending") return;
            self.state = state;
            self.result = result;
            setTimeout(function () {
                var callbacks = state === "fulfilled" ? self.onfulfilledCallbacks : self.onrejectedCallbacks;
                for (var i = 0; i < callbacks.length; i++) {
                    callbacks[i](self.result);
                }
            });
        };
        try {
            executor(
                function resolve(value) {
                    change("fulfilled", value)
                },
                function reject(reason) {
                    change("rejected", reason)
                }
            );
        } catch (err) {
            change("rejected", err);
        }
    };

    /* 原型属性方法 */
    Promise.prototype = {
        constructor: Promise,
        then: function then(onfulfilled, onrejected) {
            // 如果onfulfilled/onrejected不传递,我们需要实现顺延(穿透)效果
            if (typeof onfulfilled !== "function") {
                onfulfilled = function onfulfilled(value) {
                    return value;
                };
            }
            if (typeof onrejected !== "function") {
                onrejected = function onrejected(reason) {
                    throw reason;
                };
            }
            var self = this,
                promise;
            promise = new Promise(function (resolve, reject) {
                switch (self.state) {
                    case "fulfilled":
                        setTimeout(function () {
                            try {
                                var x = onfulfilled(self.result);
                                resolvePromise(promise, x, resolve, reject);
                            } catch (err) {
                                reject(err);
                            }
                        });
                        break;
                    case "rejected":
                        setTimeout(function () {
                            try {
                                var x = onrejected(self.result);
                                resolvePromise(promise, x, resolve, reject);
                            } catch (err) {
                                reject(err);
                            }
                        });
                        break;
                    default:
                        self.onfulfilledCallbacks.push(function (value) {
                            try {
                                var x = onfulfilled(value);
                                resolvePromise(promise, x, resolve, reject);
                            } catch (err) {
                                reject(err);
                            }
                        });
                        self.onrejectedCallbacks.push(function (reason) {
                            try {
                                var x = onrejected(reason);
                                resolvePromise(promise, x, resolve, reject);
                            } catch (err) {
                                reject(err);
                            }
                        });
                }
            });
            return promise;
        },
        catch: function mycatch(onrejected) {
            return this.then(null, onrejected);
        }
    };

    /* 静态属性方法 */
    Promise.resolve = function resolve(value) {
        return new Promise(function (resolve) {
            resolve(value);
        });
    };
    Promise.reject = function reject(reason) {
        return new Promise(function (_, reject) {
            reject(reason);
        });
    };
    Promise.all = function all(promises) {
        if (!/^\[object Array\]$/.test(Object.prototype.toString.call(promises))) throw new TypeError('promises is not a array');
        var n = 0,
            values = [];
        return new Promise(function (resolve, reject) {
            for (var i = 0; i < promises.length; i++) {
                (function (i) {
                    var promise = promises[i];
                    if (!isPromise(promise)) promise = Promise.resolve(promise);
                    promise.then(
                        function onfulfilled(value) {
                            n++;
                            values[i] = value;
                            if (n >= promises.length) resolve(values);
                        },
                        function onrejected(reason) {
                            reject(reason);
                        }
                    );
                })(i);
            }
        });
    };

    /* 规范测试 */
    Promise.deferred = function deferred() {
        var result = {};
        result.promise = new Promise(function (resolve, reject) {
            result.resolve = resolve;
            result.reject = reject;
        });
        return result;
    };

    /* 暴露API */
    if (typeof module === "object" && typeof module.exports === "object") module.exports = Promise;
    if (typeof window !== "undefined") window.Promise = Promise;
})();


22、全局变量以及局部变量




23、全局变量以及局部变量