关于javascript:8大知识点16篇总结含31道面试题解9张思维导图-梳理JavaScript堆栈内存和闭包作用域

1次阅读

共计 50039 个字符,预计需要花费 126 分钟才能阅读完成。

一、JS 的 9 种数据类型的一些细节点

(1)JS 中的数据类型
根本数据类型

1、string,能够用双引号、单引号、反引号

2、number,比方:值有123/1.2/NaN/Infinity/-Infinity...

3、boolean,值为true/false

4、null,值为null

5、undefined,值为undefined

6、bigint

7、symbol,用于创立惟一值

援用数据类型

1、object 例如:

{}一般对象

[]数组对象

③ 日期对象

④ 正则,比方:/^\d+$/

⑤ ……

2、function

① 一般函数

② 构造函数

③ 箭头函数

④ 生成器函数

⑤……

(2)number 的一些细节
number类型的值有:

1、负数、正数、零、小数 ……

2、NaN not a number 不是一个有效数字,然而它是 number 类型的

xxx,你不是一个人。不是一个人,那是什么都有可能了

NaNNaN 自身不相等,和其余值也不相等

isNaN(vlaue)检测以后值 是否不是一个有效数字,不是有效数字返回 true;反之,是有效数字返回 false

Object.is(NaN, NaN)后果是true,它的外部做了非凡解决

3、Infinity无限大 -Infinity无限小

console.log(typeof NaN); //=> 'number'
console.log(typeof Infinity); //=> 'number'
console.log(NaN == NaN); //=> false
console.log(NaN === NaN); //=> false
console.log(Object.is(NaN, NaN)); //=> true

△ NaN

Object.is https://developer.mozilla.org…

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

1、显式转换:Number(vlaue) 或者 parseInt(value)/parseFloat(value) 他们底层解决的规定不一样

2、隐式转换(逻辑用的是 Number(value) 的)

① 数学运算

② 基于 == 比拟的时候

isNaN(value)

④ ….

(3)字符串的一些细节点

string字符串:单引号、双引号、反引号,外面的内容都是字符串

其它值转换为字符串:

1、显式转换:String(value)或者 (vlaue).toString() 波及到数据类型检测,前面再说

2、隐式转换:加号除了数学运算,还会产生字符串拼接

+ 加号是斜杠青年
let n = '10',
    m = 10;
console.log(10 + n);
console.log(+n);
console.log(++n);

let obj = {};
console.log(10 + obj);
console.log(10 + new Number(10));
console.log(10 + {id:'zhaoxiajingjing'});

△ 后果是多少?

+ 作为斜杠青年,本职工作是数学运算符,还斜杠负责了字符串拼接的工作,那它什么时候切换角色呢?

+只有一边有内容时 :比方+n,把值转换为数字;++n/n++ 也会把值转换为数字,而后再进行 前置 / 后置自增 的运算

+ 两边都有内容时

1、”+” 有一边呈现了字符串,就会变成 字符串拼接

2、”+” 有一边是对象,则也可能会成为字符串拼接:

△ 图 1.1_”+” 作为一枚斜杠青年

其中:①③失去的是数字 10,起因是:{...} 没有参加运算,浏览器认为这是一个代码块,计算的是+10

而:console.log({}+10) 有一个括号把 {}+10 包起来了,它会认为这是一个整体再进行运算

那么,对象在做数学运算时的底层机制:

(1)检测对象的Symbol.toPrimitive【primitive [ˈprɪmətɪv] n. 原始的】这个属性值,如果有则基于这个值进行运算,如果没有,走下一步

(2)检测对象的 valueOf() 这个值【原始值 / 根本类型值】,如果有则基于这个值进行运算,如果不是原始值,走下一步

(3)获取对象的 toString() 把其变为字符串 => 如果是 ” +“ 解决,则看到字符串了,变为字符串拼接

(4)如果最初就是想要失去的数字,则再把字符串转换为数字即可

let obj = {[Symbol.toPrimitive]:function (){return 10;}
};
console.log(10 + obj); //=> 20

△ 对象取得的是数字

而,console.log(10 + new Number(10))的后果就是数字 20,是因为new Number(10).valueOf() 取得的原始值就是数字 10

∴ 答案是:

let n = '10',
    m = 10;
console.log(10 + n); //=> 字符串拼接:'1010'
console.log(+n); //=> 把值转换为数字:10
console.log(++n); //=> 把值转换为数字,在前置自增:11

let obj = {};
console.log(10 + obj); //=> '10[object Object]'
console.log(10 + new Number(10)); //=> 20
console.log(10 + {id:'zhaoxiajingjing'}); //=> '10[object Object]'

△ + 是一枚斜杠青年

那么,请问:i=i+1 i+=1; ++i/i++ 这三个一样吗?

其中:i=i+1i+=1 是一样的;++i/i++大部分状况是与后面的一样的。

如果 i 的值是字符串则不一样了:

i=i+1 i+=1会解决为字符串拼接

++i/i++先把值转为数字,再进行前置 / 后置累加

(4)symbol 惟一值

API:https://developer.mozilla.org…

Symbol():创立惟一值

Symbol() 函数会返回 symbol 类型的值

new Symbol()报错:Uncaught TypeError: Symbol is not a constructor

△ 图 1.2_symbol 类型

Symbol.toPrimitive

API https://developer.mozilla.org…

Symbol.toPrimitive是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数

let obj = {[Symbol.toPrimitive]:function (hint){console.log(hint); // hint 取值:"number/string/default"
        return 10;
    }
};
console.log(10 + obj); //=> 20    hint 输入 'default'

Number(obj); //=> hint 输入 'number' obj 的原始值 10
String(obj); //=> hint 输入 'string' obj 的原始值 '10'

△ Symbol.toPrimitive

(5)BigInt 大数

API:https://developer.mozilla.org…

△ 图 1.3_bigint

二、JS 的 4 大数据类型转换规则

0 / 把其余数据类型转换为 Number 类型

(1)指定须要转换为 Number 的

1、Number(value)

2、parseInt(string, radix)/parseFloat(string)

Number 转换机制

把其余类型(string/boolean/null/undefined/symbol/bigint/object)应用 Number 转换为数字:

1、字符串中只有呈现非有效数字,后果就是NaN

2、Number(true) 是 1,Number(false) 是 0

3、Number(null)是 0,Number(undefined)是 NaN

4、Number(Symbol('A')) 报错

5、Number(BigInt(10)) 是数字

6、对象变为数字:先调取 Symbol.toPrimitive 获取原始值,没有再通过valueOf 取得原始值;如果没有原始值,再调取 toString 变为字符串,最初把字符串转为数字

△ 图 1.1_Number 的转换

parseInt/parseFloat 转换机制

parseInt转换机制 :从字符串左侧第一个字符开始,查找有效数字字符(遇到非有效数字字符就进行查找,不论前面是否还有数字都不要了), 把找到的有效数字字符转换为数字,如果一个都没找到后果就是NaN

parseFloatparseInt 多辨认一个小数点

(2)隐式转换

1、isNaN(value),其余数据类型先通过 Number 转为数字类型

2、数学运算:+-*/%。数学运算,其余数据类型先用 Number 转换为数字类型再计算(非凡状况:+ 作为斜杠青年,当遇到字符串时,是字符串拼接)

3、在 == 比拟时,有些值须要转为数字再进行比拟

4、……

(3)练习题
parseInt("")
Number("")
isNaN("")
parseInt(null)
Number(null)
isNaN(null)
parseInt("12px")  
Number("12px")
isNaN("12px")
parseFloat("1.6px")+parseInt("1.2px")+typeof parseInt(null)
isNaN(Number(!!Number(parseInt("0.8"))))
typeof !parseInt(null) + !isNaN(null)

△ 其余数据类型转换为数字类型

L1parseInt("") 没有找到有效数字字符 => NaN

L2Number("") => 0

L3isNaN("") => isNaN 办法调用的是 Number 转换数据类型 => false

L4parseInt(null) => parseInt(“null”) => NaN

L5Number(null) =>0

L6isNaN(null) => false

L7parseInt("12px") => 12

L8Number("12px") => NaN

L9isNaN("12px") => true

L10parseFloat("1.6px")+parseInt("1.2px")+typeof parseInt(null)

=> typeof parseInt(null) => typeof NaN => “number”

=> 1.6 + 1 + “number”

=> “2.6number”

加号左右两边呈现字符串,此时加号变为字符串拼接(有特殊性),如果呈现对象也会变成字符串拼接,本来应该是把对象转为数字,然而对象要先转换为字符串,则遇到加号字符串就变成字符串拼接了

L11isNaN(Number(!!Number(parseInt("0.8"))))

=> parseInt(“0.8”) => 0

=> !!Number(0) => false

=> Number(false) => 0

=> isNaN(0) => false

L12typeof !parseInt(null) + !isNaN(null)

=> typeof !NaN + !false

=> typeof true + true

=> “boolean” + true

=> “booleantrue”

let result = 10+false+undefined+[]+'Tencent'+null+true+{};
console.log(result);

△ 答案是?

10+false+undefined+[]+'Tencent'+null+true+{}

=> 10 + false 没有遇到字符串和对象,数学运算:10+0 => 10

=> 10 + undefined => 10 + NaN => NaN

=> NaN + [] =>”NaN”

对象数据类型转换为数字:先转换为字符串再转换为数字

在转换为字符串后,遇到了加号 => 字符串拼接

=>”NaN” + ‘Tencent’ +null+true+{}

=> “NaNTencentnulltrue[object Object]”

链接:"+" 的斜杠身份:数学运算,字符串拼接

(4)思考题
let arr = [10.18, 0, 10, 25, 23];
arr = arr.map(parseInt);
console.log(arr);

△ 思考题

1 / 把其余数据类型转为字符串

(1)显式转换

1、toString()

2、String()

其余数据类型(number/boolean/null/undefined/symbol/bigint/object)转换为字符串,个别都是间接用 "" 引号包起来,只有 {} 一般对象调取 toString() 办法。调取的是 Object.prototype.toString() 办法(返回值:"[object Type]"),不是转换字符串,而是检测数据类型的:({id:"zhaoxiajingjing"}).toString() => "[object Object]"

△ 图 1.2_其余数据类型转换为字符串

(2)隐式转换(个别调取 toString 办法)

1、加号运算时候,如果有一边呈现字符串,则是字符串拼接

2、把对象转为数字:须要先调用 toString() 转换为字符串,再去转换为数字

3、基于 alert/confirm/prompt/document.write... 这些办法输入内容,都是先把内容转化为字符串,再输入的

4、……

3 / 把其余数据类型转换为布尔

把其余类型(string/number/null/undefined/symbol/bigint/object)转换为布尔类型:

只有 5 个值会变成布尔类型的 false:空字符串 /0/NaN/null/undefined,其余都是 true

(1)其余数据类型转换为布尔

1、!转换为布尔值后取反

2、!!转换为布尔类型

3、Boolean(value)

(2)隐式转换

在循环或者条件判断中,条件解决的后果就是布尔类型值

4 / 在 == 比拟时,数据类型转换的规定

(1)须要留神的点

1、{}=={} false 对象数据类型比拟的是堆内存地址

2、[]==[] false 对象数据类型比拟的是堆内存地址

3、NaN==NaN false

(2)类型不一样的转换规则

1、null==undefined true 其余数据类型的值与 null/undefined 都不相等

null===undefined false 它俩类型不一样

2、字符串 == 对象 把对象转换为字符串

3、剩下的,如果 == 两边数据类型不统一,须要转换为 <u>数字</u> 再进行比拟

