14 关于数据类型转换的作业题
15 关于THIS基础情况的联系题
16 arguments及形参的映射机制
17 闭包作用域的几道练习题
18 关于闭包“套娃”的面试题

一、变量提升的作业

1

  1. console.log(a, b, c);
  2. var a = 12,
  3. b = 13,
  4. c = 14;
  5. function fn(a) {
  6. console.log(a, b, c);
  7. a = 100;
  8. c = 200;
  9. console.log(a, b, c);
  10. }
  11. b = fn(10);
  12. console.log(a, b, c);

答案:

  • undefined undefined undefined
  • 10 13 14
  • 100 13 200
  • 12 undefined 200

解析:

  1. /*
  2. * EC(G)
  3. * VO(G)
  4. * a = 12
  5. * b = 13 / undefined
  6. * c = 14 / 200
  7. * fn = 0x000000 [[scope]]:EC(G) 形参(a)
  8. * 变量提升: var a; var b; var c; function fn(){...};
  9. */
  10. console.log(a, b, c); // undefined * 3
  11. var a = 12,
  12. b = 13,
  13. c = 14;
  14. function fn(a) {
  15. /*
  16. * EC(FN)
  17. * AO(FN)
  18. * a = 10 / 100
  19. * 作用域链:<EC(FN),EC(G)>
  20. * 形参赋值:a=10
  21. * 变量提升:--
  22. */
  23. console.log(a, b, c); // 10 13 14
  24. a = 100;
  25. c = 200;
  26. console.log(a, b, c); // 100 13 200
  27. }
  28. b = fn(10); //函数执行没有返回结果(RETURN)结果是undefined
  29. console.log(a, b, c); // 12 undefined 200

问题:在变量提升阶段需要考虑var和function谁先提升么?
不需要,自上而下。

2

  1. var i = 0;
  2. function A() {
  3. var i = 10;
  4. function x() {
  5. console.log(i);
  6. }
  7. return x;
  8. }
  9. var y = A();
  10. y();
  11. function B() {
  12. var i = 20;
  13. y();
  14. }
  15. B();

答案:

  • 10
  • 10

解析:函数执行,它的上级上下文是谁和函数在哪执行没关系,上级上下文是它的[[scope]],所以在哪定义的函数,它的上级上下文就是谁。
1.png

3

  1. var a=1;
  2. var obj ={
  3. name:"tom"
  4. }
  5. function fn(){
  6. var a2 = a;
  7. obj2 = obj;
  8. a2 =a;
  9. obj2.name ="jack";
  10. }
  11. fn();
  12. console.log(a);
  13. console.log(obj);

答案:

  • 1
  • {name: “jack”}

image.png
解析:

  1. /*
  2. * EC(G)
  3. * a = 1
  4. * obj = 0x000000
  5. * fn = 0x000001 [[scope]]:EC(G)
  6. */
  7. var a = 1;
  8. var obj = {
  9. name: "tom" // "jack"
  10. };
  11. function fn() {
  12. /*
  13. * EC(FN)
  14. a2 = 全局a的值1
  15. * 作用域链:<EC(FN),EC(G)>
  16. */
  17. var a2 = a;
  18. obj2 = obj; //即不是私有的,也不是全局的,则此处相当于 window.obj2=0x000000(全局的obj)
  19. a2 = a;
  20. obj2.name = "jack"; //把window.obj2中的name修改(全局的obj也是这个堆)
  21. }
  22. fn();
  23. console.log(a); //=>1
  24. console.log(obj); //=>{name:'jack'}

4

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

答案:

  • ƒ a(){}

解析:

  1. /*
  2. * EC(G)
  3. * a = 1
  4. * fn = 0x000000 [[scope]]:EC(G)
  5. */
  6. var a = 1;
  7. function fn(a) {
  8. /*
  9. * EC(FN)
  10. * a = 1
  11. * = 0x000001 [[scope]]:EC(FN)
  12. * = 2
  13. *
  14. * 作用域链:<EC(FN),EC(G)>
  15. * 形参赋值:a=1
  16. * 变量提升:
  17. * var a; 已经有a了,不需要重新声明
  18. * function a(){}; function a是声明加定义:不需要重新声明,但是需要重新赋值
  19. */
  20. console.log(a); //=>函数
  21. var a = 2; // a=2还没处理过呢,在把私有变量a的值赋值为2
  22. function a() {} //它不处理了,变量提升阶段都处理完了
  23. console.log(a); //=>2
  24. }
  25. fn(a); //fn(1)
  26. console.log(a); // => 1 全局的a还是1

5

  1. console.log(a);
  2. var a=12;
  3. function fn(){
  4. console.log(a);
  5. var a=13;
  6. }
  7. fn();
  8. console.log(a);

答案:

  • undefined
  • undefined
  • 12

