【下】8大知识点+16篇总结(含31道面试题解)+9张思维导图 | 梳理JavaScript堆栈内存和闭包作用域

169 阅读21分钟

十一、 面试题 | 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);

△ 第一题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

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

△ 第二题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第三题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图1.3_第三题

第四题

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

△ 第四题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第五题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图1.5_第五题

第六题

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

△ 第六题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第一问

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

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

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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 不允许重复声明,不能和形参变量名一致

此时,除了默认形成的“函数私有上下文”,还会多创建一个“块级私有上下文”【把函数体到括号的都包起来了】

在这个“块级私有上下文”中:

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

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

③ 会把在函数私有上下文中,把传递的形参赋值中,映射给块级上下文相同名字的变量

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第三问

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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');

△ 第二题 公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第四题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图_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);

△ 第一题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第二题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第三题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图3.3_第三题

第四题

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

△ 第四题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第五题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第六题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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);

△ 第七题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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、……

【第四个维度】:自己的思想和理解【一句话概括】

闭包的理解

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

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

这是形成闭包的一种情况。

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

闭包的优点

闭包会形成一个不被释放的私有执行上下文,在此上下文中的变量和值得以 保存下来,并且会 保护 此上下文中的变量不会被外界的内容干扰到。

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 在严格模式下会报错

在函数内部直接修改它的值也是无效的

除非函数内部重新声明这个变量,就可以修改了: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】

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

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

第一题

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

△ 第一题

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

△ 图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"】
})();

△ 第六题解析

dh.2

△ 图_对话框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 吧

公众号:朝霞的光影笔记 ID:zhaoxiajingjing

十五、 面试题 | 请实现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: developer.mozilla.org/en-US/docs/…

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 大佬学编程思想

0.1

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、利用闭包的保护作用,把它们都包起来,这样里面写的变量都是私有,防止全局变量污染

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.=Zepto; _jQuery = undefined; window.jQuery = window. = jQuery

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

window.$ = _$; 即:window.=Zepto;</b> = 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块知识点,汇总

17篇文章

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

正在总结中 ​ ​ ​ ​ 我在这里等你来~