(3)题
console.log([] == false);
console.log(![] == false);

△ 比拟 ==

[] == flase

类型不一样,须要转换为数字再进行比拟:隐式转换

1、对象转换为数字:先 toString 转换为字符串(先基于 Symbol.toPrimitive 取得原始值,没有的话基于 valueOf 取得原始值,没有原始值再去toString), 在转换为数字

2、[]=>""=>0

3、false=>0 true=>1

4、后果是:[]==false => 0==0 => true

![]==false

运算符优先级:!== 的要高

1、![] 把数组转换为布尔类型,再取反:!true => false

其余数据类型转换为布尔类型,是 false 的只有 5 个:空字符串 /0/NaN/null/undefined,其余都是 true

2、后果是:false == false => true

三、变量晋升解决机制

0 / 变量晋升解决机制

变量晋升:在以后上下文中(全局 / 公有 / 块级),JS 代码自上而下执行之前,浏览器会解决一些事件(能够了解为词法解析的一个环节,词法解析肯定是产生在代码执行之前的):

会把以后上下文中所有带 var/function 关键字的进行提前申明或者定义

var a = 10;

① 申明 declare:var a;

② 定义 defined:a = 10;

带 VAR 的只提前申明

带 FUNCTION 的会提前申明 + 定义

函数名就是变量名,函数是对象数据类型的须要堆内存存储

当初代码根本都用 ES6 语法的 let/const 写了,所以 var 的变量晋升会少很多。申明函数也尽量应用函数表达式来写,这样能够躲避掉间接应用 function 申明函数的而产生的变量晋升

1 / 练习题目

(1)var 的变量晋升
/*
EC(G) 全局执行上下文
VO(G) 变量对象
     var a;  默认值是 undefined
 -----
 代码执行:*/
console.log(a); //=> undefined
var a = 12; //=> 创立 12,a=12 赋值(申明在变量晋升阶段实现了,浏览器懒得不会做反复的事件)a = 11; //=> 全局变量 a = 11;
console.log(a); //=> 11

△ 变量晋升

数据类型:

① 根本数据类型(string/number/boolean/null/undefined/symbol/bigin)

② 对象数据类型(object/functioin)

① 根本数据类型值:间接存储在栈内存中

② 对象数据类型值:因为数据类型比较复杂,存储在堆内存中,把堆内存的 16 进制地址放到栈内存中与变量关联起来

(2)function 的变量晋升
/*
EC(G) 全局执行上下文
VO(G) 变量对象
       fn = 0x000001 堆内存地址[申明 + 定义]
 -----
 代码执行:*/
fn(); //=> 函数执行的后果:输入 "hello"
function fn(){
    var a = 12;
    console.log('hello');
}

△ 函数

我的项目开发中举荐:函数表达式 var fn = function (){};

这样,在变量晋升阶段只会申明变量,不会赋值

函数表达式
fn(); //=> Uncaught TypeError: fn is not a function
// 报错前面的代码就不执行了
var fn = function (){
    // 函数表达式:在变量晋升阶段只会申明 fn,不会赋值了
    console.log('hello');
};
fn();

△ 函数表达式

匿名函数具名化
var fn = function AA(){console.log('hello');
};
AA(); //=> Uncaught ReferenceError: AA is not defined

△ 匿名函数具名化

var fn = function AA(){console.log('hello');
    console.log(AA); //=> 输入以后函数体
};
fn();

△ 匿名函数具名化

把本来作为值的函数表达式的匿名函数“具名化”:

① 这个名字不能在函数体内部拜访,也就是不会呈现在以后上下文中

② 函数执行时,造成公有上下文,会把这个“具名化”的名字作为该上下文中的公有变量(它的值就是这个函数体)来解决

③ 在函数体外部不能批改这个名字的值,除非是从新申明这个变量

(3)不带 var 的
/*
EC(G) 全局执行上下文
VO(G) 变量对象

-----
代码执行:*/
console.log('ok'); //=> 'ok'
//=> 没有写 VAR/FUNCTION 的,不能在定义前应用
console.log(a); //=> Uncaught ReferenceError: a is not defined
a=12;
console.log(a);

△ 不带 var 的

(4)带 let 的
/*
EC(G) 全局执行上下文
VO(G) 变量对象

-----
代码执行:*/
console.log('ok'); //=> 'ok'
//=>LET/CONST 没有变量晋升
console.log(a); //=> Uncaught ReferenceError: a is not defined
let a = 12;
a = 13;
console.log(a);

△ 带 LET 的

(5)在全局上下文的映射

基于 VAR/FUNCTION 在 <u> 全局上下文 EC(G)</u> 中申明的变量(全局变量)会“<u> 映射 </u>”到 <u>GO(window 全局对象)</u> 上一份,作为它的属性;而且一个批改另外一个也会跟着批改

var a = 12;
console.log(a); //=> 12 全局变量
console.log(window.a); //=> 12 映射到 GO 上的属性

window.a = 11;
console.log(a); //=> 11 映射机制:一个批改另一个也会批改

△ 全局上下文的映射机制

2 / 变量晋升的题目

fn(); 
function fn(){ console.log(1); } 
fn();
function fn(){ console.log(2); }
fn();
var fn = function(){ console.log(3); }
fn();
function fn(){ console.log(4); }
fn();
function fn(){ console.log(5); }
fn();

△ 答案是?

四、函数底层运行机制


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

△ 援用数据类型:object

(2)第二题
var x = [12, 23];
function fn(y) {y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
}
fn(x);
console.log(x);

△ 援用数据类型:function

这些题是不是很简略?咱们次要看逻辑:

1 / 援用数据类型:object

在 Web 浏览器中执行 JS 代码,会开拓一块栈内存来作为执行环境:ECStack(Execution Context Stack)

会开拓一块栈内存供全局代码执行:全局执行上下文 EC(G)(Execution Context Global),还有其余的上下文:函数公有执行上下文、块级公有上下文…… 本人管好本人那一摊的代码执行内容

造成的执行上下文都会 进栈 到执行环境栈中运行. 公有上下文会在不被占用时出栈开释,浏览器的回收机制GC. 当浏览器敞开时,全局执行上下文就会 出栈 开释了

△ 图 2.1_第一题,简图

GO:全局对象 Global Object,并不是 VO(G) 全局变量对象 Variable Object Global

全局对象,它是个对象,它就是个堆内存,浏览器关上一加载页面就默认开拓的堆内存。

浏览器提供的一些供 JS 调用的 API,在 Web 浏览器中,全局对象能够通过 window 来拜访的

留神:运算符优先级,要多看看多比划比划【上链接】

留神 根本数据类型值 间接存储在 栈内存 中,援用数据类型值 存在 堆内存

2 / 援用数据类型:function

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

△ 函数执行

(1)第二题,简图

△ 图 2.2_函数执行

△ 图 2.3_数组的格局:键值对

(2)创立函数

创立函数 的步骤:【和创立变量区别不是很大,函数名就是变量名】

① 独自开拓一个堆内存:16 进制地址,函数堆内存中存储的是函数体中的 <u> 代码字符串 </u>

② 创立函数的时候,就申明了它的 作用域[[scope]],也就是所在的上下文环境

③ 把 16 进制地址(16 进制以 0x 结尾)寄存到栈中,供函数名变量名关联援用即可

只创立函数,不执行函数,没啥意义,那就是一堆字符串。

函数执行 的目标:把创立函数的时候在堆内存中存储的 <u> 代码字符串 </u> 变为代码执行

代码执行肯定会有一个执行的环境,它的下级执行上下文,是函数创立的中央

函数执行会造成一个全新的、公有的执行上下文,在公有上下文中,也有寄存本人变量的对象:AO(Active Object 流动对象),它是 VO 的一种。

变量对象: ① 在全局上下文中:VO ② 在公有上下文中:AO

实参都是值。形参是变量。

fn(x):执行函数 fn,把全局上下文中存储的 x 变量关联的值(0x000001),作为实参传递给函数的形参变量

(3)执行函数

执行函数 做了哪些事件:

1、造成了一个全新的、公有的执行上下文EC(xxx)

2、以后公有的上下文中,有一个寄存此上下文内申明的变量的中央 AO(xxx) 公有变量对象

① 形参变量

② 以后上下文中申明的变量

3、进栈执行

4、代码执行之前还要解决很多事件:

① 初始化作用域链

[[scope-chain]]:< 以后本人的上下文, 下级上下文(创立函数时造成的作用域)>

(作用域链有中间,一头是本人执行的上下文,另一头是本人创立时所在的上下文)

即:以后函数的下级上下文是创立函数所在的上下文,就是作用域

当前再遇到函数内的代码执行,遇到一个变量,首先看是否为本人上下文中的公有变量(看 AO 中有没有,有,是本人公有的;没有,不是本人公有的)。如果是公有的变量,则以后变量的操作和外界环境中的变量互不烦扰(没有间接关系);如果不是本人的公有变量,则依照作用域链,查找是否为其下级上下文中的公有变量 ….. 始终找到 EC(G)全局上下文为止:作用域链查找机制

② 初始化 this….

③ 初始化 arguments….

④ 形参赋值:形参都是公有变量,放在 AO 中的。如果不传递实参,默认值是 undefined

⑤ 变量晋升 ….

5、代码自上而下执行

6、…..

7、个别状况下,函数执行所造成的公有上下文,进栈执行完后,会默认 出栈 开释掉

【公有上下文中存储的公有变量和一些值都会被开释掉,目标:为了优化内存空间,缩小栈内存的耗费,进步页面或者计算机的处理速度 ……】

不能出栈开释:以后上下文中某些内容(个别是堆内存地址)被以后上下文的内部的事物占用了,则无奈出栈开释。一旦被开释,前期内部事物就无奈找到对应的内容了

留神:屡次函数执行,会造成多个全新的、公有执行上下文,这些上下文之间没有间接的关系

(4)闭包

个别,很多人认为:大函数返回小函数是闭包。

这只是闭包机制中的一种状况。

闭包:函数执行造成一个公有的执行上下文,此上下文中的公有变量,与此上下文以外的变量互不烦扰;也就是以后上下文把这些变量爱护起来了,咱们把函数的这种爱护机制称为闭包。

闭包不是具体的代码,而是一种机制。

个别状况下,造成的公有上下文很容易被开释掉,这种爱护机制存在工夫太短了,不是谨严意义上的闭包。有人认为,造成的上下文不被开释,才是闭包。此时,不仅 爱护 了公有变量,而且这些变量和存储的值也不会被开释掉,保留 起来了。

闭包的作用:① 爱护 ② 保留

利用闭包的两个作用,能够实现高阶编程技巧,当前再说~

3 / 练习题

(1)第一题
var x = 100;
function fn() {
    var x = 200;
    return function(y) {console.log(y + x++);
    }
}
var f = fn();
f(10);
f(20);

△ 第一题

i++ 后加

△ 图 2.4_后加

(2)第二题
let a=0,
    b=0;
function A(a){A=function(b){alert(a+b++);
    };
    alert(a++);
}
A(1);
A(2);

△ 第二题

(3)第三题
let x = 5;
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 vs VAR 的 5 点区别

<u> 变量晋升 </u>:在以后上下文中,代码执行之前,会把所有 var/function 关键字的进行提前申明或者定义

(1)带 var 的只是提前申明

(2)带 function 的是申明 + 定义

letvar 的区别:

(1)区别 1:var 存在变量晋升,而 let 不存在
console.log(n); //=> undefined
console.log(m); //=> Uncaught ReferenceError: m is not defined【运行时谬误】var n=12;
let m=11;

