珠峰web高级7期

js数据类型

基本类型:Number String Boolean null undefined Symbol Bigint

引用类型:Object Function

Number

NaN: not a number 不是一个有效数字,但是属于number类型的

NaN和NaN本身不相等,和其它值也都不等

  • isNaN([value]):检测当前值是否不是效数字,不是有效数字返回true,反之是有效数字则返回false
  • Object.is(NaN, NaN):true 「is方法内部做了特殊的处理」

Infinity:无限大 -Infinity:无限小

把其它数据类型转换为number类型

  • 显式转换「 Number([value]) | parseInt/parseFloat([value]) 两者的底层处理规则是不一样的」
  • 隐式转换 -> Number
    • 数学运算
    • 基于==比较的时候
    • isNaN([value])
console.log(typeof NaN); //->'number'
console.log(typeof Infinity); //->'number'
console.log(NaN === NaN); //->false
console.log(Object.is(NaN, NaN)); //->true
复制代码

String

把其它值转换为字符串

  • 显式:String([value]) 或者 [value].toString() 「->延展出数据类型检测」
  • 隐式:
    • 加号除了数学运算,还会产生字符串拼接
let n = '10',
    m = 10;
console.log(10 + n); // “+”有一边出现了字符串「前提:有两边」会变为字符串拼接 '1010'
console.log(+n); // “+”只有一边,把值转换为数字  10
console.log(++n); // “++”和上面一样也是转换为数字,但是还会自身累加一   11
// i=i+1 i+=1  这两个一样    
// i++/++i 大部分情况和上面一样,但是如果i本身的值是字符串则不一致「上面会处理为字符串拼接,下面是数学累加」

// 如果“+”两边,有一边是对象,则也可能会成为字符串拼接
//   + 10+{} 或者 10+{name:'zhufeng'} -> '10[object Object]'
//   + 10+[10] -> "1010"
//   特殊:
//   + 10+new Number(10) -> 20
//   + {}+10 或者 {name:'xxx'}+10 -> 10   「原因:{...}没有参与运算,浏览器认为其是一个代码块,计算‘+10’」
//   + ({}+10) -> '[object Object]10'
//   + let x={}+10 -> '[object Object]10'  下面两种不论是包起来还是赋值,从语法分析上都要参与到计算中了
//   + ...

底层机制:对象在做数学运算的时候
//   + 检测对象的 Symbol.toPrimitive 这个属性值,如果有则基于这个值进行运算,如果没有
//   + 检测对象的 valueOf() 这个值「原始值:基本类型值」,如果有则基于这个值进行运算,如果不是原始值
//   + 获取对象的 toString() 把其变为字符串  -> 如果是‘+’处理,则看到字符串了,所以变为字符串拼接
//   + 如果最后就是想变为数字,则再把字符串转换为数字即可

// let obj = {};
// console.log(10 + obj); //->'10[object Object]'


let obj = {
    [Symbol.toPrimitive]: function (hint) {
        // hint:记录浏览器识别出来的,你会把其转换为什么类型的 default/string/number ...
        console.log(hint);
        return 10;
    }
};
console.log(10 + obj); //20  hint->default
console.log(10 + new Number(10)); //20   new Number(10).valueOf() 10
console.log(Number(obj)) //10  hint->number
console.log(String(obj)) //'10'  hint->string
复制代码

Boolean

0 "" null undefined NaN   ---> false
复制代码

null undefined

console.log(null == undefined) //true 两者和其他都不等
复制代码

Symbol() 创建唯一值

//给对象设置一个Symbol属性:唯一属性 「减少属性处理上的冲突」
//宏观管理一些唯一的标识的时候,也是用唯一值
// new Symbol(); //Uncaught TypeError: Symbol is not a constructor
// console.log(Symbol('AA') === Symbol('AA')); //->false

/* let obj = {
    [Symbol()]: 100
};
console.log(obj[Symbol()]); //->undefined */

/* let x = Symbol();
let obj = {
    [x]: 100
};
console.log(obj[x]); //->100 */

/* 
// 很多JS的内置原理都是基于这些Symbol的属性来处理的
Symbol.toPrimitive
Symbol.hasInstance
Symbol.toStringTag
Symbol.iterator
... 
*/
复制代码

BigInt() 大数

BigInt([num])
xxxn
//大型项目中,服务器返回给客户端的数据中可能出现大数「服务器数据库中可以基于longint存储数值,这个值可能会超过最大安全数字」
// Number.MAX_SAFE_INTEGER : 9007199254740991 最大安全数字
// Number.MIN_SAFE_INTEGER
// 超过安全数字进行运算,结果是不准确的
复制代码

四大数据类型转换规则

数据类型的检测

  • typeof
    • 检测出来的结果是字符串
    • typeof null -> ‘object’ 不属于对象,而是因为二进制存储值以000开头
    • 检测对象细分的类型,结果都是’object’
  • instanceof
  • constructor
  • Object.prototype.toString.call([value])
/*
 * 把其他数据类型转换为Number类型
 *     1.特定需要转换为Number的
 *       + Number([val])
 *       + parseInt/parseFloat([val])
 *     2.隐式转换(浏览器内部默认要先转换为Number在进行计算的)
 *       + isNaN([val])
 *       + 数学运算(特殊情况:+在出现字符串的情况下不是数学运算,是字符串拼接)
 *       + 在==比较的时候,有些值需要转换为数字再进行比较
 *       + ...
 *     .......
 */

/*
 * 把其它数据类型转换为字符串
 *    1. 能使用的办法
 *      + toString()
 *      + String()
 *    2. 隐式转换(一般都是调用其toString)
 *      + 加号运算的时候,如果某一边出现字符串,则是字符串拼接
 *      + 把对象转换为数字,需要先toString()转换为字符串,再去转换为数字
 *      + 基于alert/confirm/prompt/document.write...这些方式输出内容,都是把内容先转换为字符串,然后再输出的
 *      + ...
 *    ......
 */

/*
 * 把其它数据类型转换为布尔
 *    1. 基于以下方式可以把其它数据类型转换为布尔
 *      + ! 转换为布尔值后取反
 *      + !! 转换为布尔类型
 *      + Boolean([val])
 *    2. 隐式转换
 *      + 在循环或者条件判断中,条件处理的结果就是布尔类型值
 *      + ...
 * 
 * 规则:只有 ‘0、NaN、null、undefined、空字符串’ 五个值会变为布尔的FALSE,其余都是TRUE
 */

/* 
 * 在==比较的过程中,数据转换的规则:
 *  【类型一样的几个特殊点】
 *     {}=={}:false  对象比较的是堆内存的地址
 *     []==[]:false
 *     NaN==NaN:false
 *  【类型不一样的转换规则】
 *     1. null==undefined:true,但是换成===结果是false(因为类型不一致),剩下null/undefined和其它任何数据类型值都不相等
 *     2. 字符串==对象  要把对象转换为字符串
 *     3. 剩下如果==两边数据类型不一致,都是需要转换为数字再进行比较
 */
复制代码
console.log([] == false) //true
// 对象 == 布尔 都转换为数字(隐式转换)
// 对象转换为数字:先toString转换为字符串(应该是先基于valueOf获取原始值,没有原始值再toString),再转换为数字
// [] -> '' -> 0  false -> 0  true -> 1
console.log(![] == false) // true
// ![] -> false

//把其他类型转换为字符串,一般都是直接用''包起来,只有{}普通对象调取的Object.prototype.toString,不是转换为字符串,而是检测数据类型,返回结果是'[object Object]'

//把其他类型转换为数字  
// Number机制
console.log(Number('')) // 0
console.log(Number('10')) // 10
console.log(Number('10px')) // NaN 只要出现非有效数字字符结果都是NaN
console.log(Number(true)) // 1
console.log(Number(false)) // 0
console.log(Number(null)) // 0
console.log(Number(undefined)) // NaN
console.log(Number(Symbol(10))) // 报错
console.log(Number(BigInt(10))) // 10
//对象转化为数字,先valueOf,没有原始值再toString转换为字符串,最后把字符串转换为数字

// parseInt机制
// 从字符串第一位开始,查找有效数字字符(遇到非有效数字字符停止查找,不论后面是否还有数字字符),把找到的有效数字字符转换为数字,如果一个都没有找到返回NaN parseFloat比parseInt多识别一个小数点
parseInt('') // NaN
Number('') // 0
isNaN('') // false
parseInt(null) // NaN
Number(null) // 0
isNaN(null) // false
parseInt('12px') // 12
Number('12px') // NaN
isNaN('12px') // true
parseFloat('1.6px') + parseInt('1.2px') + typeof parseInt(null) // '2.6number'
isNaN(Number(!!Number(parseInt('0.8px')))) // false
typeof !parseInt(null) + !isNaN(null) // 'booleantrue'