解析:

  1. /*
  2. * EC(G)
  3. * a = 12
  4. * fn = 0x000000 [[scope]]:EC(G)
  5. */
  6. console.log(a); // =>undefined
  7. var a=12;
  8. function fn(){
  9. /*
  10. * EC(FN)
  11. * a = 13
  12. *
  13. * 作用域链:<EC(FN),EC(G)>
  14. * 形参赋值:--
  15. * 变量提升:var a;
  16. */
  17. console.log(a); //=>undefined
  18. var a=13;
  19. }
  20. fn();
  21. console.log(a); //=>12
  1. console.log(a);
  2. var a=12;
  3. function fn(){
  4. console.log(a);
  5. a=13;
  6. }
  7. fn();
  8. console.log(a);

答案:

  • undefined
  • 12
  • 13

    1. /*
    2. * EC(G)
    3. * a
    4. * fn = 0x000000 [[scope]]:EC(G)
    5. *
    6. * 变量提升:var a; function fn(){...};
    7. */
    8. console.log(a); //=>undefined
    9. var a = 12; //全局的a=12
    10. function fn() {
    11. /*
    12. * EC(FN)
    13. *
    14. * 作用域链:<EC(FN),EC(G)>
    15. * 形参赋值:--
    16. * 变量提升:--
    17. */
    18. console.log(a); //=>12
    19. a = 13; //把全局的a=13
    20. }
    21. fn();
    22. console.log(a); //=>13
    1. console.log(a);
    2. a=12;
    3. function fn(){
    4. console.log(a);
    5. a=13;
    6. }
    7. fn();
    8. console.log(a);

    答案:

  • a is not defined

image.png
解析:

  1. /*
  2. * EC(G)
  3. * fn = 0x000000 [[scope]]:EC(G)
  4. *
  5. * 变量提升:function fn(){...};
  6. */
  7. console.log(a); //首先看是否为全局变量,不是,则再看是否为window的一个属性,如果还不是,报错:Uncaught ReferenceError: a is not defined (这行代码一但报错,下面代码都不处理了)
  8. a=12;
  9. function fn(){
  10. console.log(a);
  11. a=13;
  12. }
  13. fn();
  14. console.log(a);

6

  1. var foo='hello';
  2. (function(foo){
  3. console.log(foo);
  4. var foo=foo||'world';
  5. console.log(foo);
  6. })(foo);
  7. console.log(foo);

答案:

  • hello
  • hello
  • hello

匿名函数不进行变量提升。

  1. /*
  2. * EC(G)
  3. * foo
  4. *
  5. * 变量提升:var foo;
  6. */
  7. var foo = 'hello'; //全局的foo='hello'
  8. (function (foo) {
  9. /*
  10. * EC(ANY)
  11. * foo = 'hello'
  12. *
  13. * 作用域链:<EC(ANY),EC(G)>
  14. * 形参赋值:foo='hello'
  15. * 变量提升:var foo;
  16. */
  17. console.log(foo); //=>'hello'
  18. // A||B:A的值是真,返回A的值,否则返回B的值
  19. // A&&B:A的值是真,返回B的值,否则返回A的值
  20. // 同时出现,&&优先级高于||
  21. var foo = foo || 'world'; //foo='hello'是真值,直接赋值,不会走到'world'
  22. console.log(foo); //=>'hello'
  23. })(foo); //自执行函数执行,传递实参'hello'
  24. console.log(foo); //=>'hello'

7

新版浏览器中: function xxx(){} 如果没有出现在 {} 中,则变量提升阶段是“声明+定义”;如果出现在 {} 中,除函数/对象的大括号外,则”只声明”。
老版本浏览器:function xxx(){} 无论是否出现在 {} 中,都是“声明+定义(赋值)”。

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

答案: ƒ foo() {}
解析:function foo(){} 这段代码在全局上下文中EC(G)里面声明过一次,在私有块级上下文中声明加定义过一次,俩个地方都声明过,就会在最后一次声明的地方,把之前对于foo的操作都“映射”给全局一份。

  1. /*
  2. * EC(G)
  3. * foo
  4. *
  5. * 变量提升:function foo;
  6. */
  7. // debugger;
  8. {
  9. /*
  10. * EC(BLOCK)
  11. * foo = 0x000000
  12. * = 0x000001
  13. *
  14. * 变量提升:
  15. * function foo(n){}
  16. * function foo(m) {}
  17. */
  18. function foo(n) {} //把之前对foo的操作“映射”给全局 全局foo=0x000001
  19. foo = 1;
  20. function foo(m) {} //一样要把之前对foo的操作“映射”给全局一份 全局foo=1
  21. console.log(foo); //=>1
  22. }
  23. console.log(foo); //=>1
  1. {
  2. function foo() {}
  3. foo = 1;
  4. function foo() {}
  5. }
  6. console.log(foo);