△ var 的变量晋升

(2)区别 2:全局执行上下文的映射机制

在“全局执行上下文”中,基于 var 申明的变量,也相当于给 GO(全局对象 window)新增一个属性,并且任何一个产生值的扭转,另外一个也会跟着变动(<u> 映射机制 </u>);然而,let 申明的变量,就是全局变量,与 GO 没有任何关系

① let VS var
var n = 12; // VO(G): var n=12 <=> GO:window.n=12
console.log(n, window.n); //=> 12 12
window.n = 11;
console.log(n); //=> 11

let m = 12;
console.log(m, window.m); //=> 12 undefined

△ 映射机制

② 全局执行上下文中:不写 var
x = 11; //=> window.x = 11; 没有写任何关键词申明,相当于给 window 设置一个属性
console.log(x); //=> 先看看是不是全局变量,如果不是,再看看是不是 window 的一个属性
console.log(y); //=> 两个都不是,那就报错,变量未定义 Uncaught ReferenceError: y is not defined【运行时谬误】

△ 不写 var:在我的项目中尽量不要这样写

③ 函数中:不写 var
function fn(){
    /*
    当函数执行时,这里造成公有上下文
    遇到变量 x 时,依据【作用域链查找机制】变量 x 找到全局都没有,如果是设置值的操作,则相当于给 window 设置一个属性
    window.x = 11
    */
    x = 11; // window.x = 11
    console.log(x); //11
}
fn();
console.log(x);// 11

△ 不写 var