10 + false + undefined + [] + 'Tencent' + null + true + {}
// 10 + false -> 10
// 10 + undefined -> NaN
// NaN + [] -> 'NaN'
// 'NaN' + 'Tencent' -> 'NaNTencent'
// 'NaNTencent' + null + true + {} -> 'NaNTencentnulltrue[object Object]'

10 + false + undefined + + 'Tencent' + null + true + {}
// 'NaN[object Object]'

// parseInt(value, [radix])
// radix是一个进制,不写或者0默认都是10(特殊情况:如果value是以0x开头,则默认值不是10是16)
// radix 2-36 不在结果是NaN
// 把value看作radix进制,转换为10机制

let arr = [10.18, 0, 10, 25, 23]
arr = arr.map(parseInt)
console.log(arr)
// parseInt('10.18', 0) // 10
// parseInt('0', 1) // NaN
// parseInt('10', 2) // 2
// parseInt('25', 3) // 2
// parseInt('23', 4) // 11

// parseInt(0xf) // 15
// parseInt(0xf, 16) // 21
复制代码

堆栈内存

堆栈内存

/* var a = 12;
var b = a;
b = 13;
console.log(a);

var a = {n: 12};
var b = a;
b['n'] = 13;
console.log(a.n); */

/* var a = {
    n: 12
};
var b = a;
b = {
    n: 13
};
console.log(a.n); */

var a = {
    n: 1
};
var b = a;
a.x = a = {
    n: 2
};
console.log(a.x);
console.log(b);

// 正常运算都是从右到左
/* a=b=xxx
  b=xxx
  a=xxx 或者 a=b */
// a.x 成员访问,运算优先级很高的
/* a.x=b=xxx 
b=a.x=xxx
  + a.x=xxx
  + b=xxx */
复制代码

image.png

image.png

image.png

函数底层处理机制(创建+执行)

image.png

函数底层处理机制(闭包)

image.png

闭包

闭包:函数运行的一种机制(不是某种代码形式)

  • 函数执行会形成一个新的私有上下文,如果上下文的某些内容(一般指的是堆内存地址)被上下文以外的一些事物(例如 变量/事件绑定等)所占用,则当前上下文不能被出栈释放(浏览器的垃圾回收机制GC所决定的) =>闭包的机制:形成一个不被释放的上下文
  • 保护:保护私有上下文中的 私有变量 和外界互不影响
  • 保存:上下文不被释放,那么上下文中的 私有变量 和 值 都会被保存起来,可以供其下级上下文中使用
  • 弊端:如果大量使用闭包,会导致栈内存太大,页面渲染变慢,性能受到影响,所以真实项目中需要“合理使用闭包”;某些代码会导致栈溢出或者内存泄漏,这些操作都是需要我们注意的。
// 循环事件绑定
1. 闭包
2. let
3. 自定义属性
4。事件委托
复制代码

image.png

image.png

image.png

JS高阶编程技巧

利用闭包的机制,实现出来的一些高阶编程方式

  • 模块化思想 (单例设计模式)
  • 惰性函数
  • 柯理化函数
    • compose组合函数
    • 高阶组价 -> React
    • 函数防抖和节流
    • bind
// 模块化思想
// 单例->AMD(require.js)->CMD(sea.js)->CommonJS(Node)->ES6Module

//---------没有模块化思想之前,团队协作开发或者代码量较多的情况,回导致全局变量污染「全局变量冲突」
/* 
// 实现天气版块
var time = '2020-11-01';
function queryData() {
   // ...
}
function changeCity() {
   //...
}
// 实现资讯版块
var time = '2020-10-31';
function changeCity() {
   //...
} 
*/

//-----------暂时基于闭包的“保护作用”防止了全局变量的污染 「但是因为每个版块的代码都是私有的,无法相互调用」
/* (function () {
   var time = '2020-11-01';
   function queryData() {}
   function changeCity() {}
})();

(function () {
   var time = '2020-10-31';
   function changeCity() {}
})(); */

//----------基于某些方法去实现相互调用
/* (function () {
   var time = '2020-11-01';
   function queryData() {}
   function changeCity() {}
   // 把需要供别人调用的API方法,挂载到全局上「不能挂载太多,挂载多了,还是会引发全局变量的污染」
   window.queryData=queryData;
})();

(function () {
   var time = '2020-10-31';
   function changeCity() {}
   queryData();
})(); */

//---------
// 对象的特点:每一个对象都是一个单独的堆内存空间(单独的实例->Object),这样即使多个对象中的成员名字相同,也互不影响
// 仿照其他后台语言,其实obj1/obj2不仅仅称为对象名,更被称为“命名空间「给堆内存空间起一个名字」”
// -->每一个对象都是一个单独的实例,用来管理自己的私有信息,即使名字相同,也互不影响,其实这就是“JS中的单例设计模式”
/* var obj1 = {
   name: 'zhufeng',
   age: 12,
   fn: function () {}
};
var obj2 = {
   name: 'zhouxiaotian',
   age: 30,
   fn: function () {}
}; */

//----------高级单例设计模式:闭包+单例的结合,也是最早期的JS模块化思想
/* var weatherModule=(function () {
   var time = '2020-11-01';
   function queryData() {}
   function changeCity() {}
   return {
       queryData:queryData,
       changeCity:changeCity
   };
})();

var infoModule=(function () {
   var time = '2020-10-31';
   function changeCity() {}
   weatherModule.queryData();
   return {
       changeCity:changeCity
   };
})();

var skinModule=(function(){
   // ...
   return {};
})(); */
复制代码
// 惰性函数
// window.getComputedStyle(元素):获取当前元素经过浏览器计算的样式「返回样式对象」
// 在IE6~8中,不兼容这种写法,需要使用 “元素.currentStyle” 来获取
// “属性 in 对象” 检测当前对象是否有这个属性,有返回true,反之false
/* function getCss(element, attr) {
   if ('getComputedStyle' in window) {
       return window.getComputedStyle(element)[attr];
   }
   return element.currentStyle[attr];
}
var body = document.body;
console.log(getCss(body, 'height'));
console.log(getCss(body, 'margin'));
console.log(getCss(body, 'background')); */
//-------优化思想:第一次执行getCss我们已经知晓是否兼容了,第二次及以后再次执行getCss,则不想在处理兼容的校验了,其实这种思想就是“惰性思想”「懒,干一次可以搞定的,绝对不去做第二次」
/* var flag = 'getComputedStyle' in window;
function getCss(element, attr) {
   if (flag) {
       return window.getComputedStyle(element)[attr];
   }
   return element.currentStyle[attr];
} */
/* function getCss(element, attr) {
   // 第一次执行,根据是否兼容,实现函数的重构
   if ('getComputedStyle' in window) {
       getCss = function getCss(element, attr) {
           return window.getComputedStyle(element)[attr];
       };
   } else {
       getCss = function getCss(element, attr) {
           return element.currentStyle[attr];
       };
   }
   // 为了保证第一次也可以获取信息,则需要把重构的函数执行一次
   return getCss(element, attr);
}
var body = document.body;
console.log(getCss(body, 'height'));
console.log(getCss(body, 'margin'));
console.log(getCss(body, 'background')); */
复制代码
// 柯里化函数
// 预先处理的思想「形成一个不被释放的闭包,把一些信息存储起来,以后基于作用域链,访问到事先存储的信息,然后进行相关的处理,所有符合这种模式(或者闭包应用的)都被称为柯理化函数」
// x:预先存储的值
/* function curring(x) {
   // x->10
   return function (...args) {
       // args->[20]/[20,30]
       args.unshift(x);
       // 数组求和
       var total = eval(args.join('+'));
       return total;
   };
} */
/* function curring(x) {
   return function (...args) {
       args.unshift(x);
       return args.reduce((result, item) => result + item);
   };
}
var sum = curring(10);
console.log(sum(20)); //10+20
console.log(sum(20, 30)); //10+20+30
*/