答案: 1
解析:

  1. /*
  2. * EC(G)
  3. * foo
  4. *
  5. * 变量提升:function foo;
  6. */
  7. // debugger;
  8. {
  9. /*
  10. * EC(BLOCK)
  11. * foo = 0x000000
  12. * = 0x000001
  13. *
  14. * 变量提升:
  15. * function foo(n){}
  16. * function foo(m) {}
  17. */
  18. function foo(n) {} //把之前对foo的操作“映射”给全局 全局foo=0x000001
  19. foo = 1;
  20. function foo(m) {} //一样要把之前对foo的操作“映射”给全局一份 全局foo=1
  21. console.log(foo); //=>1
  22. }
  23. console.log(foo); //=>1
  1. {
  2. function foo() {}
  3. foo = 1;
  4. function foo() {}
  5. foo = 2;
  6. }
  7. console.log(foo);

答案:1
解析:

  1. /*
  2. * EC(G)
  3. * foo
  4. *
  5. * 变量提升: function foo(n) function foo(m)
  6. */
  7. {
  8. /*
  9. * EC(BLOCK)
  10. * foo = 0x000000
  11. * = 0x000001
  12. * 变量提升:
  13. * function foo(n){}
  14. * function foo(m){}
  15. */
  16. function foo(n) {} //映射给全局 全局foo=0x000001
  17. foo = 1;
  18. function foo(m) {} //映射给全局 全局foo=1
  19. foo = 2; //私有的处理,和全局没关系了
  20. console.log(foo); //=>2
  21. }
  22. console.log(foo); //=>1

2.png8

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

答案:

  • 2
  • 1

解析:
3.png

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

答案:

  • 3
  • 1

4.png
函数执行的时候:

  • 条件1:有形参赋值默认值(不论是否传递参数,也不论默认值的类型)
  • 条件2:函数体中有变量声明(必须是基于let/const/var, 注意let/const不允许重复声明,不能和形参变量名一致)

满足上面2个条件,除了默认形成的”函数私有上下文“,还会多创建一个”块级私有上下文“(函数体大括号包起来的)

作用域和上下文什么区别?
作用域是创建时候的环境
上下文是执行时候的环境

  1. debugger
  2. function fn(x, y) {
  3. var x=12;
  4. }
  5. fn() // 形参没有赋值默认值,只有一个私有上下文Local

image.png

  1. debugger
  2. function fn(x, y=12) {
  3. x=12;
  4. }
  5. fn() // y有默认值,但函数内x没有var,也只有一个私有上下文Local

image.png

  1. debugger
  2. function fn(x, y=12) {
  3. var x=12;
  4. }
  5. fn() // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文,一个块级上下文

image.png

  1. debugger
  2. function fn(x, y=12) {
  3. var x=12; // 只有在大括号里面申明过得才能形成块级上下文
  4. y=13; // 不属于块级上下文,属于私有上下文
  5. }
  6. fn() // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文Local,一个块级上下文Block

image.png

  1. debugger
  2. function fn(x, y=12) {
  3. var x=12;
  4. y=13; // 不属于块级上下文,属于私有上下文
  5. }
  6. fn(10, 20) // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文,一个块级上下文

image.png

  1. debugger
  2. function fn(x, y=12) {
  3. let x=12;
  4. y=13; // 不属于块级上下文,属于私有上下文
  5. }
  6. // x重复声明了,报错。
  7. fn(10, 20) // Uncaught SyntaxError: Identifier 'x' has already been declared
  1. debugger
  2. function fn(x, y=12) {
  3. function fn2(){};
  4. x=12;
  5. y=13; // 不属于块级上下文,属于私有上下文
  6. }
  7. // x重复声明了,报错。
  8. fn(10, 20)

image.png

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

答案:

  • 4
  • 1

解析:

  1. var x=1;
  2. function func(x,y=function anonymous1(){x=2}){
  3. /*
  4. * EC(FUNC) 私有函数上下文
  5. * x = 5
  6. * y = 0x000001 [[scope]]:EC(FUNC) -> anonymous1
  7. *
  8. * 作用域链:<EC(FUNC),EC(G)>
  9. * 形参赋值:x=5 y=anonymous1...
  10. */
  11. /*
  12. * EC(BLOCK) 私有块级上下文
  13. * x = 5 / 3 / 4
  14. * y = 0x000001 / 0x000002 [[scope]]:EC(BLOCK) -> anonymous2
  15. *
  16. * 作用域链:<EC(BLOCK),EC(FUNC)>
  17. * 变量提升:var x; var y;
  18. * 代码执行:
  19. */
  20. var x=3;
  21. var y=function anonymous2(){
  22. /*
  23. * EC(Y)
  24. * 作用域链:<EC(Y),EC(BLOCK)>
  25. * 形参赋值:--
  26. * 变量提升:--
  27. */
  28. x=4; // x是上级上下文EC(BLOCK)
  29. };
  30. y(); //私有块级中的y,也就是anonymous2
  31. console.log(x); //=>4
  32. }
  33. func(5);
  34. console.log(x);