function fn(){
    /*
    当函数执行时,这里造成公有上下文
    当遇到变量 y 时,依据【作用域链查找机制】变量 y 找到全局都没有,如果获取的操作,则间接报错,前面的代码就不执行了
    */
    x = 11;
    console.log(y);// Uncaught ReferenceError: y is not defined【运行时谬误】}
fn();
console.log(x);

△ 不写 var

(3)区别 3:反复申明

在雷同上下文中,let不容许反复申明【不论是基于何种形式申明的,只有申明过的,都不能基于 let 反复申明了】;而var 比拟涣散,反复申明也无所谓,反正浏览器也只会依照申明一次来解决的

console.log('hello');
var n = 11;
let n = 12;

△ let 不容许反复申明

在代码执行之前,浏览器须要干很多活儿,比方:词法剖析,变量晋升

【词法分析阶段】如果发现有基于 let/const 并且反复申明变量的操作,则间接报 <u> 语法错误Uncaught SyntaxError: Identifier 'n' has already been declared</u>,整个代码都不会执行了

(4)区别 4:暂时性死区与 typeof

API:https://developer.mozilla.org…

console.log(n); //=>Uncaught ReferenceError: n is not defined

△ 未被申明过的变量

console.log(typeof n); //=> undefined

△ 未被申明过的变量

console.log(typeof n); //=> Uncaught ReferenceError: n is not defined
let n;

△ let 没有变量晋升,在遇到申明之前是不能应用的

(5)区别 5:块级作用域

let/const/function 会产生块级公有上下文,而 var 不会

① 上下文 & 作用域

哪些是,上下文 & 作用域:

1、全局上下文

2、函数执行造成的“公有上下文”

3、块级作用域(块级公有上下文),除了 对象 / 函数 … 的大括号之外的(例如:判断体、循环体、代码块)

API:https://developer.mozilla.org…

API:https://developer.mozilla.org…

while(1 !==1) {// 块级}

for(let i = 0; i<10; i++){// 块级}

if(1==1) {// 块级}

switch(1) { // 块级
    case 1:
        break;
}
switch(1) { 
    case 1:{// 块级
         break;
    }
}

{// 块级}

△ 块级作用域 / 块级公有上下文

{
    var n = 12;
    console.log(n); //=> 12
    let m = 11;
    console.log(m); //=> 11
}
console.log(n); //=> 12
console.log(m); //=>Uncaught ReferenceError: m is not defined

△ 块级作用域

n 是 全局上下文的:代码块不会对他有任何限度

m 是代码块所代表的块级上下文中 公有的

△ 图_debugger

② let 的闭包

浏览器的控制台并不能出现很多货色,而是底层 C ++ 实现的

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

△ 造成了多少个块呢?

△图 1_ 块级上下文

var buttons = document.querySelectorAll('button');
// 浏览器在每一轮循环时,会帮咱们造成“闭包”for (let i = 0; i < buttons.length; i++) {
    /*
        let i = 0;【父级块级上下文:管制循环】i = 0 ; 第一次 循环 公有块级上下文 EC(B1)
        => 以后上下文中,造成一个小函数,被全局的按钮的 click 占用了,=> EC(B1) 不会被开释掉
        => 闭包
    */
    buttons[i].onclick = function () {console.log(` 以后按钮的索引:${i}`);
    };
}

△ let 的闭包

let i = 0; //=> 写在这里循环的时候,就不会产生块级上下文了
for(; i < 5; i++){console.log(i);
}
console.log(i);

六、高阶函数

0 / 题

JS 高阶编程技巧:利用闭包的机制,实现进去的一些高阶编程的形式

1、模块化思维

2、惰性函数

3、柯理化函数

4、compose 组合函数

5、高阶组件 React 中的

6、……

(1)模块化思维

单例 -> AMD(require.js)->CMD(sea.js)-> CommonJS(Node)->ES6Module

// 实现天气板块
var time = '2020-11-01';
function queryData(){// ...CODE}
function changeCity(){// ...CODE}

// 实现征询板块
var time = '2020-11-1';
function changeCity(){// ...CODE}

△ 很久以前的没有模块化思维之前

在没有模块化思维之前,团队合作开发或者代码量较多的状况下,会导致全局变量净化【变量抵触】

团队之前开发,合并到一起的代码,变量命名抵触了,让谁改都不适合,那怎么办呢?

① 闭包

闭包:爱护

临时基于闭包的“爱护作用”避免全局变量净化

然而,每个版块的代码都是公有的,无奈互相调用

// 实现天气板块
(function fn() {
    var time = '2020-11-01';
    function queryData() {}
    function changeCity() {}
})();

(function fn() {
    // 实现征询板块
    var time = '2020-11-1';
    function changeCity() {}
})();

△ 基于闭包的爱护作用

② 某一种计划

把须要供他人调用的 API 办法,挂在到全局上

然而也不能写太多,还是会引起全局变量净化

// 实现天气板块
(function fn() {
    var time = '2020-11-01';
    function queryData() {}
    function changeCity() {}
    window.queryData=queryData;
    window.changeCity=changeCity;
})();

(function fn() {
    // 实现征询板块
    var time = '2020-11-1';
    function changeCity() {}
    window.changeCity=changeCity;
})();

△ 挂在 window 上

③ 再一种计划

对象的特点:每一个对象都是一个独自的堆内存空间每一个对象也是独自的一个实例:Object 的实例,这样即便多个对象中的成员名字雷同,也互不影响

仿照其余后盾语言,obj1/obj2不仅仅是对象名,更被称为【<u> 命名空间 </u>】给堆内存空间起个名字

let obj1 = {
    name:'晚霞的光影笔记',
    id:'zhaoxiajingjing',
    show:function(){}
};
let obj2 = {
    name:'zxjj',
    show:function (){}
};

△ 对象

每个对象都是一个独自的实例,用来治理本人的公有信息,即便名字雷同,也互不影响:【<u>JS 中的单例设计模式 </u>】

④ 进阶一下

实现公有性和互相调用

// 实现天气板块
var weatherModule = (function fn() {
    var time = '2020-11-01';
    function queryData() {}
    function changeCity() {}

    return {
        queryData:queryData,
        changeCity:changeCity
    };
})();

var infoModule = (function fn() {
    // 实现征询板块
    var time = '2020-11-1';
    function changeCity() {}
    // 能够调用其余模块的办法
    weatherModule.queryData();
    return {changeCity:changeCity}
})();

△ 单例模式

(2)惰性函数

惰性函数,肯定要抓住精华:惰性 =>

window.getComputedStyle(document.body)获取以后元素通过浏览器计算的款式,返回款式对象

在 IE6~8 中,不兼容这个写法,须要应用 元素.currentStyle 来获取

① 一开始这样写
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, 'backgroundColor'));

△ 获取款式

当浏览器关上后,在第一次调用 getCss 办法时,就检测了兼容性了,那么,在第二次、第三次调用时是不是就没必要再去检测了

【优化思维】:第一次执行 getCss 咱们就晓得是否兼容了,第二次及当前再次调用 getCss 时,则不想再解决兼容的容错解决了,这就是“<u> 惰性思维 </u>”【就是“懒”,干一次能够搞定的,相对不去做第二次了】

② 优化一下

也能实现,但不是谨严意义上的惰性思维

var flag = 'getComputedStyle' in window;

function getCss(element, attr){if(flag){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, 'backgroundColor'));

△ 优化一下

③ 惰性思维

惰性是啥?就是

懒是啥?能坐着不站着,能躺着不坐着,能少干活就少干活

function getCss(element, attr){
    //=> 第一次执行,依据是否兼容,实现函数的重构
    if('getComputedStyle' in window){getCss = function (element, attr){return window.getComputedStyle(element)[attr];
        };
    } else {getCss = function (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, 'backgroundColor'));

△ 惰性函数 + 重构函数

(3)柯理化函数

函数柯理化:事后解决思维

造成一个不被开释的 闭包 ,把一些信息 存储 起来,当前基于 作用域链 拜访到当时存储的信息,而后进行相干解决。所有合乎这种模式的(闭包利用)都称为 <u> 柯理化函数 </u>

//=> x 是事后存储的值
function curring(x){}
var sum = curring(10);
console.log(sum(20)); //=> 10+20
console.log(sum(20,30)); //=> 10+20+30

△ 请实现柯理化函数

① 把类数组转换为数组:
let arg = {0:10, 1:20, length:2};
let arr = [...arg];
arr = Array.from(arg);
arr = [].slice.call(arg);

△ 类数组转为数组

② 数组求和

数组求和

1、for 循环 /forEach

2、eval([10,20,30].join('+'))

3、[10,20,30].reduce()

命令式编程:[关注怎么做] 本人编写代码,管控运行的步骤和逻辑【本人灵便掌控执行步骤】

函数式编程:[关注后果] 具体实现的步骤曾经被封装称为办法,咱们只须要调用办法即可,无需关注怎么实现的【益处:使用方便,代码量减少。弊病:本人无奈灵便掌控执行步骤】

③ 数组的 reduce 办法

API:https://developer.mozilla.org…

arr.reduce(callback()[, initialValue])

callback(accumulator, currentValue[, index[, array]])

let arr = [10,20,30,40];
let res = arr.reduce(function (result, item, index){console.log(result, item, index);
});

△ reduce


△ 图 1.6_reduce 执行

1、initialValue 初始值不传递,result 默认初始值是数组的第一项,reduce 是从数字第二项开始遍历的

2、每遍历数组中的一项,回调函数被触发执行一次

① result 存储的是上一次回调函数返回的后果。除了第一次是初始值或者数字第一项

② item 以后遍历这一项

③ index 以后遍历这一项的索引

let arr = [10,20,30,40];
let res = arr.reduce(function (result, item, index){console.log(result, item, index);
    return item + result;
});
console.log(res); //=> 100

△ arr.reduce

数组的 reduce 办法:在遍历数组过程中,能够累积上一次解决的后果,基于上次解决的后果持续遍历解决

let arr = [10,20,30,40];
let res = arr.reduce(function (result, item, index){console.log(result, item, index);
}, 0 );

△ reduce 传递初始值了

arr.reduce 传递了 initialValue 了,则 result 的第一次后果就是初始值,item 从数组第一项开始遍历

△ 图 1.7_reduce 执行

本人实现 reduce

Array.prototype.reduce = function reduce(callback, initialValue){
    let self = this,
        i = 0;    //=> THIS:arr

    if(typeof callback !== 'function') throw new TypeError('callback must be a function');
    
    if(typeof initialValue === "undefined"){initialValue = self[0];
        i = 1;
    }
    // 迭代数组每一项
    for(; i < self.length; i++){var item = self[i],
            index = i;
        initialValue = callback(initialValue, item, index, self);
    }
    return initialValue;
};

△ 本人手写 reduce

④ 柯理化函数
function curring(x){return (...args)=>{
        //=> 把事后存储的 x,放到数组的结尾
        args.unshift(x);
        return args.reduce((res,item)=>(res+item), 0);
    };
}
var sum = curring(10);
console.log(sum(20)); //=> 10+20
console.log(sum(20,30)); //=> 10+20+30

△ 柯理化函数

(4)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 函数的编写

② 答题
function compose(...funcs){return function(x){
        let result,
            len=funcs.length;
       
        if(len===0){
             // 一个函数传递,那就把参数间接返回
            result=x;
        }else if(len===1){
            // 只传递了一个函数
            result=funcs[0](x);
        }else{
            // funcs 参数执行程序从右到左
            result=funcs.reduceRight((result, item) => {if(typeof item !== 'function') return result;
                return  item(result);
            },x);
        }
        return result;
    }
}

△ 实现 compose 组合函数

七、redux 的 compose 函数

1 / redux 中的 compose 函数

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

△ redux 中的 compose 函数

咱们一般写调用函数,然而可读性太差

const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;
div2(mul3(add1(add1(0)))); //=>3

△ 一般调用函数

那么,须要写一个可读性较高的组合函数:

const operate = compose(div2, mul3, add1, add1);
operate(0);

△ 调用 compose 函数

compose(div2, mul3, add1, add1)传参的程序与div2(mul3(add1(add1(0)))) 调用函数的程序关系

那么,咱们上次写的 reduceRight 从外面往外调用函数实现组合函数的调用

然而,redux 应用的是 reduce 依照输出参数的顺序调用的

2 / 逐渐剖析

咱们就依据:执行上下文、作用域、作用域链、VO、AO 这些一步步剖析即可

// funcs=[div2,mul3, add1, add1] 函数汇合
//  return funcs.reduce((a, b) => (...args) => a(b(...args)))
return funcs.reduce((a, b) => {let fn = (x) => {return a(b(x));
    };
    return fn;
});

△ 剖析 reduce 的写法

(1)compose()函数调用

【0】EC(C001) 假如这是 compse() 执行造成的执行上下文

operate 是一个函数

那么,compose return 进来的是一个函数

let result = funcs.reduce((a,b)=>{return function anonymous(x){return a(b(x));
    };
});

△ result 失去的是一个函数

result 接管到的后果是一个函数

此时,须要通过 reduce 每次调用 callback 造成 函数公有上下文

在每次的函数的公有上下文中,都会创立一个匿名函数

每个匿名函数所处的作用域是不同的

代码执行到:funcs.reduce(callback)

① reduce 第一轮遍历

【1】第一轮遍历 EC(CB1)公有执行上下文

AO(CB1) 变量对象

​ a=div2

​ b=mul3

​ anonymous=0xA001

作用域链:<EC(CB1), EC(C001)>

形参赋值:a=div2; b=mul3

变量晋升:anonymous=0xA001

代码执行:

return function anonymous(x){a(b(x));
}; //=====>【return 0xA001】;

△ 第一轮循环返回的值

② reduce 第一轮遍历

【2】第二轮遍历 EC(CB2) 公有执行上下文

AO(CB2) 变量对象

​ a=0xA001

​ b=add1

​ anonymous=0xA002

作用域链:<EC(CB2), EC(C001)>

形参赋值:a=0xA001; b=add1

变量晋升:anonymous=0xA002

代码执行:

return function anonymous(x){a(b(x));
}; //=>【return 0xA002】;

△ 第二轮循环返回的值

③ reduce 第三轮遍历

【3】第三轮遍历 EC(CB3)公有执行上下文

AO(CB3) 变量对象

​ a=0xA002

​ b=add1

​ anonymous=0xA003

作用域链:<EC(CB3), EC(C001)>

形参赋值:a=0xA003; b=add1

变量晋升:anonymous=0xA003

代码执行:

return function anonymous(x){a(b(x));
}; //=>【return 0xA003】;

△ 第三轮循环返回的值

(4)reduce 遍历完结后,赋值

三轮遍历完结后,把0xA003 赋值给operate

operate(0) 执行

③ 0xA003(0) 执行

【3】EC(0xA003)

AO(0xA003)

​ x=0

作用域链:<EC(0xA003),EC(CB3) >

形参赋值:x=0

代码执行:

a(b(x));

​ => x 是本人的:x=0; a 和 b 都是下级上下文的

​ => a=0xA002

​ => b=add1

​ ==> 0xA002(add1(0))

​ => add1(0) => x+1=1

​ => add1(0) 就当它是最初后果了,为了最初看到的成果是一样的,就不写计算了

​ => 0xA002() 调用

② 0xA002() 调用

【2】EC(0xA002)

AO(0xA002)

​ x = add1(0)

作用域链:<EC(0xA002),EC(CB2) >

形参赋值:x=add1(0)

代码执行:

a(b(x));

​ => x 是本人的:x=add1(0);a 和 b 都是下级上下文的

​ => a=0xA001

​ => b=add1

​ ==> 0xA001(add1(add1(0)))

​ => add1(add1(0))就当是计算后的后果了

​ => 0xA001() 调用

① 0xA001() 调用

【1】EC(0xA001)

AO(0xA001)

​ x = add1(add1(0))

作用域链:<EC(0xA001),EC(CB1) >

形参赋值:x = add1(add1(0))

代码执行:

a(b(x));

​ => x 是本人的:x=add1(add1(0)); a 和 b 都是下级上下文的

​ => a=div2

​ => b=mul3

​ ==> div2(mul3(add1(add1(0))))

即:div2(mul3(add1(add1(0))))

八、闭包利用之循环事件绑定的 N 种解决办法

(1)事件绑定
<button> 我是 1 </button>
<button> 我是 2 </button>
<button> 我是 3 </button>

△ html

var buttons = document.querySelectorAll('button'); //=> NodeList“类数组”汇合
for(var i = 0; i < buttons.length; i++){buttons[i].onclick = function (){console.log(` 以后按钮的索引:${i}`);
    };
}

△ JS 代码

问:以上的 JS 代码,能顺次点击按钮能输入对应的索引吗?

△ 图 1_事件执行

每次点击触发函数执行时,获取的 i 都是全局的,也就是循环完结后的后果 3

那么,如何解决这个问题呢?

(2)计划一:基于闭包的机制实现
第一种闭包
var buttons = document.querySelectorAll('button');
for(var i = 0; i < buttons.length; i++){(function (i){
        /*【自执行匿名函数】每一轮循环都会造成一个闭包
            存储公有变量 i 的值,以后循环传递进来的 i 值(1)自执行函数执行,产生一个公有的执行上下文 EC(A),公有形参变量 i =0/1/2(2)EC(A) 上下文中创立一个小函数,并让全局的 buttons 中的某一项占用创立的小函数    
        */
        buttons[i].onclick = function (){console.log(` 以后按钮的索引:${i}`);
        };
    })(i);
}

△ 自执行函数

△ 图 2_自执行函数图解

基于 <u> 闭包 </u> 的机制,每一轮循环时都会产生一个闭包,<u> 存储对应的索引 </u>。点击事件触发,执行对应的函数,让其下级上下文是闭包即可

第二种闭包

基于这个思路,还能够这样写,只有产生闭包就好啦

var buttons = document.querySelectorAll('button');
for(var i = 0; i < buttons.length; i++){buttons[i].onclick = (function (i){return function (){console.log(` 以后按钮的索引:${i}`);
        };
    })(i);
}

△ 闭包:自执行函数

let obj = {fn:(function() {
        // 自执行函数:把 return 的小函数赋值给 obj.fn 了
        console.log('大函数');
        return function () {console.log('小函数');
        };
    })()};
obj.fn(); //=> 每次调用时,执行的是【小函数】这个函数
第三种闭包
var buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {buttons[i].onclick = function () {console.log(` 以后按钮的索引:${i}`);
    };
}

△ 通过 let 造成闭包

(3)计划二:自定义属性

自定义属性的性能要比闭包好。

循环多少次闭包会造成多少个执行上下文,那如果有 100 个按钮,1000 个按钮呢?十分耗性能

var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
    // 每一轮循环都给以后按钮(对象)设置一个自定义属性:存储它的索引
    buttons[i].myIndex = i;
    buttons[i].onclick = function () {
        // this: 以后点击的按钮
        console.log(` 以后按钮的索引:${this.myIndex}`);
    };
}

△ 自定义属性

△ 图 3_自定义属性

(4)计划三:事件委托
<button index='0'> 我是 1 </button>
<button index='1'> 我是 2 </button>
<button index='2'> 我是 3 </button>

△ 增加自定义属性

事件委托:不管点击 BODY 中的谁,都会触发 BODY 的点击事件

ev.target 是事件源:具体点击的是谁

document.body.onclick = function (ev){
    var target = ev.target,
        targetTag = target.tagName;
    // 点击的是【BUTTON 按钮】if(targetTag === 'BUTTON'){var index = target.getAttribute('index');
        console.log(` 以后按钮的索引:${index}`);
    }
};

△ 事件委托

在闭包中要占用栈内存,自定义属性中要占用对象堆内存空间写属性,而 <u> 事件委托 </u> 并没有额定的占用这些空间,性能是最好的

九、函数防抖(debounce)和函数节流(throttle)

函数的防抖(debounce)和节流(throttle)

在 <u> 高频 </u> 触发的场景下,须要进行防抖和节流,比方:

① 狂点一个按钮 给你的爱豆投票的按钮

② 页面滚动 一下加载 500+ 的数据,往下滚动的时候

③ 输出含糊匹配 百度的搜寻条,每次输出都在触发搜寻的接口

④ ……

函数防抖 debounce
function debounce(func, wait, immediate){if(typeof func !== 'function') throw new TypeError('func must be a function');
    if(typeof wait === 'undefined') wait = 500;
    if(typeof wait === 'blloelan') {
        immediate = wait;
        wait = 500;
    }
    if(typeof immediate !== 'boolean') immediate = false;
    
    let timer = null;
    return function proxy(...args){
        let self = this,
            now = immediate && !timer;
        
        clearTimeout(timer);
        timer = setTimeout(()=>{
            timer = null;
            !immediate ? func.call(self, ...args) : null;
        }, wait);
        
        now ? func.call(self, ...args) : null;
    }
}

△ 函数防抖 debounce

函数节流 throttle
function throttle(func, wait){if(typeof func !== 'function') throw new TypeError('func must be a function');
    if(typeof wait === 'undefined') wait = 500;
    let timer = null,
        previous = 0;
    return function proxy(...args){
        let self = this,
            now = new Date(),  
            remaining = wait - (now - previous); 
        if(remaining <= 0) {clearTimeout(timer);
            timer = null;

            previous = now; 
            func.call(self, ...args);
        } else if(!timer){timer = setTimeout(function (){clearTimeout(timer);
                timer = null;

                previous = new Date(); 
                func.call(self, ...args);
            }, remaining);
        }
    };
}
function handle(){console.log('world');
}
window.onscroll = throttle(handle, 500);
// window.onscroll = proxy;

△ 函数节流

十、严格模式 VS 非严格模式的区别

API

开启严格模式:在执行上下文的最顶部 "use strict"; 或者'use strict';

严格模式同时扭转了 <u> 语法 </u> 及 <u> 运行 </u> 时行为,分为几类:

1、将问题间接转化为谬误,比方:语法错误或运行时谬误

(1)严格模式下无奈创立全局变量

"use strict";
n = 12; //=>Uncaught ReferenceError: n is not defined

(2)严格模式下给 NaN 赋值、给不可写属性赋值、给制度属性赋值、给不可扩大对象的新属性赋值,都会抛出异样

"use strict";
NaN = 12;// Uncaught TypeError: Cannot assign to read only property 'NaN' of object '#<Window>'
"use strict";
let obj = {};
Object.defineProperty(obj, 'x', {value:12, writable:false});
obj.x = 11; //Uncaught TypeError: Cannot assign to read only property 'x' of object '#<Object>'
"use strict";
let obj1 = {get y() {return 11;}};
obj1.y = 12; // Uncaught TypeError: Cannot set property y of #<Object> which has only a getter
"use strict";
let obj2 = {};
Object.preventExtensions(obj2);
obj2.newProp = 'hello'; // Uncaught TypeError: Cannot add property newProp, object is not extensible

(3)严格模式下,删除不可删除的属性报错

"use strict";
delete Object.prototype; // Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] }

(4)严格模式下,函数参数名惟一

"use strict";
function sum(a, a, c) { //Uncaught SyntaxError: Duplicate parameter name not allowed in this context
    return a + a + c; 
}

(5)严格模式下的八进制

ES6 中反对用 0o 示意八进制,在浏览器中反对以 0 结尾的八进制语法

"use strict";
var sum = 015 +  // Uncaught SyntaxError: Octal literals are not allowed in strict mode.
          197 +
          142; 
// 写成 0o15

(6)非严格模式会疏忽,严格模式下报错

(function () {
    "use strict";

    false.true = "";              // Uncaught TypeError: Cannot create property'true'on boolean'false'(14).sailing ="home";        // Uncaught TypeError: Cannot create property'sailing'on number'14'"with".you = "far away";      // Uncaught TypeError: Cannot create property 'you' on string 'with
})();

2、简化变量的应用

(1)严格模式下禁用with

var x = 12;
var obj = {x:11};
with(obj) {console.log(x); //=> 11。是 obj 上的【非严格模式】};

(2)严格模式下的 eval 不再为下层范畴引入新变量

var x = 17;
var evalX = eval("var x = 42;x");
console.log(x, evalX); //=> 42 42
var x = 17;
var evalX = eval("'use strict'; var x = 42; x");
console.log(x, evalX);//=> 17 42

(3)严格模式进制删除申明变量,非严格模式下疏忽

"use strict";

var x;
delete x; // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.

eval("var y; delete y;"); // !!! 语法错误

3、简化了 eval 以及arguments

(1)以下都会报语法错误:eval/arguments 不容许做变量名

"use strict";
eval = 17;
arguments++;
++eval;
var obj = {set p(arguments) {}};
var eval;
try {} catch (arguments) { }
function x(eval) { }
function arguments() {}
var y = function eval() {};
var f = new Function("arguments", "'use strict'; return 17;");

(2)严格模式下,参数的值不会碎 arguments 对象的值的变动而变动。切断映射关系

function f(a) {
    a = 42;
    return [a, arguments[0]];
}
var pair = f(17);
console.log(pair); //[42, 42]
function f(a) {
    "use strict";
    a = 42;
    return [a, arguments[0]];
}
var pair = f(17);
console.log(pair); //[42, 17]

(3)严格模式下,不反对arguments.callee, 间接应用函数名即可

"use strict";
var f = function() { return arguments.callee;};
f(); // 抛出类型谬误

4、“平安的”JS

(1)函数执行主体 THIS,严格模式下,传啥就是啥

"use strict";
function fun() { return this;}
console.assert(fun() === undefined);
console.assert(fun.call(2) === 2);
console.assert(fun.apply(null) === null);
console.assert(fun.call(undefined) === undefined);
console.assert(fun.bind(true)() === true);
function fun() { return this;}
console.log(fun() === undefined, fun()); // window
console.log(fun.call(2) === 2, fun.call(2)); // Number(2)
console.log(fun.apply(null) === null, fun.apply(null)); // window
console.log(fun.call(undefined) === undefined, fun.call(undefined)); // window
console.log(fun.bind(true)() === true, fun.bind(true)()); // Boolean(true)

(2)严格模式下,不容许应用fn.caller/fn.arguments(不可被删除的属性)了

function restricted() {
  "use strict";
  restricted.caller;    // 抛出类型谬误
  restricted.arguments; // 抛出类型谬误
}

function privilegedInvoker() {return restricted();
}

privilegedInvoker();

(3)严格模式下,arguments.caller不容许应用了(在严格模式下也是不可删除属性)

"use strict";
function fun(a, b) {
  "use strict";
  var v = 12;
  return arguments.caller; // 抛出类型谬误
}
fun(1, 2); // 不会裸露 v(或者 a,或者 b)

5、为将来的 ECMAScript 版本铺路

(1)严格模式下一部分字符变成了保留的关键字:implements/interface/let/package/private/public/yield/static,严格模式下,不容许用作变量名或形参名

(2)严格模式下,禁止了不在脚本或者函数层面上的函数申明

"use strict";
if (true) {function f() { } // !!! 语法错误
  f();}

for (var i = 0; i < 5; i++) {function f2() { } // !!! 语法错误
  f2();}

function baz() { // 非法
  function eit() {} // 同样非法
}

十一、面试题 | 8 道题带你坚固【变量晋升】

第一题

console.log(a, b, c);
var a = 12,
    b = 13,
    c = 14;
function fn(a) {console.log(a, b, c);
    a = 100;
    c = 200;
    console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c);

△ 第一题

△ 图 1.1_第一题

第二题

var i = 0;
function A() {
    var i = 10;
    function x() {console.log(i);
    }
    return x;
}
var y = A();
y();
function B() {
    var i = 20;
    y();}
B();

△ 第二题

△ 图 1.2_第二题

第三题

var a=1;
var obj ={name:"tom"}
function fn(){
   var a2 = a;
   obj2 = obj;
   a2 =a;
   obj2.name ="jack";
}
fn();
console.log(a);
console.log(obj);

△ 第三题

△ 图 1.3_第三题

第四题

var a = 1;
function fn(a){console.log(a)
    var a = 2;
    function a(){}
}
fn(a);

△ 第四题

△ 图 1.4_第四题

第五题

//--- 第一小题
console.log(a); 
var a=12; 
function fn(){console.log(a); 
    var a=13;   
}
fn();   
console.log(a);

//--- 第二小题

console.log(a); 
var a=12;
function fn(){console.log(a);
    a=13;
}
fn();
console.log(a);

//--- 第三小题

console.log(a);
a=12;
function fn(){console.log(a);
    a=13;   
}
fn();
console.log(a);

△ 第五题

△ 图 1.5_第五题

第六题

var foo='hello'; 
(function(foo){console.log(foo);
   var foo=foo||'world';
   console.log(foo);
})(foo);
console.log(foo);

△ 第六题

△ 图 1.6_第六题

问:1||2&&3||4后果是?

第七题

//--- 第一问
{function foo() {}
    foo = 1;
}
console.log(foo);

//--- 第二问
{function foo() {}
    foo = 1;
    function foo() {}
}
console.log(foo);

//--- 第三问
{function foo() {}
    foo = 1;
    function foo() {}
    foo = 2;
}
console.log(foo);

△ 第七题

let/const/function 会造成块级作用域

能造成块级作用域的大括号有:

if(1=1){// 块级}
for(let i=0; i<3; i++){// 块级}

while(1!==1){// 块级}

switch(1) { // 块级
    case 1:
        break;
}

switch(1) {
    case 1:{ // 块级
        console.log(1);
        break;
    }
}

{// 块级}

// …… 等

△ let/const/function 能够造成块级作用域

① 第一问
//--- 第一问
{function foo() {}
    foo = 1;
}
console.log(foo);

△ 第一问

△ 图 1.7.1_第一问

老版本浏览器 :没有块级上下文,function 该怎么执行还怎么执行

新版本浏览器:为了兼容 ES3/EC5,同时也要兼容 ES6,产生了一些奇奇怪怪的机制

除了函数 / 对象的大括号,其余的大括号(判断体、循环体、代码块 …..)中有:let/const/function申明的变量,会独自呈现全新的公有的块级执行上下文

这里为啥独自说 function? 是因为:let/const 没有变量晋升,在公有块级执行上下文中就是本人的公有变量

然而function,有共性!变量晋升 + 块级公有上下文它都要小孩子才做选择题,小孩儿啥都要

全局上下文和块级公有上下文,两个大佬,function都惹不起、惹不起、惹不起的,所以……两边都爱~

把第一问改一改题目:

console.log(A1);
{console.log(A1);
    function A1(){}
    A1 = 1;
    console.log(A1);
}
console.log(A1);

△ function 在块级作用域中的状况

/*
EC(G) 全局执行上下文
VO(G) 变量对象
    
--------
变量晋升:function A1;[只申明]【函数 A1 呈现在了块级上下文中,此时:只申明】代码执行:*/
console.log(A1); //=> undefined
{ //=>【进入到:块级公有执行上下文】/*
    EC(Block) 块级公有执行上下文
    AO(Block) 变量对象
        A1 = 0xB00001 [[scope]]:EC(Block)
           = 1
    --------
    作用域链:<EC(Block), EC(G)>【没有 this\arguments\ 形参赋值....】变量晋升:[函数:申明 + 定义]
        A1 = 0xB00001 [[scope]]:EC(Block)
    代码执行:*/
    console.log(A1); //=> 输入函数 0xB000001 function A1(){}
    function A1() {} //【此处:函数的申明 + 定义,在“变量晋升”阶段曾经实现了。】//-->【然而,此处有特殊性:因为函数在全局和块级上下文中都有“提前申明”】//-->【它会把对函数 A1 做的操作,映射到全局上下文上(两边大佬都不得罪)】A1 = 1; //->【只给块级公有上下文的 A1 赋值,不会给全局上下文】console.log(A1); //=> 1
}
console.log(A1); //=> 函数 function A1(){}

△ 块级上下文 +function

△ 图 1.7.1_debugger

② 第二问
//--- 第二问
{function foo() {}
    foo = 1;
    function foo() {}
}
console.log(foo);

△ 第二问

/*
EC(G) 全局执行上下文
VO(G) 变量对象

------
变量晋升:function foo; [只申明]【函数 A1 呈现在了块级上下文中,此时:只申明】代码执行:*/
{//=>【进入到块级上下文】/*
    EC(Block) 块级公有上下文
    AO(Block) 变量对象
    
    -----
    作用域链:<EC(Block), EC(G)>【没有 this\arguments\ 形参赋值....】变量晋升:[函数:申明 + 定义]
        foo = 0xB00001 [[scope]]:EC(Block)
            = 0xB00002 [[scope]]:EC(Block)
    代码执行:*/
    function foo() {} //--> 把在块级上下文对 foo 做过的解决,会同步到全局上下文
    foo = 1; //=> 给块级上下文的公有变量赋值 foo=1
    function foo() {} //--> 把在块级上下文对 foo 做过的解决,会同步到全局上下文
}
console.log(foo); //=> 1

△ 第二问剖析流程

console.log(A1); //=>undefined
{console.log(A1); //=> function A1(m){}
    function A1(n){}
    A1 = 1;
    function A1(m){}
    console.log(A1); //=> 1
}
console.log(A1); //=> 1

△ 革新第二问,新版浏览器

③ 第三问
//--- 第三问
{function foo() {}
    foo = 1;
    function foo() {}
    foo = 2;
}
console.log(foo);

△ 第三问

/*
EC(G) 全局执行上下文
VO(G) 变量对象

-----
变量晋升:function foo;[只申明]
代码执行:*/
{//=>【开启块级公有上下文】/*
    EC(Block) 块级公有上下文
    AO(Block) 变量对象
        foo = 0xB00001 [[scope]]:EC(Block)
            = 0xB00002 [[scope]]:EC(Block)
            = 1
            = 2
    -----
    作用域链:<EC(Block), EC(G)>【没有 this\arguments\ 形参赋值....】变量晋升:foo = 0xB00001 [[scope]]:EC(Block)
            = 0xB00002 [[scope]]:EC(Block)
       代码执行:*/
    function foo() {}
    foo = 1;
    function foo() {}
    foo = 2;
    console.log(foo); //=> 2
}
console.log(foo); //=> 1

△ 第三问,新版浏览器

判断体中的 function
console.log(A1); 
if (1 == 1) {console.log(A1);
    function A1(m) { };
    A1 = 2;
    function A1(n) { }
    console.log(A1);

}
console.log(A1);

△ 条件成立 +function

console.log(A1);
if (1 !== 1) {console.log(A1);
    function A1(m) { };
    A1 = 2;
    function A1(n) { }
    console.log(A1);

}
console.log(A1);

△ 条件不成立 +function

解析:条件成立

/*
EC(G) 全局执行上下文
VO(G) 变量对象

---------
变量晋升:function A1;
代码执行:*/
console.log(A1); //=> undefined
if (1 == 1) {
    /*
    EC(Block) 块级执行上下文
    AO(Block) 变量对象
        A1 = 0xB00001 [[scope]]:EC(Block)
           = 0xB00002 [[scope]]:EC(Block)
           = 2
    --------【没有 this/arguments/ 形参赋值...】变量晋升:A1 = 0xB00001 [[scope]]:EC(Block)
           = 0xB00002 [[scope]]:EC(Block)
       
       代码执行:*/
    console.log(A1); //=> function A1(n) { } ->0xB00002
    function A1(m) { };
    A1 = 2;
    function A1(n) { }
    console.log(A1);//=>2
}
console.log(A1); //=> 2

△ 条件成立,function,新版浏览器

解析:条件不成立

console.log(A1);//=>undefined
if (1 !== 1) { // 条件不成立,不进入判断体
    console.log(A1);
    function A1(m) { };
    A1 = 2;
    function A1(n) { }
    console.log(A1);
}
console.log(A1); //=>undefined

△ 条件不成立,function,新版浏览器

第八题

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

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

//--- 第三问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
    var x = 3;
    var y = function  anonymous1(){x=4}
    y();
    console.log(x);
}
func(5);
console.log(x);

△ 第八题

考察点:形参有默认值 + 函数体中有申明

① 第一问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
    x = 3;
    y();
    console.log(x);
}
func(5);
console.log(x);

△ 第一问,只有形参有默认值的状况

△ 图 1.8.1_第一问,形参有默认值

② 第二问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
    var x = 3;
    y();
    console.log(x);
}
func(5);
console.log(x);

△ 第二问

函数执行

1、无形参赋值的默认值(不论是否传递了实参,不论默认值的类型)

2、函数体中有变量申明

① 必须是let/const/var

② 留神let/const 不容许反复申明,不能和形参变量名统一

此时,除了默认造成的 <u>“函数公有上下文”</u>,还会多创立一个 <u>“块级公有上下文”</u>【把函数体到括号的都包起来了】

在这个 <u>“块级公有上下文”</u> 中:

① 在这里申明的变量都是块级上下文的公有变量,跟函数的公有上下文没半毛钱关系了

② 作用域链 <u><EC(Block), EC(FN)></u>,块级上下文的下级上下文就是这个函数公有上下文

③ 会把在函数公有上下文中,把传递的形参赋值中,映射给块级上下文 <u> 雷同名字的变量 </u>

△ 图 1.8.2_第二问,形参有默认值,函数体内有 var 申明变量

改一改:

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

△ 后果是?

逐渐剖析:

/*
EC(G) 全局执行上下文
VO(G) 变量对象
    func = 0x000001 [[scope]]:EC(G)
    x = 1
-----
变量晋升:var x;
    func = 0x000001 [[scope]]:EC(G)
代码执行:*/
var x = 1;
function func(x, y = function anonymous1(){console.log(x);x=2;}){
    /*
    func(5) 调用函数 0x000001(5)
    EC(func) 公有执行上下文
    AO(func) 变量对象【跟着代码执行会扭转值】x = 5
          = 2【在 y()是扭转的】-----
    作用域链:<EC(func), EC(G)>
    形参赋值:x = 5
        y = 0xB00001 [[scope]]:EC(func)
    
    ① 形参有默认值
    ② 函数体内有变量申明:var/let/const
    则 ---> 
        ① 造成一个全新的公有上下文
        ② 传递的形参赋值,同名的变量同步过来
    */
    //======================
    /*
    EC(Block) 公有上下文
    AO(Block) 变量对象
        x = 5【同名的变量同步过去的】= 3
    ----
    变量晋升:var x;
    代码执行:*/
    console.log(x); //=> 输入 5
    var x = 3;
    y(); //=> EC(Y) 
    /*
    y() --> 0xB00001()
    EC(Y) 公有执行上下文
    AO(Y) 变量对象
    
    -----
    作用域链:<EC(Y), EC(func)>
    ... 省略
    代码执行:console.log(x);【=> x 不是本人的,是 EC(func)的】【=> 输入 5】x=2;
    */
    // 继续执行 EC(Block)的代码
    console.log(x);//=> 是本人的
    //=> 输入 3
}
func(5);
console.log(x); //=> 输入 1

△ 剖析步骤

③ 第三问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
    var x = 3;
    var y = function  anonymous1(){x=4}
    y();
    console.log(x);
}
func(5);
console.log(x);

△ 第三问

△ 图 1.8.3_第三问

改一改:

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

△ 后果是?

var x = 1;
function func(x, y = function anonymous1(){console.log(x);x=2;}){
    var x = 3;
    y(); //=> y[[scope]]:EC(func)
    //=> 作用域链:<EC(Y1), EC(func)>
    //=> x 是 EC(func)中的 5
    //=> 输入 5
    //=> 而后 EC(func) 中的 x=2
    
    
    var y = function  anonymous1(){console.log(x);x=4;}
    y(); //=> y[[scope]]:EC(Block)
    //=> 作用域链:<EC(Y2), EC(Block)>
    //=> x 是 EC(Block)中的 3
    //=> 输入 3
    //=> 而后 EC(Block) 中的 x=4
    
    
    console.log(x); //=> 4
}
func(5);
console.log(x); //=> 1

△ 剖析图就本人画吧

Tips

1、let/const/function 会造成块级上下文,let/const没有变量晋升,然而 function 会有一些奇奇怪怪的事件产生,尽量多应用函数表达式申明let fun = function (){};

2、函数的形参默认值 + 函数体内应用 let/const/var 申明变量,会产生一些奇奇怪怪的事件:尽量不应用形参默认值

十二、面试题 | 4 道题带你坚固【数据类型】

第一题

let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result);

△ 后果是?

从左到右计算:

① 100+true => 100 + 1 = 101

② 101 + 21.2 = 121.2

③ 121.2 + null => 121.2 + 0

④ 121.2 + undefined => 121.2 + NaN = NaN

⑤ NaN + “Tencent” => “NaNTencent” 字符拼接

⑥ “NaNTencent” + [] + null + 9 + false

=> “NaNTencentnull9false”

请问:[]==false![]==false 后果是?

第二题

{}+0?alert('ok'):alert('no');
0+{}?alert('ok'):alert('no');

△ 第二题

△ 图 1_大括号的计算_浏览器控制台输入

{}+0?alert('ok'):alert('no');

=> {}+0 => +0 => 0

=> 0?alert(‘ok’):alert(‘no’);

=> 弹出 ’no’

0+{}?alert('ok'):alert('no');

=> 0+{} => “0[object Object]”

=> “0[object Object]” ? alert(‘ok’):alert(‘no’)

=> 弹出 ’ok’

第三题

let res = Number('12px');
if(res===12){alert(200);
}else if(res===NaN){alert(NaN);
}else if(typeof res==='number'){alert('number');
}else{alert('Invalid Number');
}

△ 第三题

这道题比较简单了~

let res = Number('12px'); //=> res = NaN
if(res===12){alert(200);
}else if(res===NaN){alert(NaN);
}else if(typeof res==='number'){
    // typeof NaN === 'number'
    // 弹出 字符串 number
    alert('number');
}else{alert('Invalid Number');
}

△ 第三题

第四题

let arr = [27.2,0,'0013','14px',123];
arr = arr.map(parseInt);
console.log(arr);

△ 第四题

△ 图_parseInt 的用法,radix:2~36,0 或者不写:十进制

parseInt(27.2, 0)

radix:0 基数是十进制

=> 27

parseInt(0, 1)

radix:1 不在范畴内

=> NaN

parseInt('0013', 2)

radix:2 找到无效字符为二进制的

‘001’

0*2^2+0*2^1+1*2^0

=>1

parseInt('14px', 3)

radix:3 找到无效字符为三进制的

‘1’

1*3^0

=>1

parseInt(123, 4)

radix:4 找到无效字符为四进制的

‘123’

1*4^2+2*4^1+3*4^0

=> 16+8+3

=> 27

后果是:[27, NaN, 1, 1, 27]

十三、面试题 | 11 道题带你坚固【闭包作用域】

第一题

var a = 10,
    b = 11,
    c = 12;
function test(a) {
    a = 1;
    var b = 2;
    c = 3;
}
test(10);
console.log(a, b, c);

△ 第一题

△ 图 3.1_第一题

第二题

var a = 4;
function b(x, y, a) {console.log(a);
    arguments[2] = 10;
    console.log(a);
}
a = b(1, 2, 3);
console.log(a);

△ 第二题

△ 图 3.2_第二题

arguments 类数组对象,实参汇合 {0:1, 1:2, 2:3, length3}

在 JS 的“非严格模式”下,初始实现 arguments 和形参赋值完结后

1、会建设 arguments 和形参之间的映射机制:一一对应

2、只有在这个阶段才会建设映射机制,代码执行的时候,则不再解决这件事件了

function fn(x,y,z){arguments[0] = 10;
    console.log(x);
    
    y=20;
    console.log(arguments[1]);
    
    z=30;
    console.log(arguments[2]);
}
fn(1,2);

△ 形参赋值,arguments

function fn(x,y,z){
    /*
    fn(1,2)
    EC(FN) 公有上下文
    AO(FN) 变量对象
        x=1
        y=2
    ----
    初始化 arguments:{0:1, 1:2, length:2}【arguments 与形参有映射机制】形参赋值:x=1,y=2
    变量晋升:——
    代码执行:*/
    arguments[0] = 10;
    console.log(x);//=> 10
    
    y=20;
    console.log(arguments[1]);//=>20
    
    // 在初始化时,没有 z,所以没有关联
    z=30;
    console.log(arguments[2]);//=> undefined
}
fn(1,2);

△ arguments 实参汇合

当初写代码,须要基于 webpack 编译后的后果都是严格模式:"use strict";

第三题

var a = 9;
function fn() {
    a = 0;
    return function (b) {return b + a++;}
}
var f = fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);

△ 第三题

△ 图 3.3_第三题

第四题

var test = (function (i) {return function () {alert(i *= 2);
    }
})(2);
test(5);

△ 第四题

△ 图 3.4_第四题

第五题

var x = 4;
function func() {return function(y) {console.log(y + (--x));
    }
}
var f = func(5);
f(6);
func(7)(8);
f(9);
console.log(x);

△ 第五题

△ 图 3.5_第五题

第六题

var x = 5,
    y = 6;
function func() {
    x += y;
    func = function (y) {console.log(y + (--x));
    };
    console.log(x, y);
}
func(4);
func(3);
console.log(x, y);

△ 第六题

△ 图 3.6_第六题

第七题

function fun(n, o) {console.log(o);
    return {fun: function (m) {return fun(m, n);
        }
    };
}
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);

△ 第七题

△ 图 3.7_第七题

第八题

简述你对闭包的了解,以及其优缺点?

【第一个维度】:根本介绍:ECStack、EC、VO、AO、GO、SCOPE、SCOPE-CHAIN、GC(垃圾回收机制)

【第二个维度】:优缺点,保留和爱护、性能耗费(可能会引发内存透露不必具体说透露)

【第三个维度】:实战利用

1、循环事件绑定——【突出:事件委托】、let 和 var

2、插件组件利用:JS 高程编程技巧【单例设计模式、惰性函数、柯理化函数、compose 函数】

3、源码浏览利用:Lodash 源码[函数的防抖和节流]、JQ 的源码、redux[createStore\combineReducers]、react-redux【高阶组件】……

4、……

【第四个维度】:本人的思维和了解【一句话概括】

闭包的了解

在函数的公有执行上下文中,有一些事物(个别是堆内存地址)被此上下文以外的事物占用了(比方:事件绑定、变量赋值等),使得此上下文不能出栈开释。这种机制称为“闭包”。

市面上,很多人认为造成不销毁的作用域才是造成了闭包,比方,大家认为一个大函数返回一个小函数,就是闭包。

这是造成闭包的一种状况。

其实,在函数执行时,就造成了闭包,把外面的变量爱护起来,只是有的会在执行完后销毁。大家感觉它太短暂了,就没算在闭包的状况外面。

闭包的长处

闭包会造成一个不被开释的公有执行上下文,在此上下文中的变量和值得以 <u> 保留 </u> 下来,并且会 <u> 爱护 </u> 此上下文中的变量不会被外界的内容烦扰到。

1、爱护:函数执行开拓一个全新的、公有的执行上下文,爱护这外面的变量不受外界的烦扰

2、保留:当这个执行上下文中的一些事物(个别是堆内存地址)被外界的事物占用了,那么此上下文不会被浏览器回收销毁。此时,该上下文中的所有变量都被保留下来了

闭包的毛病

1、大量应用闭包,会造成很多不被开释的栈内存,导致页面渲染变慢,性能受到影响。在理论开发中,应该正当利用闭包

2、有些代码会导致栈溢出或内存透露,须要留神:比方,死递归

第九题

简述 let 和 var 的区别?

申明变量

① var function

② let const import

let VS var 区别:

① 用 var 关键字申明的变量,在代码执行之前,会把提前申明变量 declare。而 let 则不容许提前申明了

② 应用 var 关键字申明的变量,会“映射”一份到 window 上。两边相互影响,一边扭转了,另一边也会扭转。而 let 则不会呈现这种状况了

③ 应用 var 关键字申明的变量,在其前面反复用 var 再申明一次,也是容许的。而 let 申明的变量,不管用任何关键字申明都会在代码执行之前检测进去,并报错

④ 暂存死区

console.log(n); 未被申明过的变量间接应用会报错

console.log(typeof n) 然而在此时就输入 undefined 了

而,console.log(typeof n); let n; 就会报错,修复了这个问题

let/const/function 会造成块级作用域,var 不会

第十题

上面代码输入的后果是多少,为什么?如何革新一下,就能让其输入 20 10

var b = 10;
(function b() {
    b = 20;
    console.log(b);
})();
console.log(b);

△ 第十题

第一问:输入后果是 function b(){b=20;console.log()b;} 10

匿名函数“具名化”:这样的写法是符合规范的

document.body.onclick = function bodyClickHandle(){}

匿名函数具名化,设置的名字不属于以后函数所在上下文中的变量

函数名只能在函数外部应用

[益处]:前期匿名函数也能够实现递归调用

解决了:arguments.callee 在严格模式下会报错

在函数外部间接批改它的值也是有效的

除非函数外部 <u> 从新申明 </u> 这个变量,就能够批改了:let/const/function/var

第二问:如何革新,能输入 20 10

匿名函数具名化:

1、在函数里面是不能调用这个名字的,能够在外面应用这个函数名。优化的是:严格模式下,arguments.caller 报错

2、函数外部,默认是不能批改的,函数名代表的是函数体。

3、从新申明后,就能够批改了

革新:变量申明

var b = 10;
(function b() {
    var b = 20;
    console.log(b);
})();
console.log(b);

△ 在函数体外部从新申明变量

第十一题

实现函数 fn,让其具备如下性能

let res = fn(1,2)(3);
console.log(res); //=>6  1+2+3

△ 第十一题

柯理化函数:事后解决一些事件

function fn(...outArgs){// outArgs = [1,2]
    return function anonyous(...innerArgs){// innerArgs = [3]
        return [...outArgs, ...innerArgs].reduce((result, item)=>result+item,0)
    }
}
let res = fn(1,2)(3);
console.log(res); 

△ 第十一题

十四、面试题图解 | 6+ 1 道题带你坚固【判断 THIS】

△ 图_在 csdn 上看到的题目:https://ask.csdn.net/question…

第一题

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

△ 第一题

△ 图 4.1_第一题

第二题

let obj = {fn: (function () {return function () {console.log(this);
        }
    })()};
obj.fn();
let fn = obj.fn;
fn();

△ 第二题

//=>【第二题解析】let obj = {fn: (function () {return function () {console.log(this);
        }
    })()};