/* 
// 数组的reduce方法:在遍历数组的过程中,可以累积上一次处理的结果,基于上次处理的结果继续遍历处理
//   + 数组.reduce([callback],[initialValue])
var arr = [10, 20, 30, 40];
/!* var res = arr.reduce(function (result, item, index) {
   // [initialValue]初始值不传递,result默认初始值是数组第一项,然后reduce从数组第二项开始遍历
   // 每遍历数组中的一项,回调函数被触发执行一次
   //   + result 存储的是上一次回调函数返回的结果(除了第一次是初始值或者数组第一项)
   //   + item 当前遍历这一项
   //   + index 当前遍历这一项的索引
   console.log(result);
   return item + result;
});
console.log(res); *!/
var res = arr.reduce((result, item) => {
   // 如果传递初始值,则result第一次的结果就是初始值,item从数组第一项开始遍历
   return result + item;
}, 0); 
*/
/* 
Array.prototype.reduce = function reduce(callback, initial) {
   var self = this, // this->arr
       i = 0;
   if (typeof callback !== "function") throw new TypeError('callback must be an function!');
   if (typeof initial === "undefined") {
       initial = self[0];
       i = 1;
   }
   // 迭代数组每一项
   for (; i < self.length; i++) {
       var item = self[i],
           index = i;
       initial = callback(initial, item, index);
   }
   return initial;
};
var arr = [10, 20, 30, 40];
var total = arr.reduce((result, item) => {
   return result + item;
});
console.log(total); 
*/
复制代码
/* 
    在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
    const add1 = (x) => x + 1;
    const mul3 = (x) => x * 3;
    const div2 = (x) => x / 2;
    div2(mul3(add1(0))); //=>3

    而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
    const operate = compose(div2, mul3, add1)
    operate(0) //=>相当于div2(mul3(add1(0))) 
    operate(2) //=>相当于div2(mul3(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;

// funcs:存储的是最后需要执行的函数及其顺序(最后传递的函数优先执行)
//   + 执行compose只是把最后要执行的函数及顺序事先存储起来,函数还没有执行「柯理化思想」
//   + 返回一个operate处理函数,执行operate,并且传递初始值,才按照之前存储的函数及顺序依次执行函数
function compose(...funcs) {
    return function operate(x) {
        if (funcs.length === 0) return x;
        if (funcs.length === 1) return typeof funcs[0] === "function" ? funcs[0](x) : x;
        return funcs.reduceRight(function (result, item) {
            if (typeof item !== "function") return result;
            return item(result);
        }, x);
    };
}
var operate = compose(div2, mul3, add1);
var result = operate(0);
console.log(result);

/* var result = div2(mul3(add1(0)));
console.log(result); //->1.5 */

/* 
// 思考题:研究这个是如何实现的
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
*/
复制代码

js中声明变量

  • 传统:var function
  • ES6:let const import(模块导入)
// let vs var
1.var存在变量提升,而let不存在
2.全局上下文中 var声明的变量也相当于给GO新增一个属性,并且一个值发生改变,另一个值也会跟着变化(映射机制),但是let声明的变量,就是全局变量,和GO没有任何关系
3.在同一上下文中,let不能重复声明,var可以 
4. 暂时性死区(浏览器暂存bug)
5. let/const/function会产生块级私有上下文,而var不会

/*
 * EC(G)
 * 变量提升: var n;
*/
console.log(n) //undefined
console.log(m) //Uncaught ReferenceError: Cannot access 'm' before initialization 
var n = 12
let m = 13

let n = 12
console.log(n) //12
console.log(window.n) //undefined

var n = 12
console.log(n) //12
console.log(window.n) //12
window.n = 13
console.log(n) //13

n = 13 // window.n = 13 没有基于任何关键词声明的,则相当于给window设置一个属性
console.log(n) //13 首先看是否为全局变量,如果不是,则再看是否为window的属性
console.log(m) //如果两者都不是,则报变量未定义 Uncaught ReferenceError: m is not defined

//在代码执行之前,浏览器会做很多事情:词法分析、变量提升
//'词法分析阶段',如果发现基于let/const重复声明变量操作,报语法错误
console.log('ok')
var n = 12
var n = 13
let n = 14


console.log(n) //Uncaught ReferenceError: n is not defined
console.log(typeof n) //typeof检测一个未被声明的变量,不会报错 undefined

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

/*
 * 上下文 & 作用域
 * 全局上下文
 * 函数执行形成的 私有上下文
 * 块级上下文(除了对象/函数...的大括号,例如判断体if、循环体for switch while、代码块{})都可能会产生块级上下文
*/
{
    var n = 12
    console.log(n) //12
    
    let m = 13
    console.log(m) //13
}
console.log(n) //12
console.log(m) //Uncaught ReferenceError: m is not defined

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

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

let i = 0
for(;i<5;i++){
    console.log(i) // 0 1 2 3 4
}
console.log(i) // 5

// 浏览器帮助我们形成的闭包
for(let i=0;i<buttons.length;i++){
    // 父级块级上下文 控制循环
    // 第一轮循环 私有的块级上下文EC(B1) i=0
    // 当前上下文中创建的一个小函数,被全局的按钮click占用了,EC(B1)不会被释放(闭包)
    buttons[i].onclick = function(){
        console.log(`当前点击按钮的索引:${i}`)
    }
    // ...
}

// 实现不了,产生不了闭包
let i = 0
for(;i<buttons.length;i++){
    buttons[i].onclick = function(){
        console.log(`当前点击按钮的索引:${i}`)
    }
}
复制代码

变量提升机制

变量提升:在当前上下文中(全局/私有/块级),js代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)

  • 会把所有带var 和 function 关键字的进行提前声明或定义
  • var 提前声明
  • function 提前声明加定义
/*
 * 代码执行之前: 全局上下文的变量提升
 * var a;   默认值是undefined
*/
console.log(a) // undefined
var a = 12 // 创建值12 不需要再声明a(变量提升阶段完成了,完成的事情不会重新处理) a = 12赋值
a = 13 // 全局变量 a = 13
console.log(a) // 13


/*
 * EC(G)变量提升
*/
// window.a  undefined
console.log(a) //Uncaught ReferenceError: a is not defined
a = 12
console.log(a)


/*
 * EC(G)变量提升 ES6中let 和 const不会
*/
console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 12
a = 13
console.log(a)


/*
 * 基于var 和 function 在 全局上下文中声明的变量(全局变量)会映射到GO(全局对象 window)上一份,作为他的属性,而且接下来一个修改,另外一个也会跟着修改
*/
/*
 * a = 12 也会给window设置属性 let 和 const 不会
*/

/*
 * EC(G)变量提升
 * 不论条件是否成立,都要进行变量提升(细节点:条件中带function的在新版本浏览器中只会提前声明,不会定义了)
 * [老版本]
 * var a;
 * func = 函数
 * [新版本]
 * var a;
 * func;
*/
if(!('a' in window)){
    var a = 1
}
console.log(a) //undefined
复制代码

总结:

  • var 和 function 关键字的进行提前声明或定义
  • var 和 function 在 全局上下文中声明的变量(全局变量)会映射到GO
  • 条件中带function的在新版本浏览器中只会提前声明,不会定义了
  • 浏览器很懒,在变量提升和代码执行阶段 干过的事情不会再干
fn() //5
function fn () {console.log(1)}
fn() //5
function fn () {console.log(2)}
fn() //5
var fn = function() {console.log(3)}
fn() //3
function fn () {console.log(4)}
fn() //3
function fn () {console.log(5)}
fn() //3
// 有说函数的优先级高于变量
// 有说先声明var 再声明function
复制代码
var foo = 1
function bar () {
    if(!foo){
        var foo = 10
    }
    console.log(foo) // 10
}
bar()
复制代码

闭包作用域习题

变量提升

/*
 * EC(G)中的变量提升
 *   var a;
 *   var b;
 *   var c;
 *   fn = 0x000; [[scope]]:EC(G)
 */
console.log(a, b, c); //=>undefined * 3
var a = 12,
    b = 13,
    c = 14;
function fn(a) {
    /*
     * EC(FN)私有上下文
     *   作用域链:<EC(FN),EC(G)>
     *   形参赋值:a=10
     *   变量提升:--
     */
    console.log(a, b, c); //=>10 13 14
    a = 100;//私有a赋值100
    c = 200;//全局c赋值200
    console.log(a, b, c); //=>100 13 200
}
b = fn(10);//全局b赋值undefined
console.log(a, b, c); //=>12 undefined 200
复制代码
/*
 * EC(G)变量提升
 *   var i;
 *   A = 0x000; [[scope]]:EC(G) 
 *   var y;
 *   B = 0x001; [[scope]]:EC(G) 
 */
var i = 0;
function A() {
    /*
     * EC(A)私有上下文
     *   作用域链:<EC(A),EC(G)>
     *   形参赋值:--
     *   变量提升: 
     *      var i;
     *      x = 0x002; [[scope]]:EC(A)
     */
    var i = 10;
    function x() {
        /*
         * EC(X1)私有上下文
         *   作用域链:<EC(X1),EC(A)>
         *   形参赋值:--
         *   变量提升:--
         */
        /*
         * EC(X2)私有上下文
         *   作用域链:<EC(X2),EC(A)>
         *   形参赋值:--
         *   变量提升:--
         */
        console.log(i);
    }
    return x; //return 0x002;
}
var y = A(); //y=0x002;
y(); //=>10
function B() {
    /*
     * EC(B)私有上下文
     *   作用域链:<EC(B),EC(G)>
     *   形参赋值:--
     *   变量提升:
     *     var i;
     */
    var i = 20;
    y();//=>10
}
B();
复制代码
/*
 * EC(G)变量提升
 *   var a;
 *   var obj;
 *   fn = 0x000;  [[scope]]:EC(G)
 */