image.png

二、数据类型和基础知识作业

1

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

答案:”NaNTencentnull9false”
解析:
100+true => 100+1 =>101
101+21.2 = > 122.2
122.2+ null => 122.1+0=>122.2
122.2+undefined=>122.1+NaN =>NaN
NaN+”Tencent” => “NaNTencent”
“NaNTencent”+[] => “NaNTencent”+”” => “NaNTencent”
“NaNTencent”+null+9+false =>”NaNTencentnull9false”

  • A+B

两边中的任何一边遇到字符串或者对象(对象转换为数字要先转换为字符串[排除有原始值的]),都是按照字符串拼接处理的
{}+10 => 10 左边的大括号当作一个代码块,不参与运算, 这里{}当做代码块来处理的
({}+10) => "[object Object]10" 这里外面包了一个(),执行块,里面{}就会被解析为对象了。
10+{} => "10[object Object]" 参与运算
+A/++A/A++ 都是转换为数字

  1. let n = "10";
  2. console.log(n+20) // 1020;
  3. console.log(parseInt(n)+20)// 30;
  4. console.log(+n+20) // 30;

2

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

答案: 先弹出no再弹出ok
解析: {}+0 =>0 {}不参与运算,0、NaN、’’、null、undefined都是false
10+{} => "10[object Object]" 参与运算

3

  1. let res = Number('12px');
  2. if(res===12){
  3. alert(200);
  4. }else if(res===NaN){
  5. alert(NaN);
  6. }else if(typeof res==='number'){
  7. alert('number');
  8. }else{
  9. alert('Invalid Number');
  10. }

答案:弹出number
解析:Number(‘12px’) => NaN
NaN属于Number类型

  1. parseInt('12px') => 12
  2. parseInt('12px13') => 12
  3. parseInt('px12') => NaN
  4. parseInt('12.5px') => 12
  5. parseFloat('12.5px') => 12.5
  • Number中只要出现任何一个非有效数字,结果都是NaN
  • parseInt([value])

把一个字符串转换为数字([value]不是字符串,也要先转换为字符串),处理机制:从字符串左侧第一个字符开始查找,把找到有效数字字符转换为数字,直到遇到一个非有效数字字符,则停止查找(不论后面是否还有数字字符,都不再查找了)

  • parseInt([value],[radix])

    [radix]不写(或者写0),默认是10进制;特殊情况,如果[value]是以0x开头的字符串,则[radix]不写默认是16进制;
    [radix]有自己的取值范围 2~36 之间,不在这个之间的,返回结果都是NaN
    首先会在[value]字符串中,从左到右找到所有符合[radix]进制的值(遇到一个非[radix]进制的值则停止查找),把找到的结果最后转换为10进制的值

  • 把一个N进制的值转换为10进制

例如:把一个8进制的值转换为10进制
let n = "1042"; //8进制(0~7)
parseInt(n, 8) = 546;
28^0 + 48^1 + 08^2 + 18^3 = 2 + 32 + 0 + 512 = 546

4

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

答案:[27, NaN, 1, 1, 27 ]
解析:

  1. arr=arr.map(function(item,index){
  2. //每迭代数组中的一项,都会触发回调函数执行,并且把当前迭代这一项和这一项的索引传递给这个函数
  3. //回调函数的返回值会把数组这一项替换调,原始数组不变,返回一个新数组
  4. });
  5. let arr = [27.2, 0, '0013', '14px', 123];
  6. arr = arr.map(parseInt);
  7. // parseInt('27.2',0) =>27 27.2先变成字符串, 0相当于10 进制
  8. // parseInt('0',1) =>NaN 0先变成字符串
  9. // parseInt('0013',2) =>从左到右找到符合进制的'001'看作2进制最后转换为10进制 0*2^2+0*2^1+1*2^0 => 1
  10. // parseInt('14px',3) =>'1'看作3进制转换为10进制 1*3^0 => 1
  11. // parseInt('123',4) =>123先转成字符串 '123'看作4进制转换为10进制 1*4^2+2*4^1+3*4^0 = 16+8+3 => 27
  12. console.log(arr);
  1. let arr = [27.2, 0, '0013', '14px', 123];
  2. arr.map(parseInt);
  3. console.log(arr);// [27.2, 0, "0013", "14px", 123]

三、闭包作用域的作业

1

  1. var a = 10,
  2. b = 11,
  3. c = 12;
  4. function test(a) {
  5. a = 1;
  6. var b = 2;
  7. c = 3;
  8. }
  9. test(10);
  10. console.log(a, b, c);