obj.fn(); //=> 点后面是:obj
//=> this:obj 
//=> 输入:obj  {fn:function (){console.log(this)}}


let fn = obj.fn;
fn();//=> 没有点:this:window
//=> 输入:window

△ 第二题解析

第三题

var fullName = 'language';
var obj = {
    fullName: 'javascript',
    prop: {getFullName: function () {return this.fullName;}
    }
};
console.log(obj.prop.getFullName());
var test = obj.prop.getFullName;
console.log(test());

△ 第三题

//=>【第三题解析】var fullName = 'language';
var obj = {
    fullName: 'javascript',
    prop: {getFullName: function () {return this.fullName;}
    }
};
/*
getFullName 的点后面是 obj.prop
    this:obj.prop
    this.fullName => obj.prop.fullName
    => return undefined
*/
console.log(obj.prop.getFullName()); //=> 输入 undefined


var test = obj.prop.getFullName;
/*
    test 后面没有点
    this:window
        => this.fullName => window.fullName
        => return "language"
*/
console.log(test()); //=> 输入 "language"

△ 第三题解析

第四题

var name = 'window';
var Tom = {
    name: "Tom",
    show: function () {console.log(this.name);
    },
    wait: function () {
        var fun = this.show;
        fun();}
};
Tom.wait();