var a = 1;
var obj = {
    name: "tom"
}; //obj=0x001
function fn() {
    /*
     * EC(FN)
     *   作用域链:<EC(FN),EC(G)>
     *   形参赋值:--
     *   变量提升:var a2; 
     */
    var a2 = a; //私有a2=1
    obj2 = obj; 
    //obj2不是私有的,向全局找,如果全局也没有
    //  情况1:输出obj2  直接报错  Uncaught ReferenceError: obj2 is not defined
    //  情况2:赋值操作  相当于给window设置一个obj2的属性,并且赋值  window.obj2=0x001
    a2 = a; //私有a2=1
    obj2.name = "jack"; //遇到obj2,但是发现全局没有这个变量,它会继续再看window下是否有这个属性,如果window下有这个属性,则获取属性值,如果window下也没有这个属性,则报错(未定义变量) ->把0x001中的name属性值改为'jack'
}
fn();
console.log(a);//=>1
console.log(obj);//=>{name:'jack'}

/* 
// VO(G):全局变量对象「全局上下文中声明的变量」
// GO「window」:全局对象 「浏览器默认开辟的一个堆内存,存储供JS调用的各种API的」
console.log(a);//首先看VO(G)中是否有,如果没有,则再看GO中有没有,如果也没有,则报错:a is not defined
*/

/*
// 在全局上下文中
var n = 10;
//老版本机制:VO(G)中声明一个n的全局变量,赋值10;特殊:“全局上下文”中,“基于var/function声明”的变量,也相当于给GO新增私有属性;并且之间存在映射关系「一个修改,另外一个也跟着修改」;
//新版本机制:“全局上下文”中,“基于var/function声明”的变量,直接都当做GO的属性存储起来;
console.log(n); //=>10
console.log(window.n); //=>10

m = 10; //给GO设置一个属性m -> window.m=10
console.log(m); //=>10
console.log(window.m); //=>10
*/

/*
// 基于let/const声明的变量,只是给VO(G)中设置全局变量,和GO没有任何的关系;
let n = 10;
console.log(n); //=>10
console.log(window.n); //=>undefined
*/
复制代码
/*
 * EC(G)变量提升
 *   var a;
 *   fn=0x000; [[scope]]:EC(G) 
 */
var a = 1;
function fn(a) {
    /*
     * EC(FN)
     *   作用域链:<EC(FN),EC(G)>
     *   形参赋值:a=1
     *   变量提升:
     *     var a; 「没用了:因为此时私有上下文中已经有a了,不会重复声明」
     *     a=0x001; [[scope]]:EC(FN) 「不会重复声明,但是需要重新赋值」
     */
    console.log(a); //=>函数
    var a = 2; //私有a重新赋值为2
    function a() {}
    console.log(a); //=>2
}
fn(a); //fn(1)
console.log(a); //=>1
复制代码
/*
 * EC(G)变量提升
 *   var a;
 *   fn=0x000; [[scope]]:EC(G) 
 */
console.log(a); //=>undefined
var a = 12;
function fn() {
    /*
     * EC(FN)
     *   作用域链:<EC(FN),EC(G)>
     *   形参赋值:--
     *   变量提升:var a;
     */
    console.log(a); //=>undefined
    var a = 13;
}
fn();
console.log(a); //=>12
复制代码
/*
 * EC(G)变量提升
 *   var a;
 *   fn=0x000; [[scope]]:EC(G) 
 */
console.log(a); //=>undefined
var a=12;
function fn(){
    /*
     * EC(FN)
     *   作用域链:<EC(FN),EC(G)>
     *   形参赋值:--
     *   变量提升:--
     */
    console.log(a); //=>12
    a=13;
}
fn();
console.log(a); //=>13
复制代码
/*
 * EC(G)变量提升
 *   fn=0x000; [[scope]]:EC(G) 
 */
console.log(a); //先看VO(G)中是否有,没有再看GO中是否有,也没有 Uncaught ReferenceError: a is not defined
a=12;
function fn(){
    console.log(a);
    a=13;   
}
fn();
console.log(a);
复制代码
/*
 * EC(G)变量提升
 *   var foo;
 */
var foo = 'hello';
(function (foo) {
    /*
     * EC(AN)
     *   作用域链:<EC(AN),EC(G)>
     *   形参赋值:foo='hello'
     *   变量提升:var foo;
     */
    console.log(foo); //=>'hello'
    var foo = foo || 'world';
    // A||B  A如果为假返回B的值,A为真返回A的值
    // A&&B  A如果为假返回A的值,A为真返回B的值
    // 同时存在 &&优先级高于||
    console.log(foo); //=>'hello'
})(foo); //自执行函数执行,把全局foo的值“hello”当做实参传递进来
console.log(foo); //=>'hello'
复制代码

变态机制

console.log(foo); //undefined
{
    console.log(foo); //函数
    function foo() {}
    foo = 1;
    console.log(foo); //1
}
console.log(foo); //函数
复制代码

image.png

/*
 * EC(G)中的变量提升
 *    function foo;
 *    function foo;
 */
console.log(foo); //=>undefined
{
    /*
     * EC(B)变量提升
     *    foo = 0x000;
     *    foo = 0x001;
     */
    console.log(foo); //=>0x001
    function foo() {0} //特殊:之前对foo的操作都给全局一份 全局foo=0x001
    console.log(window.foo); //=>0x001
    foo = 1;
    function foo() {1} //特殊:之前对foo的操作都给全局一份 全局foo=1
    console.log(foo); //=>1
}
console.log(foo); //=>1
复制代码
{
    function foo() {}
    foo = 1;
    function foo() {}  //特殊:之前对foo的操作都给全局一份
    foo = 2;
}
console.log(foo); //=>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
复制代码

image.png

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

image.png

var x = 1;
function func(x, y = function anonymous1(){x=2}){
    var x = 3;
    var y = function anonymous2(){x = 4}
    y();
    console.log(x); //4
};
func(5);
console.log(x); //1
复制代码
/*
 * arr.map((item,index)=>{
 *    return xxx;
 * })  ->数组迭代的方法
 * 
 * parseInt(xxx):把xxx转换为字符串,紧接着从字符串左侧第一个字符开始查找,找到有效数字字符「直到遇到一个非有效数字字符结束(不论后面是否还有,都不在查找)」,把找到的有效数字字符转换为数字!
 * parseInt(xxx,radix):radix进制,在xxx字符串中,从左到右查找符合radix进制的信息,把找到的结果最后转换为10进制
 *    radix:取值范围2~36之间,不在这个之间,则最后结果都是NaN
 *    radix:没有设置或者设置为0,则默认都是按照10进制处理的「特殊:如果字符串是按照0x开始的,则默认值不是10,而是16」
 */
/* let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
// parseInt(27.2)  -> parseInt('27.2') -> 27
// parseInt(0,1) -> NaN
// parseInt('0013',2) -> '001' 把找到的2进制转换为10进制
//    1*2^0 + 0*2^1 + 0*2^2 =>1 + 0 + 0  =>1 
// parseInt('14px',3) -> '1' 把找到的3进制转换为10进制
//    1*3^0 =>1
// parseInt(123,4) -> parseInt('123',4) -> '123' 把找到的4进制转换为10进制
//    3*4^0 + 2*4^1 + 1*4^2 =>3+8+16 =>27
console.log(arr);
*/

parseInt(070)
// JS中遇到“以0开始的数字”,浏览器解析阶段 就会把其当做8进制,最后转换为十进制
//    0*8^0 + 7*8^1 + 0*8^2 =>56
// parseInt(56) -> parseInt('56',10) */
复制代码
var a = 4
function b(x, y, a) {
    console.log(a) //3
    arguments[2] = 10
    console.log(a) //10
}
a = b(1, 2, 3)
console.log(a) //undefined
复制代码

image.png

function fn(x, y, z) {
    // 初始ARGUMENTS : {0:1,1:2,length:2}
    // 形参赋值:x=1  y=2  z=undefined
    // 「映射机制 ARG[0]:x  ARG[1]:y 」
    arguments[0]=10;
    console.log(x); //10

    y=20;
    console.log(arguments[1]); //20

    // 最开始没有建立起来映射机制,后期不论如何处理,都没有关系
    console.log(z); //undefined
    arguments[2]=30;
    console.log(z); //undefined
}
fn(1, 2);
复制代码
// 把自执行函数执行的返回结果赋值给test
//    test=0x001;
var test = (function (i) {
    /* 
     * EC(AN) 「闭包」
     *  作用域链:<EC(AN),EC(G)>
     *  形参赋值:i=2
     *  变量提升:--
     */
    return function () {
        /*
         * EC(TEST)
         *   作用域链:<EC(TEST),EC(AN)>
         *   初始ARGUMENTS:{0:5,length:1}
         *   形参赋值:--
         *   变量提升:--
         */
        alert(i *= 2); //=>让EC(AN)中的i在自身基础上累乘2  =>"4"
    }; //return 0x001;  [[scope]]:EC(AN)
})(2);
test(5); 
复制代码
// func执行传递的值没啥卵用
var x = 4;//3 2 1
function func() {
    return function (y) { //0x001  0x002
        console.log(y + (--x)); //x是全局的
    };
}
var f = func(5); //f=0x001
f(6); //=>9
func(7)(8); //0x002(8)  =>10
f(9); //=>10
console.log(x); //=>1
复制代码
var x = 5,
    y = 6;