答案: 10 11 3
解析:test(10) ,test形成私有上下文,形参a和声明过得变量b都是私有变量,c是全局变量。
**

2

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

答案:

  • 3
  • 10
  • undefined

解析:
arguments是个实参集合(类数组集合),arguments传多少个实参b(1, 2, 3)就有多少个实参,不管 function b(x, y, a){} 定义多少个形参。真实结构是 {0:1,1:2,2:3,length:3}
知识点:
非严格模式与严格模式的区别:

  • this指向window和undefined的区别
  • 实参和形参是否建立隐射机制

    1. /*
    2. * EC(G)
    3. * a
    4. * function b(x,ya){...} b = 0x000000
    5. */
    6. var a = 4; // 全局的a=4
    7. function b(x, y, a) {
    8. /*
    9. * EC(B)
    10. * x=1
    11. * y=2
    12. * a=3
    13. * 作用域链:<EC(B),EC(G)>
    14. * 初始THIS:window
    15. * 初始arguments:[1,2,3] => {0:1,1:2,2:3,length:3} 类数组集合(不伦是否设置形参,
    16. 只要传递实参,arguments就有值,不传递实参是一个空的类数组集合)
    17. * 形参赋值:x=1 y=2 a=3
    18. * 变量提升:--
    19. *
    20. * 在非严格模式下,形参赋值完成,会和ARGUMENTS中的每一项建立映射机制(一个改,另外一个也会跟着改);但是严格模式下("use strict")不存在映射机制;
    21. */
    22. console.log(a);// 3
    23. arguments[2] = 10; // a=10
    24. console.log(a);
    25. }
    26. a = b(1, 2, 3);
    27. console.log(a); // undefined 函数b没有返回值(主要看return)

    2.png

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

    答案: undefined

    1. var a = 4;
    2. function b(x, y, a) {
    3. /*
    4. * EC(B)
    5. * 作用域链:<EC(B),EC(G)>
    6. * 初始ARGUMENTS:[1,2]=> {0:1,1:2,length:2} 类数组
    7. * 形参赋值:x=1 y=2 a=undefined
    8. * 变量提升:--
    9. *
    10. * 映射机制是在函数代码执行之前完成的,那会建立了映射就有映射,如果此时没建立,映射机制后续也就不会再有了
    11. */
    12. a = 3;
    13. console.log(arguments[2]); //=>undefined
    14. }
    15. a = b(1, 2);

    知识点:剩余运算符
    ES6的箭头函数已经没有arguments了,利用剩余运算符获取传入的参数(数组形式)

    1. function b(...args) {
    2. console.log(arguments) // 现在很少这么用了
    3. console.log(args) // ES6都是用剩余运算符直接获取到参数数组
    4. }
    5. b(1,2,3,4)


    image.png
    问题1:**改变arguments会隐射到形参,那么改变私有变量是否会改变arguments呢?
    会改变arguments

    1. var a = 4;
    2. function b(x, y, a) {
    3. console.log(arguments, a)
    4. a=13
    5. console.log(arguments, a);
    6. }
    7. a = b(1, 2, 3);
    8. console.log(a);

    image.png
    问题2:如果函数形参取默认值呢?改变私有变量是否会改变arguments么?
    不会,因为实参只有1和2,arguments里面根本就不包含a,所以不会影响到arguments。

    1. var a = 4;
    2. function b(x, y, a=15) {
    3. console.log(arguments, a)
    4. a=13
    5. console.log(arguments, a);
    6. }
    7. a = b(1, 2);
    8. console.log(a);

    image.png

    3

    1. var a = 9;
    2. function fn() {
    3. a = 0;
    4. return function (b) {
    5. return b + a++;
    6. }
    7. }
    8. var f = fn();
    9. console.log(f(5));
    10. console.log(fn()(5));
    11. console.log(f(5));
    12. console.log(a);

    答案:

  • 5

  • 5
  • 6
  • 2

解析:
变量提升阶段,fn是声明加定义
var f = fn(); 是把fn()执行的结果返回给 f。
匿名函数不参与变量提升。
当前函数fn(AAAFFF000)执行形成的私有上下文,私有上下文中的BBBFFF000被f占用,导致不能出栈释放,因此fn(AAAFFF000)也不能释放,形成了闭包。
fn(AAAFFF000)形成私有上下文,私有上下文中可能有一些私有变量(也可能没有),私有上下文理如果有东西被它上下文以外其他东西占用,AAAFFF000就不会被释放,不会被释放的话如果有私有变量也会被存起来,这种函数机制叫闭包。
闭包俩大作用:

  • 保护私有变量,操作和外部没有任何关系,不会有干扰
  • 还能形成一个不被释放的上下文,能够保存里面的一些东西