△ 第四题

//=>【第四题解析】var name = 'window';
var Tom = {
    name: "Tom",
    show: function () {console.log(this.name);
    },
    wait: function () {
        // this:Tom
        // fun = Tom.show
        var fun = this.show;
        /*
        fun 后面没有点,this:window
            console.log(window.name);
            输入:"window"
        */
        fun();}
};
/*
    wait 的点后面是 Tom
        this:Tom
*/
Tom.wait(); //=> 输入 "window"

△ 第四题解析

第五题

window.val = 1;
var json = {
    val: 10,
    dbl: function () {this.val *= 2;}
}
json.dbl();
var dbl = json.dbl;
dbl();
json.dbl.call(window);
alert(window.val + json.val);

△ 第五题

//=>【第五题解析】window.val = 1;
var json = {
    val: 10,
    dbl: function () {this.val *= 2;}
}
json.dbl(); // this:json ; json.val = 10*2=20
var dbl = json.dbl;
dbl(); // this:window ; window.val = 1*2 =2


/*
    json.dbl.call(window)
    
    1、把 json.dbl 中的 this 指向 window
    2、调用 json.dbl

    window.val = 2*2 =4
*/
json.dbl.call(window);
alert(window.val + json.val); // 4+20【弹出 "24"】

△ 第五题解析