function func() {
    /*
     * EC(F1)
     *   作用域链:<EC(F1),EC(G)> 
     *   形参赋值:--
     *   变量提升:--
     */
    x += y; //改变全局的x=11
    func = function (y) { //全局的func=0x001  [[scope]]:EC(F1) 「函数重构」
        /*
         * EC(F2)
         *   作用域链:<EC(F2),EC(F1)> 
         *   形参赋值:y=3
         *   变量提升:--
         */
        console.log(y + (--x)); //先把全局的x=10  =>13
    };
    console.log(x, y); //=>11 6
}
func(4);
func(3);
console.log(x, y); //=>10 6
复制代码
//===============作业
// 简述你对闭包的理解,以及其优缺点?「剧本」
// + 基本介绍:ECStack、EC、VO、AO、GO、SCOPE、SCOPE-CHAIN、GC
// + 优 缺 点:保存和保护 、性能消耗(内存泄漏)
// + 实战应用:
//   + 项目实战应用:循环事件绑定「突出:事件委托」、LET和VAR
//   + 插件组件封装:JS高阶编程技巧「单例设计模式、惰性函数、柯理化函数、compose组合函数」
//   + 源码阅读应用:Lodash源码「函数的防抖和节流」、JQ的源码、redux、react-redux「高阶组件」...
//   + ...
// + 自己的思想和理解「一句话概括」
// 总结个5min左右的话术,回答过程中拒绝“背书式”回答,通俗一些,安排一些场景...
// 小技巧:你也可以写一篇文章,详细的介绍闭包「面试的时候,给面试官看,它边看你边简单说一些」

//===============作业
// 简述let和var的区别?
// + 变量提升
// + 重复声明
// + 全局下声明和GO的关系
// + 暂时性死区 「突出 “typeof检测一个没有被声明变量,结果是undefined” 的作用 -> 封装插件组件」
// + 块级作用域
// + ...
复制代码
// 匿名函数“具名化”:这样的写法是符合规范的
//   const func = function func() {};
//   document.body.onclick = function bodyclickhandle() {};
//   (function anonymous(){})();
//   ...
// + 匿名函数具名化,设置的名字不属于当前函数所在作用域中的变量
/* (function b() {})();
console.log(b); //Uncaught ReferenceError: b is not defined */
// + 函数名只能在函数内部使用:好处就是,后期匿名函数也可以实现递归调用「严格模式下禁止arguments.callee的使用」
/* "use strict";
(function b() {
    console.log(b); //=>函数本身
    console.log(arguments.callee); //Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
})(); */
// + 并且函数内部,直接修改它的值也是无效的
/* (function b() {
    b = 10;
    console.log(b); //=>函数
})(); */
// + 除非函数内部重新声明了这个变量,则可以修改这个名字的值:基于var/let/const/function都可以
/* (function b() {
    let b = 10;
    console.log(b); //=>10
})(); */

var b = 10;
(function b() {
    /* b = 20;
    console.log(b); //=>函数 */
    let b = 20;
    console.log(b); //=>20
})();
console.log(b); //=>10

/* var b = 10;
(function b(b) {
    // 没有被声明过,b都是按照具名化的名字处理;但是不论基于何种方式一但被声明了,则按照变量处理,和函数名字没关系了;
    console.log(b); //=>20
})(20);
console.log(b); */
复制代码
function fn(...outerArgs) {
    return function anonymous(...innerArgs) {
        return outerArgs.concat(innerArgs).reduce(function handle(result, item) {
            return result + item;
        }, 0);
    };
}
const fn = (...outerArgs) => (...innerArgs) => outerArgs.concat(innerArgs).reduce((result, item) => result + item, 0);
let res = fn(1, 2)(3);
console.log(res); 
复制代码
var num = 10; //window.num=10
var obj = {
    num: 20
}; //obj=0x000;
obj.fn = (function (num) {
    /*
     * EC(AN) 「闭包」
     *   作用域链:<EC(AN),EC(G)>
     *   初始THIS:window
     *   形参赋值:num=20  //->21 22 23
     *   变量提升:--
     */
    this.num = num * 3; //window.num=num*3=60
    num++;
    return function (n) {
        /*
         * EC(FN1) -> fn(5) 
         *   作用域链:<EC(FN1),EC(AN)>
         *   初始THIS:window
         *   形参赋值:n=5
         *   变量提升:--
         */
        /*
         * EC(FN2) -> obj.fn(10) 
         *   作用域链:<EC(FN2),EC(AN)>
         *   初始THIS:obj
         *   形参赋值:n=10
         *   变量提升:--
         */
        this.num += n; //window.num=window.num+n=65   obj.num=obj.num+n=30
        num++;
        console.log(num); //=>22 23
    }; //obj.fn=0x001; [[scope]]:EC(AN)
})(obj.num);
var fn = obj.fn; //fn=0x001;
fn(5);
obj.fn(10);
console.log(num, obj.num); //=>65 30
复制代码
// 函数中的THIS是谁,和函数在哪定义的以及在哪执行的都没有关系,按照总结的规律去分析执行主体即可
let obj = {
    fn: (function () {
        return function () {
            console.log(this);
        } //obj.fn=0x000;
    })()
};
obj.fn(); //=>obj
let fn = obj.fn; //fn=0x000;
fn(); //=>window 
复制代码
var fullName = 'language'; //window.fullName='language';
var obj = {
    fullName: 'javascript',
    prop: {
        getFullName: function () {
            return this.fullName;
        }
    }
};
console.log(obj.prop.getFullName());
//=>this:obj.prop  =>obj.prop.fullName  =>undefined
var test = obj.prop.getFullName;
console.log(test());
//=>this:window =>window.fullName =>'language'
复制代码
var name = 'window'; //window.name='window'
var Tom = {
    name: "Tom",
    show: function () {
        // this->window
        console.log(this.name); //=>'window'
    },
    wait: function () {
        // this->Tom
        var fun = this.show;
        fun();
    }
};
Tom.wait(); 
复制代码
window.val = 1;
var json = {
    val: 10,
    dbl: function () {
        this.val *= 2;
    }
}
json.dbl();
//->this:json  ->json.val=json.val*2  ->json.val=20
var dbl = json.dbl;
dbl();
//->this:window ->window.val=window.val*2 ->window.val=2
json.dbl.call(window);
//->this:window ->window.val=window.val*2 ->window.val=4
alert(window.val + json.val); //=>"24"
复制代码
(function () {
    var val = 1; //->2
    var json = {
        val: 10,
        dbl: function () {
            // this->json
            val *= 2; //val=val*2  此处的val是变量  json.val是对象的属性
        }
    };
    json.dbl();
    alert(json.val + val); //=>"12"
})(); 
复制代码

image.png
curring

/* function add(...params) {
    const proxy = (...args) => {
        // 把每一次传递的信息都保存在原始的集合中
        params = params.concat(args);
        return proxy;
    };
    proxy.toString = () => {
        // 基于函数运算或者输出的时候,隐式调用函数的toString,我们在这里实现求和即可
        return params.reduce((result, item) => result + item);
    };
    return proxy;
} */