fn()(5)和f(5)的效果一样,先执行fn()->AAAFFF000(),返回的结果BBBFFF111,再执行BBBFFF111(5)。
唯一区别就是EC(AN)没有被占用,隐藏EC(FN2)也没被占用,执行万会员都会被释放。
f(5) => BBBFFF000(5), 此时全局的a为1了,b+a为6。再执行a++为2
1.png

4

  1. var test = (function (i) {
  2. return function () {
  3. alert(i *= 2);
  4. }
  5. })(2);
  6. test(5);

答案: 弹出4
解析: 把自执行函数结果返回test

  1. var test = (function (i) {
  2. /*
  3. * 先把自执行函数执行,再把自执行函数执行的返回结果赋值给test
  4. * => test等于的是返回的小函数
  5. * EC(AN)
  6. * 作用域链:<EC(AN),EC(G)>
  7. * 形参赋值:i=2
  8. * 变量提升:--
  9. * 不释放的闭包(通俗说大函数里面包小函数),function(){alert(i*=2)} 形成堆内存地址,返回给test
  10. * 即就是堆内存地址被test引用,不被释放形成闭包
  11. */
  12. return function () {
  13. /*
  14. * test(5)执行 EC(TEST)
  15. * 作用域链:<EC(TEST),EC(AN)>
  16. * 形参赋值:--(虽然传了5,但是没有形参,没有用)
  17. * 变量提升:--
  18. */
  19. alert(i *= 2); //=> i=i*2 =>'4'
  20. }
  21. })(2);
  22. test(5);

5

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

答案:

  • 9
  • 10
  • 10
  • 1

    6

    1. var x = 5,
    2. y = 6;
    3. function func() {
    4. x += y;
    5. func = function (y) {
    6. console.log(y + (--x));
    7. };
    8. console.log(x, y);
    9. }
    10. func(4);
    11. func(3);
    12. console.log(x, y);

    答案:

  • 11 6

  • 13
  • 10 6

    7

    1. function fun(n, o) {
    2. console.log(o);
    3. return {
    4. fun: function (m) {
    5. return fun(m, n);
    6. }
    7. };
    8. }
    9. var c = fun(0).fun(1);
    10. c.fun(2);
    11. c.fun(3);

    答案:

  • undefined

  • 0
  • 1
  • 1
  • {func: f(m)}

image.png
解析:
套娃题
变量提升阶段:声明加定义fun, 声明c
image.png
image.png
**

  1. function fun(n, o) {
  2. console.log(o);
  3. return {
  4. fun: function (m) {
  5. return fun(m, n);
  6. }
  7. };
  8. }
  9. var c = fun(0).fun(1);
  10. c.fun(2);
  11. c.fun(3);

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

  1. function test(){
  2. var age=18;
  3. function addAge(){
  4. age++;
  5. alert(age);
  6. }
  7. return addAge;
  8. }
  9. var fn=test(); fn();//弹出19
  • 闭包有三个特性
    • 函数嵌套函数;
    • 内部函数使用外部函数的参数和变量;
    • 参数和变量不会被垃圾回收机制回收。
  • 闭包的好处
    • 希望一个变量长期保存内存中;
    • 避免全局变量污染;
    • 私有成员的存在。
  • 闭包的缺点
    • 常驻内存,增加内存使用量;
    • 使用不当造成内存泄漏。

9 简述let和var的区别?

  • let 块级作用域,不存在变量提升,而且要求必须 等let声明语句执行完之后,变量才能使用,不然会报Uncaught ReferenceError错误。
  • let变量不能重复声明

let不允许在相同作用域内,重复声明同一个变量。否则报错:Uncaught SyntaxError: Identifier 'XXX' has already been declared

  • ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

  • var 可以变量提升,会声明为全局变量,且会给window新增一个私有属性。let/const 声明的全局变量和window没有关系

  • 10 下面代码输出的结果是多少,为什么?如何改造一下,就能让其输出 20 10?

    1. var b = 10;
    2. (function b() {
    3. b = 20; // 函数b没有被重新声明,b还是初始的函数
    4. console.log(b);
    5. })();
    6. console.log(b);

    答案:

  • b() { b=20; console.log(b) }

  • 10

image.png
改造代码

  1. var b = 10;
  2. (function b() {
  3. var b = 20; // 函数重新被声明变成 b= 20
  4. console.log(b);
  5. })();
  6. console.log(b);

解析:

  1. var b = 10;
  2. (function () {
  3. b = 20;
  4. console.log(b); // 20
  5. })();
  6. console.log(b); // 20

如果是匿名函数的话,答案都是20。

  1. (function fn(){})(); // 自执行函数
  2. console.log(fn); //Uncaught ReferenceError: fn is not defined

匿名函数具名化(设置了名字):

  • 设置的名字只能在函数内部使用,外部是无法使用的(基于这种方式代替严格模式下不兼容的arguments.callee,并以此实现递归算法[自己调用自己])