第六题

(function () {
    var val = 1;
    var json = {
        val: 10,
        dbl: function () {val *= 2;}
    };
    json.dbl();
    alert(json.val + val);
})();

△ 第六题

//=>【第六题解析】(function () {
    // 自执行函数:this:window
    var val = 1;
    var json = {
        val: 10,
        dbl: function () {// [[scope]]:EC(AN)
            // val 是 EC(AN)中的 1
            // val = 1*2 =2
            val *= 2;
        }
    };
    json.dbl(); // this:json
    alert(json.val + val); // 10+2【弹出 "12"】})();

△ 第六题解析

<img src=”../FD.07.JS. 已发 /2020-11-6. 面试题 4.THIS.assets/dh.2.png” alt=”dh.2″ style=”zoom:50%;” />

△ 图_对话框 2

5 种判断函数调用中的 THIS

一般函数,函数名后面是否有点

=> 有点xxx.fn(),点后面是谁,this 就是谁

=> 没有点fn(),非严格模式下是 window,严格模式下是 undefined

构造函数new 函数名()

③ 事件绑定

④ 箭头函数外面没有 this

⑤ 基于 call/apply/bind,扭转调用函数中的 this

函数外部的 arguments 是一个类数组对象,实参汇合。比方:{0:1, 1:2, length:2} 在函数调用时,与形参变量有对应关系

对象拜访属性

let obj = {
    name:'晚霞的光影笔记',
    id:'zhaoxiajingjing',
    say:function(){console.log(this.name);
    }
};
obj.name;
obj['name'];

obj.say(); 
obj['say']();

∴ 对象属性拜访有两种:

① 点表示法

② 括号表示法

所以,要留神括号表示法时候的 this 值,跟点表示法是一样的

好啦~ 咱们看看那道题为啥是 20 3 吧~

十五、面试题 | 请实现 sum(1)(2,3)(4,5)的函数 | 柯理化函数利用

sum(1,2,3,4)(5); //=> 15
sum(1)(2,3)(4,5); //=> 15
sum(1)(2)(3)(4,5); //=> 15
sum(1)(2)(3)(4)(5); //=> 15

△ 实现 sum 函数:① 调用次数不固定,② 传参个数不固定 ③ 最初输入后果

(1)第一种计划
sum(1,2,3,4)(5);
sum(1)(2,3)(4,5);
sum(1)(2)(3)(4,5);
sum(1)(2)(3)(4)(5);

△ 实现 sum 函数:① 调用次数不固定,② 传参个数不固定 ③ 最初输入后果

① 函数能够始终被调用
function sum(...params){const proxy = (...args) => {return proxy;};
    
    return proxy;
}
sum();
sum()()();

