一、 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,你不是一个人。不是一个人,那是什么都有可能了
① NaN
和 NaN
自身不相等,和其余值也不相等
② 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); //=> falseconsole.log(NaN === NaN); //=> falseconsole.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 [prmtv] 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); //=> 把值转换为数字:10console.log(++n); //=> 把值转换为数字,在前置自增:11let obj = {};console.log(10 + obj); //=> '10[object Object]'console.log(10 + new Number(10)); //=> 20console.log(10 + {id:'zhaoxiajingjing'}); //=> '10[object Object]'
△ + 是一枚斜杠青年
那么,请问:i=i+1
i+=1;
++i/i++
这三个一样吗?
其中:i=i+1
和i+=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的原始值 10String(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
parseFloat
比parseInt
多辨认一个小数点
(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)
△ 其余数据类型转换为数字类型
L1:parseInt("")
没有找到有效数字字符=> NaN
L2:Number("")
=> 0
L3:isNaN("")
=> isNaN 办法调用的是 Number 转换数据类型 => false
L4:parseInt(null)
=> parseInt("null") => NaN
L5:Number(null)
=>0
L6:isNaN(null)
=> false
L7:parseInt("12px")
=> 12
L8:Number("12px")
=> NaN
L9:isNaN("12px")
=> true
L10:parseFloat("1.6px")+parseInt("1.2px")+typeof parseInt(null)
=> typeof parseInt(null) => typeof NaN => "number"
=> 1.6 + 1 + "number"
=> "2.6number"
加号左右两边呈现字符串,此时加号变为字符串拼接(有特殊性),如果呈现对象也会变成字符串拼接,本来应该是把对象转为数字,然而对象要先转换为字符串,则遇到加号字符串就变成字符串拼接了
L11:isNaN(Number(!!Number(parseInt("0.8"))))
=> parseInt("0.8") => 0
=> !!Number(0) => false
=> Number(false) => 0
=> isNaN(0) => false
L12:typeof !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); //=> undefinedvar 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 defineda=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 definedlet 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
的是申明+定义
let
和 var
的区别:
(1)区别1:var
存在变量晋升,而let
不存在
console.log(n); //=> undefinedconsole.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=12console.log(n, window.n); //=> 12 12window.n = 11;console.log(n); //=> 11let 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 definedlet 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); //=> 12console.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+20console.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+20console.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()); // windowconsole.log(fun.call(2) === 2, fun.call(2)); // Number(2)console.log(fun.apply(null) === null, fun.apply(null)); // windowconsole.log(fun.call(undefined) === undefined, fun.call(undefined)); // windowconsole.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); //=> undefinedif (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);//=>undefinedif (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 = NaNif(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()); //=> 输入 undefinedvar 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=20var 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); //=> 15sum(1)(2,3)(4,5); //=> 15sum(1)(2)(3)(4,5); //=> 15sum(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);// 留神:每次调用前都要从新调用curryingsum = 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 英 [knflkt , knflkt] 美 [knflkt , knflkt]
n. 抵触;争执;争执;(军事)抵触;战斗;冲突;矛盾;不统一
v. (两种思维、信奉、说法等)抵触,冲突
jQuery.noConflict的作用:
① 开释$的应用权限
② 开释jQuery的应用权限
var jQuery = function (selector, context) { return new jQuery.fn.init(selector, context);};//...CDOEvar _jQuery = window.jQuery, _$ = window.$;jQuery.noConflict = function (deep) { if (window.$ === jQuery) { window.$ = _$; } if (deep && window.jQuery === jQuery) { window.jQuery = _jQuery; } return jQuery;};// 在浏览器中 noGlobal => undefinedif (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 = Zeptowindow.$ === undefined && (window.$ = Zepto)
△ 从zepto.js中粘贴进去的源码
var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context );};//...CDOEvar _jQuery = window.jQuery, _$ = window.$;jQuery.noConflict = function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery;};// 在浏览器中 noGlobal => undefinedif(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的应用权限