arguments.callee代表函数本身,严格模式下会报错,用自执行函数来代替

  1. // 自执行函数
  2. (function fn(){
  3. console.log(fn); // 函数本身
  4. console.log(arguments.callee) // 函数本身
  5. })();

image.png

  • 在函数内部去修改这个名字值,默认是不能修改的,代表的依然是函数本身(除非这个函数名字在函数体中被重新声明过,重新声明后,一起都按照重新声明的为主)
    1. // 自执行函数
    2. (function fn(){
    3. console.log(fn); // 函数本身
    4. fn = 10;
    5. console.log(fn); // 依然是函数本身
    6. })();
    image.png
    1. // 自执行函数
    2. (function fn(){
    3. function fn(){2} // 重新声明
    4. console.log(fn); // 函数被改变
    5. })();
    image.png
    1. // 自执行函数
    2. (function fn(){
    3. /*
    4. * 变量提升: var fn;
    5. */
    6. console.log(fn) // undefined
    7. var fn = 20;
    8. console.log(fn); // 20
    9. })();
    image.png

11 实现函数fn,让其具有如下功能(百度二面)

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

答案一:

  1. function fn(x,y) {
  2. return function(z) {
  3. return x+y+z
  4. }
  5. }
  6. let res = fn(1, 2)(3);
  7. //=>先让FN执行,执行的返回结果再执行(返回结果一定是个函数
  8. //把返回的小函数执行,最后小函数返回的结果是把,这几次传递的实参依次相加
  9. console.log(res); //=>6 1+2+3

答案二:

  1. //基于ES6中的箭头函数来优化
  2. let fn = (x, y) => (z) => x + y + z;
  3. let res = fn(1, 2)(3);
  4. console.log(res); //=>6

答案三:

  1. function fn() {
  2. // 执行FN传递的进来的实参集合 Array.from()把类数组转换为数组
  3. let outerArg = Array.from(arguments);
  4. return function () {
  5. // 执行返回的小函数,传递进来的实参集合
  6. let innerArg = Array.from(arguments);
  7. outerArg = outerArg.concat(innerArg);
  8. // 把数组按照"+"变为每一项相加的字符串,再基于EVAL把字符串变为表达式执行
  9. return eval(outerArg.join('+'));
  10. }
  11. }
  12. let res = fn(1, 2)(3);
  13. console.log(res); //=>6 1+2+3

参考:https://juejin.im/post/6855707169459798030

12 实现函数fn,让其具有如下功能(百度二面)

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

答案:
参考:https://juejin.im/post/6844904160765149192

  1. function compose(...funcs) {
  2. // funcs接收的就是所有传递进来的函数
  3. return function anonymous(val) {
  4. // val第一个函数执行时候需要的实参 0
  5. if (funcs.length === 0) return val;
  6. if (funcs.length === 1) return funcs[0](val);
  7. // return funcs.reverse().reduce((N, item) => {
  8. //两种方式
  9. //1.
  10. return typeof N === "function" ? item(N(val)) : item(N);
  11. });
  12. //2.
  13. return funcs.reverse().reduce((N, item) => {
  14. return item(N);
  15. }, val);
  16. }
  17. }
  18. let result = compose(div2, mul3, add1)(5);
  19. console.log(result);

四、THIS的作业题

THIS

  • 函数的执行主体,和执行上下文不是一个概念
  • 全局的this是window,我们研究的都是函数中的this
  • this是谁和函数在哪执行,以及在哪定义都没有必然的联系

  • 按照以下规律来确定执行主体是谁:

    • 给当前元素的某个事件行为绑定方法,事件触发,执行对应的方法,方法中的this是当前元素本身(排除:IE6~8基于attachEvent实现的DOM2事件绑定,绑定的方法中的this不是操作的元素,而是window)
    • 函数执行,首先看函数名之前是否有“点”,有“点”,“点”前面是谁this就是谁,没有“点”this就是window(在JS的严格模式下,没有“点”,方法中的this是undefined)
      • 自执行函数(匿名函数)中的this一般都是window/undefined(严格模式)
      • 回调函数中的this一般也都是window/undefined(除非特殊处理了)
    • 构造函数中的this是当前类的实例
    • 箭头函数没有自己的this,用到的this都是上下文中的this
    • 基于call/apply/bind可以强制改变this的指向
      1. document.body.onclick = fucntion() {
      2. // this是body
      3. // DOM 0级事件绑定
      4. }
      5. ==================================
      6. "use strict"; //全局上下文开启严格模式
      7. ===================================
      8. (function () {
      9. "use strict"; //当前上下文开启严格模式
      10. })();
      11. =================================
      12. function fn(){
      13. console.log(this)
      14. }
      15. let obj = {
      16. name: 'xxx',
      17. fn: fn
      18. };
      19. fn(); // window
      20. obj.fn(); // {name: 'xxx', fn:f} this是obj
      21. ==================================
      22. function fn(){
      23. console.log(this)
      24. }
      25. let obj = {
      26. name: 'xxx',
      27. fn: fn
      28. };
      29. fn(); // undefined
      30. obj.fn(); // {name: 'xxx', fn:f} this是obj