function curring() {
    let params = [];
    let add = function (...args) {
        params = params.concat(args);
        return add;
    }
    add.toString = function () {
        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

/* 
// 基于函数进行运算,或者输出的时候,一般都会调用函数的toString
function fn() {}
fn.toString = function () {
    console.log('OK');
};
// console.log(fn + 1); //会依次调取fn的 Symbol.toPrimitive/valueOf/toString 这三个属性
console.log(fn); 
*/
复制代码
/* 
 * 函数的防抖(debounce)和节流(throttle)
 *   在“高频”触发的场景下,需要进行防抖和节流
 *     + 狂点一个按钮
 *     + 页面滚动
 *     + 输入模糊匹配
 *     + ...
 *   我们自己设定,多长的时间内,触发两次及以上就算“高频”:封装方法的时候需要指定这个频率(可以设置默认值)
 *  「防抖」在某一次高频触发下,我们只识别一次(可以控制开始触发,还是最后一次触发);详细:假设我们规定500MS触发多次算是高频,只要我们检测到是高频触发了,则在本次频繁操作下(哪怕你操作了10min)也是只触发一次...
 *  「节流」在某一次高频触发下,我们不是只识别一次,按照我们设定的间隔时间(自己规定的频率),没到达这个频率都会触发一次;详细:假设我们规定频率是500MS,我们操作了10min,触发的次数=(10*60*1000)/500
 */

function debounce(func, wait, immediate) {
    // 多个参数及传递默认的处理
    if (typeof func !== "function") throw new TypeError("func must be an function!");
    if (typeof wait === "undefined") wait = 500;
    if (typeof wait === "boolean") {
        immediate = wait;
        wait = 500;
    }
    if (typeof immediate !== "boolean") immediate = false;

    // 设定定时器返回值标识
    let timer = null;
    return function proxy(...params) {
        let self = this,
            now = immediate && !timer;

        clearTimeout(timer);
        timer = setTimeout(function () {
            timer = null;
            !immediate ? func.call(self, ...params) : null;
        }, wait);

        // 第一次触发就立即执行
        now ? func.call(self, ...params) : null;
    };
}

/* 
function handle(ev) {
    // 具体在点击的时候要处理的业务
    console.log('OK', this, ev);
}
submit.onclick = debounce(handle, true);
// submit.onclick = proxy;  疯狂点击的情况下,proxy会被疯狂执行,我们需要在proxy中根据频率管控handle的执行次数
// submit.onclick = handle; //handle->this:submit  传递一个事件对象 
*/


// 业务场景中处理的技巧1:标识判断  类似节流
/* let flag = false;
submit.onclick = function () {
    if (flag) return;
    flag = true;
    console.log('OK');
    setTimeout(() => {
        // 事情处理完
        flag = false;
    }, 1000);
}; */

/* // 业务场景中处理的技巧2:按钮重置为灰色,移除事件绑定  类似节流
function handle() {
    submit.onclick = null;
    submit.disabled = true;

    // ...
    console.log('OK');
    setTimeout(() => {
        submit.onclick = handle;
        submit.disabled = false;
    }, 1000);
}
submit.onclick = handle; */


function throttle(func, wait) {
    if (typeof func !== "function") throw new TypeError("func must be an function!");
    if (typeof wait === "undefined") wait = 500;
    let timer = null,
        previous = 0; //记录上一次操作的时间
    return function proxy(...params) {
        let self = this,
            now = new Date(), //当前这次触发操作的时间
            remaining = wait - (now - previous);
        if (remaining <= 0) {
            // 两次间隔时间超过wait了,直接执行即可
            clearTimeout(timer);
            timer = null;
            previous = now;
            func.call(self, ...params);
        } else if (!timer) {
            // 两次触发的间隔时间没有超过wait,则设置定时器,让其等待remaining这么久之后执行一次「前提:没有设置过定时器」
            timer = setTimeout(function () {
                clearTimeout(timer);
                timer = null;
                previous = new Date();
                func.call(self, ...params);
            }, remaining);
        }
    };
}

function handle() {
    console.log('OK');
}
window.onscroll = throttle(handle, 500);
// window.onscroll = proxy;

/* window.onscroll = function () {
    // 默认情况下,页面滚动中:浏览器在最快的反应时间内(4~6MS),就会识别监听一次事件触发,把绑定的方法执行,这样导致方法执行的次数过多,造成不必要的资源浪费
    console.log('OK');
}; */
复制代码

image.png

// 利用JS的暂时性死区「基于typeof检测一个未被声明的变量,结果是undefined」:如果是在浏览器或者webview环境下运行JS,则A=window,如果是在Node环境下执行,则A=global或者当前模块
var A = typeof window !== "undefined" ? window : this;
// 回调函数:把一个函数作为实参值,传递给另外一个函数执行,在执行过程中,把传递的函数执行
var B = function (window, noGlobal) {
    // 浏览器环境下
    // window=window
    // noGlobal=undefined
    "use strict";

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

    var _jQuery = window.jQuery,
        _$ = window.$;
    jQuery.noConflict = function (deep) {
        if (window.$ === jQuery) {
            window.$ = _$;
        }
        if (deep && window.jQuery === jQuery) {
            window.jQuery = _jQuery;
        }
        return jQuery;
    };

    // 支持AMD模块化思想 require.js
    if (typeof define === "function" && define.amd) {
        define("jquery", [], function () {
            return jQuery;
        });
    }

    // 浏览器环境下
    if (typeof noGlobal === "undefined") {
        // 把私有的方法暴露到全局对象上
        window.jQuery = window.$ = jQuery;
    }
};

// 自执行函数
(function (global, factory) {
    "use strict";
    if (typeof module === "object" && typeof module.exports === "object") {
        // 当前运行JS的环境是支持CommonJS模块规范「node.js/webpack,浏览器默认不支持」
        // ...
    } else {
        // 浏览器或者webview环境
        // -> B(window)
        factory(global);
    }
})(A, B);


//============================
(function () {
    function ModalPlugin() {}
    // ...

    // 防止冲突处理
    var _M = window.M;
    ModalPlugin.noConflict = function noConflict() {
        if (window.M === ModalPlugin) {
            window.M = _M;
        }
        return ModalPlugin;
    };

    // 暴露API:支持浏览器「直接SCRIPT导入」 && CommonJS「支持在webpack中或者ES6Module导入」
    if (typeof window !== "undefined") {
        window.M = window.ModalPlugin = ModalPlugin;
    }
    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = ModalPlugin;
    }
})();
复制代码

this的几种情况

  • 事件绑定
// 不论是DOM0还是DOM2级事件绑定,给元素E的某个事件行为绑定方法,当事件触发方法执行,方法中的this是当前元素E本身
// 特殊情况:1.IE6~8中基于attachEvent实现DOM2事件绑定,事件触发方法执行,方法中的this不在是元素本身,大部分情况都是window   2.如果基于call/apply/bind强制改变了函数中的this,我们也是以强制改变的为主
document.body.onclick = function () {
    console.log(this);
};
document.body.addEventListener('click', function () {
    console.log(this);
});
复制代码
  • 普通函数执行
// 函数执行,看函数前面是否有“点”,有“点”,“点”前面是谁this就是谁,没有“点”this是window「JS严格模式下是undefined」
// fn() -> this:window/undefined
// obj.fn() -> this:obj
// xxx.__proto__.fn() -> this:xxx.__proto__

// 自执行函数执行:其内的this一般都是window/undefined
// 回调函数中的this一般也是window/undefined,除非做过特殊的处理
// 括号表达中的this很变态

/* function fn() {
    console.log(this);
}
var obj = {
    name: 'zhufeng',
    fn: fn
};
fn(); //this->window
obj.fn(); //this->obj
(obj.fn)(); //this->obj 小括号中只有一项,不算是括号表达式
(fn, 10, obj.fn)(); //this->window 小括号中有多项,只取最后一项,如果把其执行,不论之前this是谁,现在基本上都会变为window「括号表达式」 
 */
 
// var obj = {
//     num: (function () {
//         // 把自执行函数执行的返回值,赋值给obj.num成员
//         console.log(this); //->window
//         return 10;
//     })()
// };


// 回调函数:把一个函数作为值,传递给另外一个函数,在另外一个函数执行中,把传递进来的函数执行...
// var obj = {
//     name: 'zhufeng'
// };
// setTimeout(function () {
//     console.log(this); //->window
// }, 1000);
// [10].forEach(function () {
//     console.log(this); //->window
// });
// [10].forEach(function () {
//     console.log(this); //->obj
// }, obj);
复制代码
  • 构造函数执行
  • 箭头函数执行
// 箭头函数中没有自己的this,所用到的this都是所处上下文中的this
//在浏览器端运行JS代码,非函数中的this一般都是window;研究this都是研究函数中的this;有一个特殊的,就是ES6+中“块级上下文”中的this,是其所在上下文中的this「理解为:块级上下文是没有自己this的」
let obj = {
    n: 0,
    fn() {
        // this->obj
        /* 
        let self = this;
        setTimeout(function () {
            // console.log(this); //this->window
            self.n++;
        }, 1000); 
        */

        setTimeout(() => {
            // this -> 上级上下文中的this -> obj
            this.n++;
        }, 1000);
    }
};
obj.fn();
复制代码
  • 基于call/apply/bind强制改变this
// Function.prototype : call/apply/bind 都是用来改变this指向的,以后函数执行调用这三个方法就可以实现this的改变
/* 
Function.prototype.call = function call(context) {
    // ...
};
*/

function fn(x, y, ev) {
    console.log(this, x, y, ev);
}
let obj = {
    name: 'zhufeng'
};
// obj.fn(); //Uncaught TypeError: obj.fn is not a function  obj和fn没有关系,我们无法基于obj.fn来改变fn中的this
// ==BIND== 并不会把函数立即执行,它是预先处理函数中的THIS和参数的
// document.body.onclick = fn; //->把fn方法本身作为值绑定给BODY的点击事件,当触发BODY的点击操作,浏览器会帮助我们把fn函数执行「并且传递一个事件对象」,方法中的THIS->BODY  =>this:body  x:MouseEvent  y:undefined
// document.body.onclick = fn(); //->立即把fn执行,把其执行的返回结果当作值,赋值给事件绑定,事件触发执行的是返回结果

// 需求:点击BODY的时候,把FN执行,并且让THIS->OBJ,而且传递10/20给X/Y
// document.body.onclick = fn.call(obj, 10, 20); //这样处理不行:因为call/apply都是把函数立即执行的,还没有等到点击的时候,函数都执行完了
/* document.body.onclick = function (ev) {
    // 创建一个匿名函数,绑定给点击事件,触发BODY点击行为的时候,执行的是匿名函数
    //   + this->body
    //   + ev->传递的MouseEvent事件对象
    // 在匿名函数中,我们自己执行fn即可,接下来想改啥改啥
    fn.call(obj, 10, 20, ev);
}; */
// document.body.onclick = fn.bind(obj, 10, 20);

// ==CALL/APPLY== 都是立即执行函数,并且改变函数中的THIS,再并且给函数传递参数信息
/* 
fn.call(obj, 10, 20);
// 1.fn实例基于__proto__找到Function.prototype上的call方法,把call方法执行
//   + call方法中的this:fn
//   + context存储的是传递的obj
// 2.call方法内部做的事情
//   + 把fn执行(也就是把call中的this执行)
//   + 把fn中的this改变为第一个参数context:obj
// 简述:把fn执行,让fn中的this变为obj,并且把除了obj外的剩余参数,依次传递给fn 
*/
// fn.call(undefined); //在非严格模式下,context不传递或者传递null/undefined,则this都改为window;严格模式下,不传是undefined,否则传递谁,this就改为谁;
// fn.call(10, 20); //this->Number{10}  x->20  y->undefined
// fn.apply(obj, [10, 20]); //this->obj  x->10  y->20  apply和call的唯一区别,就是再给fn传递参数的时候,apply需要把所有需要传递的参数信息放在一个数组中,而call是一个个的传递进来即可,但是不论那种办法,最后的结果都是,都是把这些参数一个个的传递给fn

// call的性能要比apply好一些「一丢丢」:尤其是传递三个及以上参数的时候
复制代码
// 获取数据中的最大值
// let arr = [10, 13, 24, 15, 26, 34, 11];

/* arr.sort((a, b) => b - a);
console.log(`数组中的最大值是:${arr[0]}`); */

/* console.log(Math.max(arr)); //NaN
console.log(Math.max(10, 13, 24, 15, 26, 34, 11)); //34
console.log(Math.max(...arr)); //34 */

/* let max = Math.max.apply(Math, arr); //利用apply传递给方法的参数是基于数组完成的
console.log(`数组中的最大值是:${max}`); */

/* let max = 0;
arr.forEach(item => {
    item > max ? max = item : null;
});
console.log(`数组中的最大值是:${max}`); */

/* let max = eval(`Math.max(${arr})`);
console.log(`数组中的最大值是:${max}`); */


// 需求:任意数求和「把传递进来的实参(不固定个数)进行求和」
/* 
function sum(...params) {
    // params->[...] 数组,存储传递的所有实参
    return params.reduce((result, item) => {
        return result + item;
    }, 0);
} 
*/

/* Array.prototype.slice = function slice() {
    // this->arr
    let params = [];
    for (let i = 0; i < this.length; i++) {
        params.push(this[i]);
    }
    return params;
};
let arr = [10, 20, 30];
let params = arr.slice(); //把arr克隆 */

function sum() {
    // arguments 类数组,不是数组,不能直接用数组的方法(只有数组作为Array的实例才可以直接调用)
    // 1.把类数组转换为数组
    /* let params = Array.from(arguments);
     let params = [...arguments]; */

    /* let params = [];
    for (let i = 0; i < arguments.length; i++) {
        params.push(arguments[i]);
    } */

    // 鸭子类型:长的像鸭子就称之为鸭子 -> 类数组除了__proto__不是Array.prototype外,它的结构特点和数组基本一样「常规操作处理都是一样的,例如:循环...」 -> 让类数组借用数组原型上的方法,实现一些特定的处理
    let params = [].slice.call(arguments);
    // Array.prototype.forEach.call(arguments, item => {});
    // ...

    // 2.求和
    return params.length === 0 ? 0 : eval(params.join('+'));
}
let total = sum();
console.log(total); //->0
total = sum(10);
console.log(total); //->10
total = sum(10, 20);
console.log(total); //->30
total = sum(10, 20, 30);
console.log(total); //->60
复制代码
/*
 * 重写内置的CALL:把需要执行的函数和需要改变的THIS关联在一起  
 *   + context.xxx=this  obj.fn=fn;
 *   + context.xxx()  obj.fn();
 */
Function.prototype.call = function call(context, ...params) {
    // this->fn  context->obj  params->[10,20]
    context == null ? context = window : null;
    // 需要保证context必须是对象类型的值:只有对象才能设置属性
    !/^(object|function)$/.test(typeof context) ? context = Object(context) : null;
    let self = this,
        result = null,
        key = Symbol('KEY'); //新增的属性名保证唯一性,防止污染了原始对象中的成员
    context[key] = self;
    result = context[key](...params);
    delete context[key]; //用完后移除自己新增的属性
    return result;
};

function fn(x, y) {
    console.log(this, x, y);
}
let obj = {
    name: 'zhufeng',
    fn: 100
};

fn.call(0, 10, 20); 


/*
 * 重写内置BIND:柯理化思想「预处理思想」
 */
Function.prototype.bind = function bind(context, ...outerArgs) {
    // this->fn context->obj outerArgs->[10,20]
    let self = this;
    return function (...innerArgs) {
        // innerArgs->[ev]
        self.call(context, ...outerArgs.concat(innerArgs));
    };
};

function fn(x, y, ev) {
    console.log(this, x, y, ev);
}
let obj = {
    name: 'zhufeng'
};

/* document.body.onclick = function (ev) {
    fn.call(obj, 10, 20, ev);
}; */
document.body.onclick = fn.bind(obj, 10, 20);
复制代码

面向对象

构造函数 VS 普通函数

/*
 * 编程语言
 *    面向对象 OOP  java、javascript、php、C#(ASP.NET)、Python、GO、C++、Ruby...
 *    面向过程 POP  C
 * 标记语言:HTML / CSS 
 * 
 * =====面向对象编程「对象、类、实例」
 *   对象:万物皆对象(泛指)
 *   类:对“对象”的划分(按照其功能结构特点,划分出大类和小类)
 *   实例:类中具体的事务
 * 
 * JS本身就是基于面向对象思想开发出来的编程语言,所以我们学习和开发JS的时候,也要按照面向对象的思想去处理!!
 *  「内置类」
 *     + 每一种数据类型都有一个自己所属的内置类:Number数字类(每一个数字/NaN/Infinity都是它的实例)、String、Boolean、Symbol、BigInt、Array、RegExp、Date、Function、Object...
 *     + 每一种DOM元素也都有自己所属的类:
 *       window -> Window -> WindowProperties -> EventTarget -> Object
 *       document -> HTMLDocument -> Document -> Node -> EventTarget -> Object
 *       div -> HTMLDivElement -> HTMLElement -> Element -> Node -> ...
 *       a -> HTMLAnchorElement -> HTMLElement -> ...
 *     + HTMLCollection / NodeList / CSSStyleDeclaration /  DOMTokenList ...
 *     + ....
 *   学习数组,首先分析一个数组(实例),研究清楚这个实例的特征后(含:结构特点和常用方法等),我们再遇到其他的数组,直接也是按照相同的机制进行处理的
 *   
 *  「自定义类」
 *     创建一个函数 fn
 *       + fn() 普通函数执行「堆栈机制」
 *       + new fn() 构造函数执行 「堆栈机制 + 面向对象机制」
 */
function Fn(x, y) {
    let total = x + y;
    this.x = x;
    this.y = y;
}
let result = new Fn(10, 20);
console.log(result);
复制代码

image.png

对象遍历问题及解决方案

function Fn() {
    /*
     * EC(FN)
     *   初始创建Fn找个类的一个实例对象  0x000
     *   初始THIS:this->0x000
     */
    let total = 0; //上下文的私有变量  和实例对象没有必然的联系
    this.x = 10; //this.xxx=xxx 都是给实例对象设置的私有属性和方法
    this.y = 20;
    this.say = function () { //0x000.say=0x100   0x001.say=0x101
        console.log('SAY');
    };
    /* 如果不设置返回值,或者返回值是一个基本类型值,默认都会把实例对象 0x000 返回;如果手动返回的是一个引用数据类型值,则以自己返回的为主; */
    // return {
    //     name: 'zhufeng'
    // };
}
let f1 = new Fn(); //->0x000
let f2 = new Fn; //->0x001  new执行的时候,如果类不需要传递实参,可以不用加小括号(不加小括号,叫做无参数列表new;设置小括号,叫做带参数列表new;除了是否传递参数的区别,在运算的优先级上也有区别? new Fn->19  new Fn()->20)
// 每一次new都是把函数重新执行(重新形成一个新的私有上下文、重新创建一个实例对象、代码重新执行...)
// console.log(f1, f2, f1 === f2); //=>false

// 检测某个成员(属性/键)是否属于这个对象,或者是否属于这个对象的私有属性
// in:检测成员是否属于这个对象「特点:不论是私有属性,还是公有的属性,只要有则检测结果就是true」
// hasOwnProperty:用来检测当前成员是否为对象的私有属性「特点:只有是私有属性,结果才是ture,哪怕有这个属性,但是属于公有的属性,结果也是false」

// console.log(f1);
// console.log('say' in f1); //->true
// console.log(f1.hasOwnProperty('say')); //->true
// f1是一个对象,他可以访问hasOwnProperty方法并且执行,说明:‘hasOwnProperty’属性是它的一个成员
// console.log('hasOwnProperty' in f1); //->true
// console.log(f1.hasOwnProperty('hasOwnProperty')); //->false 说明‘hasOwnProperty’不是它的私有属性,也就是它的公有属性「前提基于in检测出来的结果是true」

// obj:要检测的对象
// attr:要验证的成员
 function hasPubProperty(obj, attr) {
    // 思路一:是它的属性 但是还不是私有的,那么一定是公有的「BUG:如果某个属性即是私有的,也是公有的,则检测出来的结果是不准确的」
    // return (attr in obj) && (!obj.hasOwnProperty(attr));
    
    // 思路二:真正的思路应该是检测原型上的属性,因为原型上的属性都是公有的
    // Object.getPrototypeOf:获取当前对象的原型
    let proto = Object.getPrototypeOf(obj);
    while (proto) {
        // 依次查找原型链,直到找到Object.prototype为止
        if (proto.hasOwnProperty(attr)) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    return false;
} 

// let sy = Symbol();
// let obj = {
//     name: 'zhufeng',
//     age: 12,
//     3: '哈哈哈',
//     0: 'zhouxiaotian',
//     [sy]: 100
// };
/* console.log(obj);
console.log(obj.hasOwnProperty('name'));
console.log(obj.hasOwnProperty(sy)); //->hasOwnProperty是可以检测Symbol属性的
console.log(sy in obj); //->in也是可以检测Symbol属性的 */

// 很多对“对象”的操作是无法拿到Symbol属性的
// Object.prototype.AAA = 100; //->‘AAA’是obj公共的属性  obj.hasOwnProperty('AAA')->false  'AAA' in obj->true

/* for (let key in obj) {
    console.log(key); //->‘name’ ‘AAA’  
    // for in遍历的时候
    //  + 无法遍历Symobol的私有属性
    //  + 但是可以遍历到自己扩展的公共属性「内置的公共属性是不可枚举的(就是无法遍历到的)」
    //  + 优先遍历数字属性,而且按照从小到大(不会严格按照属性书写的顺序)
} */

/* // 解决:能够避免遍历公共的
for (let key in obj) {
    if (!obj.hasOwnProperty(key)) break; //已经遍历到公共的,则私有已经遍历完,结束循环
    console.log(key); //->'name'
} */

/* // 解决:只想遍历私有的,包含Symbol的
// Object.keys:获取一个对象非Symbol的私有属性(结果是一个数组,数组中包含获取的属性)  
// 类似的还有:Object.getOwnPropertyNames 
// Object.getOwnPropertySymbols:只获取Symbol的私有属性(结果也是一个数组)
let keys = [
    ...Object.getOwnPropertyNames(obj),
    ...Object.getOwnPropertySymbols(obj)
];
keys.forEach(key => {
    console.log(key, obj[key]);
}); */
复制代码

原型和原型链

image.png

原型重定向

image.png

function Fn() {}
Fn.prototype.x = 100;
Fn.prototype.y = 200;

/* // 缺少constructor && 原始原型对象上的x/y也丢失了
Fn.prototype = {
    // getX:function() {}
    getX() {},
    getY() {}
}; */

/* let proto = {
    // 手动设置的constructor是属于可枚举的
    constructor: Fn,
    getX() {},
    getY() {}
};
Fn.prototype = Object.assign({}, Fn.prototype, proto); //->这样合并,最后返回的是一个全新的对象,由于内置的Fn.prototype中的constructor是内置的不可枚举的属性,所以合并后也是无法赋给新对象的 */

/* Fn.prototype = Object.assign(Fn.prototype, {
    getX() {},
    getY() {}
}); //->这种合并的办法,Fn.prototype还是之前的堆地址,只不过是把新对象中的内容全部扩展到了原始的堆中 */

/* 
let obj1 = {
    x: 100,
    y: 200,
    n: {
        0: 1,
        1: 2
    }
};
let obj2 = {
    y: 300,
    z: 400,
    n: {
        name: 'zhufeng'
    }
};
// Object.assign:合并两个对象「浅比较」
//   + 让obj2中的内容替换obj1中的:两者都有的以obj2为主,只有其中一个具备的都是相当于新增...
//   + 最后返回的是obj1对象的堆内存地址「相当于改变的是obj1对象中的内容」,并不是返回一个全新的对象...
// let obj = Object.assign(obj1, obj2);
// console.log(obj === obj1); //true

// let obj = Object.assign({}, obj1, obj2);
// console.log(obj); //->全新的对象,也就是assign的第一个参数「新对象」

// console.log(Object.assign(obj1, obj2)); //->浅比较:obj2.n直接覆盖obj1.n 
*/

/* let obj = {
    fn1() {},
    fn2: function fn2() {}
    // 两者写法的区别:
    //   + 第一种写法:obj.fn1函数是没有prototype属性的 「不能被作为构造函数」
    //   + 第二种写法:和正常的函数没有区别
};
new obj.fn1(); //Uncaught TypeError: obj.fn1 is not a constructor */
复制代码

内置类原型扩展方法

image.png

/* 
 * 向内置类的原型扩展方法
 *   + 内置类的原型上提供了很多内置方法,但是这些方法不一定完全满足业务需求,此时需要我们自己扩展一些方法
 *  「优势」
 *   + 调用起来方便
 *   + 可以实现链式写法
 *   + 限定调取方法的类型,必须是指定类的实例
 *   + 扩展的方法,各个模块「其他成员」都可以直接的调用
 *   + ...
 *  「弊端」
 *   + 自己扩展的方法,容易覆盖内置的方法 (解决:自己设定的方法名要设置前缀 myUnique)
 *     Array.prototype={...} 这样操作是无效的,也怕你一行代码,把数组方法全干没了
 *   + 基于for in遍历的时候,会把自己扩展到原型上的方法也遍历到
 *   + ...
 */
/* 
function unique(arr) {
    let obj = {};
    for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (obj.hasOwnProperty(item)) {
            // 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
            arr.splice(i, 1);
            i--;
            continue;
        }
        obj[item] = item;
    }
    return arr;
}
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr = unique(arr);
*/

Array.prototype.unique = function unique() {
    // this:一般都是当前要操作的实例(也就是要操作的数组)
    let obj = {},
        self = this;
    for (let i = 0; i < self.length; i++) {
        let item = self[i];
        if (obj.hasOwnProperty(item)) {
            // 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
            self.splice(i, 1);
            i--;
            continue;
        }
        obj[item] = item;
    }
    return self; //实现链式写法
};
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr.unique().sort((a, b) => a - b).reverse().push('zhufeng'); //执行完成sort返回的是排序后的数组(原始数组也是变的)... 执行完成push返回的是新增后数组的长度「不能再调数组方法了」 => “链式写法”:执行完成一个方法,返回的结果是某个实例,则可以继续调用这个实例所属类原型上的方法...
console.log(arr);
复制代码

重写内置new

/* 
function Fn() {
    // 创建一个实例对象
    // ---- 也会像普通函数执行一样,让其执行 「THIS指向实例对象」
    // 返回值没有或者是基本值,则返回的是实例对象...
}
let f1 = new Fn; 
*/


// 分析内置new的原理,重写一下
function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}

function _new(Ctor, ...params) {
    // 1.创建一个实例对象「创建Ctor类的实例:实例.__proto__ -> 类.prototype」
    /* let obj = {};
    obj.__proto__ = Ctor.prototype; */
    let obj = Object.create(Ctor.prototype);

    // 2.把函数执行「THIS指向实例对象」  call->执行函数,改变函数中的THIS
    let result = Ctor.call(obj, ...params);

    // 3.处理返回值
    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

// ----https://www.caniuse.com/
// Object.create([proto]):创建一个空对象,并且让创建的这个空对象的.__proto__指向[proto]  “把[proto]作为创建对象的原型”
// let obj = Object.create(); //Uncaught TypeError: Object prototype may only be an Object or null
// let obj = Object.create(Dog.prototype);
// let obj = Object.create(null); //->创建一个空对象,并且阻止了他的__proto__指向「没有这个属性了」
// console.log(obj);
复制代码

函数的多种角色

image.png

JQ源码分析

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享