△ 为了让 sum()()()能够始终调用上来

② 收集参数
function fn(){}
console.log(fn + 1); //=> "function fn(){}1"

△ 函数名做运算,会调用 toString 办法

fn+1 加号做运算:fn先转成数字,会顺次调用 fn 的 Symobl.toPrimitive/valueOf/toString 这三个属性,加号遇到字符串就变成字符串拼接了

基于函数进行运算时或者输入时,个别都会调用到函数的 toString 属性

function fn(){}
fn.toString = function (){console.log('hello');
};
console.log(fn);

△ 在输入 fn 时,会调用到 fn.toString 属性。API:https://developer.mozilla.org…

function sum(...params){const proxy = (...args)=>{
        // 把每一次传递的信息都保存起来
        params = params.concat(args);
        return proxy;
    };
    proxy.toString = ()=>{
        // 须要计算的值
        return params.reduce((result, item) => result + item);
    };
    return proxy
}
sum(1,2,3,4)(5);
sum(1)(2,3)(4,5);
sum(1)(2)(3)(4,5);
sum(1)(2)(3)(4)(5);

△ 能够始终计算上来

(2)第二种计划
function currying(){let params = [];
    let sum = (...args) =>{params = params.concat(args);
        return sum;
    }
    sum.toString = () => {return params.reduce((result, item)=>result + item);
    };
    return sum;
}
let sum = currying();
sum(1,2,3,4)(5);
// 留神:每次调用前都要从新调用 currying
sum = curring();
sum(1)(2,3)(4,5);

△ 闭包会保留下来上次的值,每次调用前都要清空

请问:为什么每次都须要从新调用 curring 这个办法?

let sum = currying();
sum(1,2,3,4)(5);
sum(1)(2,3)(4,5);

△ 后果是:30

let sum = currying() 执行时,全新的公有执行上下文 EC(curring),造成一个不被销毁的闭包。

=> 公有变量 params 是对象数据类型,会被保留下来

=> 公有变量 let sum = 函数[[scope]]:EC(curring),函数数据类型

=> return 函数[[scope]]:EC(curring),赋值给 sum

sum(1,2,3,4)(5) 执行时,会造成一个全新的公有执行上下文 EC(s5)

=> 作用域链[[scope-chain]]:<EC(s5), EC(currying)>

=> 用到变量 params,不是本人的,向下级作用域查找是 EC(currying)外面的

=> params 的值:[1,2,3,4,5]

=> …………

紧接着执行 sum(1)(2,3)(4,5) 时,会造成一个全新的公有执行上下文 EC(s45)

=> 作用域链[[scope-chain]]:<EC(s45), EC(currying)>

=> 用到变量 params,不是本人的,向下级作用域查找是 EC(currying)外面的

=> params 的值:[1,2,3,4,5,1,2,3,4,5]

=> …………

十六、面试题 | $ 应用权限抵触了,如何解决?| 跟着 jQuery 大佬学编程思维

<img src=”../FD.07.JS. 已发 /2020-11-8.jQuery 应用权限抵触,如何解决?.assets/0.1.png” alt=”0.1″ style=”zoom:25%;” />

0 / jQuery 源码局部解析

$npm init -y

$npm install jquery

node_modules\jquery\dist\jquery.js 查看 jQuery 的源码

这是我摘取后,改了一下:

var A = typeof window !== "undefined" ? window : this;
var B = function (window, noGlobal){};

(function (global, factorys){
    "use strict";
    if(typeof module === 'object' && typeof module.exports === 'object'){
        // 以后运行 JS 的环境是反对 CommonJS 模块标准的
        // nodejs/webpack 反对 CommmonJS 模块标准
        // 浏览器不反对 CommonJS 模块标准的
    } else {
        // 浏览器或者 webview 环境
        factory(global);//=> B(window)
    }
})(A, B);

△ jquery 源码剖析

L1

利用 JS 的暂时性死区:基于 typeof 检测一个未被申明的变量,后果是undefined

如果是在浏览器或者 webview 环境下运行 JS,则 A =>window

在 nodejs 下运行 JS,则 A =>global 或者以后模块

在浏览器环境下是把 B 函数执行factory(global)

var B = function (window, noGlobal){
    // 浏览器环境下
    // window => window
    // noGlobal => undefined
    "use strict";
    var jQuery = function (selector, context){};
    // ...CODE
    // 在里面用到这个 jQuery 办法
    if(typeof noGlobal === 'undefined'){
        // 把公有的办法裸露到全局对象上
        window.jQuery = window.$ = jQuery;
    }
};

△ 把 jQuery 裸露在全局上:window.jQuery = window.$ = jQuery

$() 就是 jQuery() 就是让闭包中的jQuery 办法执行

依葫芦画瓢,在咱们本人封装组件时:

1、利用 <u> 闭包的爱护作用 </u>,把它们都包起来,这样外面写的变量都是公有,<u> 避免全局变量净化 </u>

2、裸露的 API:反对浏览器 和 CommonJS标准

(function (){function ModulePlugin(){}
    
    // 避免抵触
    var _M = window.M;
    
    if(typeof window !== 'undefined'){window.MP = window.ModulePlugin = ModulePlugin;}
    if(typeof module === 'object' && typeof module.exports === 'object'){// COMMONJS 标准}
})();

△ 本人写组件时:① 闭包 ② 反对浏览器和 CommonJS 标准

1 / 开释 $ 和 jQuery 的应用权限

<script src="../node_modules/jquery/dist/jquery.js"></script>
<script>
    (function (){window.$$ = jQuery.noConflict(); //=> 只开释 $ 的应用权限
        window.$jq = jQuery.noConflict(true); //=> ① 开释 $ 的应用权限 ② 开释 jQuery 的应用权限
    })();
</script>

△ jQuery.noConflict 的作用

conflict 英 [ˈkɒnflɪkt , kənˈflɪkt] 美 [ˈkɑːnflɪkt , kənˈflɪkt]

n. 抵触; 争执; 争执;(军事)抵触; 战斗; 冲突; 矛盾; 不统一
v. (两种思维、信奉、说法等) 抵触,冲突

jQuery.noConflict 的作用:

① 开释 $ 的应用权限

② 开释 jQuery 的应用权限

var jQuery = function (selector, context) {return new jQuery.fn.init(selector, context);
};

//...CDOE

var _jQuery = window.jQuery,
    _$ = window.$;

jQuery.noConflict = function (deep) {if (window.$ === jQuery) {window.$ = _$;}

    if (deep && window.jQuery === jQuery) {window.jQuery = _jQuery;}

    return jQuery;
};

// 在浏览器中 noGlobal => undefined
if (typeof noGlobal === 'undefined') {window.jQuery = window.$ = jQuery;}

△ 从 jQuery.js 中粘贴进去的源码,jQuery.noConflict 办法开释 $ 的应用权限

代码自上而下执行时:

var _jQuery = window.jQuery; var _$ = window.$;

此时 window.jQuery 没有这个属性,则:_jQuery = undefined

此时 window.$ 没有这个属性,则:_$= undefined

jQuery.noConflict = function ....

在 jQuery 对象上定义了一个办法 noConflict

window.jQuery = window.$ = jQuery;

在 web 浏览器上 noGlobal 是 undefined,能进入判断体

此时:window.jQuery 和 window.$ 都赋值为 jQuery 了

那么,能够间接调用 $ 和 jQuery 了 作用域链查找机制

window.$$ = jQuery.noConflict();

调用 jQuery.noConflict 办法,把返回值赋值给 window.$$

=> 形参赋值 deep = undefined

=> 代码执行:

​ => window.$ === jQuery –> true 进入判断体

​ => window.$ = _$; 其中 \_$ 不是本人公有的变量,查找到:_$=undefined,即:window.$=undefined 使用权被开释了

​ => deep && window.jQuery === jQuery 其中 deep=undefined,不能进入判断体

​ => return jQuery; 即:window.$$ = jQuery

window.$jq = jQuery.noConflict(true);

调用 jQuery.noConflict 办法,把返回值赋值给 window.$jq

=> 形参赋值 deep = true

=> 代码执行:

​ => window.$ === jQuery –> true 进入判断体

​ => window.$ = _$; 其中 \_$ 不是本人公有的变量,查找到:_$=undefined,即:window.$=undefined 使用权被开释了

​ => deep && window.jQuery === jQuery 其中 deep=true,window.jQuery === jQuery 也是 true,进入判断体

​ => window.jQuery = _jQuery; 其中_jQuery 不是本人公有的变量,查找到:\_jQuery = undefined,即:window.jQuery = undefined 使用权被开释了

​ => return jQuery; 即:window.$jq = jQuery

jQuery.noConflict 应用场景

① 同时引入 zepto 和 jQuery

② 引入不同版本的 jQuery

③ 咱本人写了个库,占用了 jQuery 这个属性名

2 / 以同时引入 Zepto 和 jQuery 为例

<script src="./zepto.min.js"></script> <!-- window.$ = Zepto -->
<script src="./jquery.min.js"></script><!-- window.$ = jQuery -->
<script>
    (function (){window.$$ = jQuery.noConflict(); //=> 只开释 $ 的应用权限
    })();
</script>

△ 我的项目同时引入 zepto 和 jquery

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

△ 从 zepto.js 中粘贴进去的源码

var jQuery = function(selector, context) {return new jQuery.fn.init( selector, context);
};
//...CDOE
var _jQuery = window.jQuery,
     _$ = window.$;

jQuery.noConflict = function(deep) {if ( window.$ === jQuery) {window.$ = _$;}

    if (deep && window.jQuery === jQuery) {window.jQuery = _jQuery;}

    return jQuery;
};

// 在浏览器中 noGlobal => undefined
if(typeof noGlobal === 'undefined'){window.jQuery = window.$ = jQuery;}

△ 从 jQuery.js 中粘贴进去的源码,jQuery.noConflict 办法开释 $ 的应用权限

0、window.$ = zepto 在 jQuery 引入之前,曾经把 zepto 赋值给 $ 了

1、在导入 JQ 的时候,把现有全局的 $ 是谁记录下来

var _$ = window.$; 即: _$= Zepto

var _jQuery = window.jQuery 这个时候_jQuery 的值是 undefined

导入 jQuery 完结当前,window.jQuery = window.$ = jQuery 了

2、如果发现 $ 使用权和别的类库抵触了,则转让使用权

window.$jq = jQuery.noConflict() 在调用此办法之前:_$=Zepto; _jQuery = undefined; window.jQuery = window.$ = jQuery

window.$ === jQuery => true,进入判断体

window.$ = _$; 即:window.$ = Zepto;</b> 把 $ 的应用权限让进来了

deep && window.jQuery === jQuery 形参 deep 没有接管到实参值,即:deep 为 undefined,不进入判断体

return jQuery;,即:`window.$$= jQuery` 之后应用 $$ 即示意 jQuery 了

3 / 咱本人写的模块

(function (){var utils = {};
    
    var _utils = window.utils,
        _temp = window._;
    
    utils.noConflict = function (deep){if(window._ === utils){window._ = _temp;}
        if(deep && window.utils === utils){window.utils = _utils;}
        return utils;
    };
    
    if(typeof module === 'object' && typeof module.exports === 'object'){// ... CommonJS 标准下的操作} else if(typeof window !== 'undefined'){window._ = window.utils = utils;}
    
})();

△ ① 辨别 web 浏览器还是 CommonJS 标准的 node 环境 ② 在 web 浏览器中转让_和 utils 的应用权限

十七、JS 第 1 块知识点,汇总

十八、下一个知识点:面向对象

正文完
 0