1

  1. var num = 10;
  2. var obj = {
  3. num: 20
  4. };
  5. obj.fn = (function (num) {
  6. this.num = num * 3;
  7. num++;
  8. return function (n) {
  9. this.num += n;
  10. num++;
  11. console.log(num);
  12. }
  13. })(obj.num);
  14. var fn = obj.fn;
  15. fn(5);
  16. obj.fn(10);
  17. console.log(num, obj.num);

答案:

  • 22
  • 23
  • 65 30

解析:
1.png

2

  1. let obj = {
  2. fn: (function () {
  3. return function () {
  4. console.log(this);
  5. }
  6. })()
  7. };
  8. obj.fn();
  9. let fn = obj.fn;
  10. fn();

答案:

  • {fn: f()}
  • Window {….}

image.png
解析:

  1. let obj = {
  2. // 等于返回的小函数
  3. fn: (function () {
  4. return function () {
  5. console.log(this);
  6. }
  7. })()
  8. };
  9. obj.fn(); // this为obj
  10. let fn = obj.fn;
  11. fn(); // this 为window

3

  1. var fullName = 'language';
  2. var obj = {
  3. fullName: 'javascript',
  4. prop: {
  5. getFullName: function () {
  6. return this.fullName;
  7. }
  8. }
  9. };
  10. console.log(obj.prop.getFullName());
  11. var test = obj.prop.getFullName;
  12. console.log(test());

答案:

  • undefined
  • language

解析:

  1. var fullName = 'language';
  2. var obj = {
  3. fullName: 'javascript',
  4. prop: {
  5. getFullName: function () {
  6. return this.fullName;
  7. }
  8. }
  9. };
  10. // this->obj.prop
  11. // this.fullName ->obj.prop.fullName -> undefined
  12. console.log(obj.prop.getFullName());
  13. var test = obj.prop.getFullName;
  14. // this-> window
  15. // this.fullName -> window.fullName => 'language'
  16. console.log(test());

4

  1. var name = 'window';
  2. var Tom = {
  3. name: "Tom",
  4. show: function () {
  5. console.log(this.name);
  6. },
  7. wait: function () {
  8. var fun = this.show;
  9. fun();
  10. }
  11. };
  12. Tom.wait();

答案: window
解析:

  1. var name = 'window';
  2. var Tom = {
  3. name: "Tom",
  4. show: function () {
  5. // this->window
  6. console.log(this.name); // window.name -> 'window'
  7. },
  8. wait: function () {
  9. // this-> Tom
  10. var fun = this.show; // fun = Tom.show
  11. fun(); // 没有点 this-> window
  12. }
  13. };
  14. Tom.wait(); // this -> Tom

5

  1. window.val = 1;
  2. var json = {
  3. val: 10,
  4. dbl: function () {
  5. this.val *= 2;
  6. }
  7. }
  8. json.dbl();
  9. var dbl = json.dbl;
  10. dbl();
  11. json.dbl.call(window);
  12. alert(window.val + json.val);

答案:弹出 24

  1. window.val = 1; //2 // 4
  2. var json = {
  3. val: 10, // 20
  4. dbl: function () {
  5. this.val *= 2;
  6. }
  7. }
  8. // this->json
  9. // this.val*=2 => json.val*=2 => 10*2 = 20
  10. json.dbl();
  11. var dbl = json.dbl;
  12. // this-> window =>this.val*=2 => window.val*=2 => 1*2 = 2
  13. dbl();
  14. // this->window
  15. // this.val *= 2 => window.val*=2 => window.val = 2*2 = 4
  16. json.dbl.call(window);
  17. alert(window.val + json.val); // 4+20 = 24

6

  1. (function () {
  2. var val = 1;
  3. var json = {
  4. val: 10,
  5. dbl: function () {
  6. val *= 2;
  7. }
  8. };
  9. json.dbl();
  10. alert(json.val + val);
  11. })();

答案:弹出12
解析:

  1. (function () {
  2. var val = 1; // 2
  3. var json = {
  4. val: 10,
  5. dbl: function () {
  6. // this-> json
  7. // val = val*2 val不是自己私有的变量,是自执行函数执行创建出来的上下文中的变量
  8. val *= 2;
  9. }
  10. };
  11. json.dbl();
  12. alert(json.val + val); // 10 +2 = 12 =>最后弹出的是字符串“12” alert的原因
  13. })();