14 关于数据类型转换的作业题
15 关于THIS基础情况的联系题
16 arguments及形参的映射机制
17 闭包作用域的几道练习题
18 关于闭包“套娃”的面试题
一、变量提升的作业
1
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);
答案:
- undefined undefined undefined
- 10 13 14
- 100 13 200
- 12 undefined 200
解析:
/** EC(G)* VO(G)* a = 12* b = 13 / undefined* c = 14 / 200* fn = 0x000000 [[scope]]:EC(G) 形参(a)* 变量提升: var a; var b; var c; function fn(){...};*/console.log(a, b, c); // undefined * 3var a = 12,b = 13,c = 14;function fn(a) {/** EC(FN)* AO(FN)* a = 10 / 100* 作用域链:<EC(FN),EC(G)>* 形参赋值:a=10* 变量提升:--*/console.log(a, b, c); // 10 13 14a = 100;c = 200;console.log(a, b, c); // 100 13 200}b = fn(10); //函数执行没有返回结果(RETURN)结果是undefinedconsole.log(a, b, c); // 12 undefined 200
问题:在变量提升阶段需要考虑var和function谁先提升么?
不需要,自上而下。
2
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();
答案:
- 10
- 10
解析:函数执行,它的上级上下文是谁和函数在哪执行没关系,上级上下文是它的[[scope]],所以在哪定义的函数,它的上级上下文就是谁。
3
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
- {name: “jack”}

解析:
/** EC(G)* a = 1* obj = 0x000000* fn = 0x000001 [[scope]]:EC(G)*/var a = 1;var obj = {name: "tom" // "jack"};function fn() {/** EC(FN)a2 = 全局a的值1* 作用域链:<EC(FN),EC(G)>*/var a2 = a;obj2 = obj; //即不是私有的,也不是全局的,则此处相当于 window.obj2=0x000000(全局的obj)a2 = a;obj2.name = "jack"; //把window.obj2中的name修改(全局的obj也是这个堆)}fn();console.log(a); //=>1console.log(obj); //=>{name:'jack'}
4
var a = 1;function fn(a){console.log(a)var a = 2;function a(){}}fn(a);
答案:
- ƒ a(){}
解析:
/** EC(G)* a = 1* fn = 0x000000 [[scope]]:EC(G)*/var a = 1;function fn(a) {/** EC(FN)* a = 1* = 0x000001 [[scope]]:EC(FN)* = 2** 作用域链:<EC(FN),EC(G)>* 形参赋值:a=1* 变量提升:* var a; 已经有a了,不需要重新声明* function a(){}; function a是声明加定义:不需要重新声明,但是需要重新赋值*/console.log(a); //=>函数var a = 2; // a=2还没处理过呢,在把私有变量a的值赋值为2function a() {} //它不处理了,变量提升阶段都处理完了console.log(a); //=>2}fn(a); //fn(1)console.log(a); // => 1 全局的a还是1
5
console.log(a);var a=12;function fn(){console.log(a);var a=13;}fn();console.log(a);
答案:
- undefined
- undefined
- 12
解析:
/** EC(G)* a = 12* fn = 0x000000 [[scope]]:EC(G)*/console.log(a); // =>undefinedvar a=12;function fn(){/** EC(FN)* a = 13** 作用域链:<EC(FN),EC(G)>* 形参赋值:--* 变量提升:var a;*/console.log(a); //=>undefinedvar a=13;}fn();console.log(a); //=>12
console.log(a);var a=12;function fn(){console.log(a);a=13;}fn();console.log(a);
答案:
- undefined
- 12
13
/** EC(G)* a* fn = 0x000000 [[scope]]:EC(G)** 变量提升:var a; function fn(){...};*/console.log(a); //=>undefinedvar a = 12; //全局的a=12function fn() {/** EC(FN)** 作用域链:<EC(FN),EC(G)>* 形参赋值:--* 变量提升:--*/console.log(a); //=>12a = 13; //把全局的a=13}fn();console.log(a); //=>13
console.log(a);a=12;function fn(){console.log(a);a=13;}fn();console.log(a);
答案:
a is not defined

解析:
/** EC(G)* fn = 0x000000 [[scope]]:EC(G)** 变量提升:function fn(){...};*/console.log(a); //首先看是否为全局变量,不是,则再看是否为window的一个属性,如果还不是,报错:Uncaught ReferenceError: a is not defined (这行代码一但报错,下面代码都不处理了)a=12;function fn(){console.log(a);a=13;}fn();console.log(a);
6
var foo='hello';(function(foo){console.log(foo);var foo=foo||'world';console.log(foo);})(foo);console.log(foo);
答案:
- hello
- hello
- hello
匿名函数不进行变量提升。
/** EC(G)* foo** 变量提升:var foo;*/var foo = 'hello'; //全局的foo='hello'(function (foo) {/** EC(ANY)* foo = 'hello'** 作用域链:<EC(ANY),EC(G)>* 形参赋值:foo='hello'* 变量提升:var foo;*/console.log(foo); //=>'hello'// A||B:A的值是真,返回A的值,否则返回B的值// A&&B:A的值是真,返回B的值,否则返回A的值// 同时出现,&&优先级高于||var foo = foo || 'world'; //foo='hello'是真值,直接赋值,不会走到'world'console.log(foo); //=>'hello'})(foo); //自执行函数执行,传递实参'hello'console.log(foo); //=>'hello'
7
新版浏览器中: function xxx(){} 如果没有出现在 {} 中,则变量提升阶段是“声明+定义”;如果出现在 {} 中,除函数/对象的大括号外,则”只声明”。
老版本浏览器:function xxx(){} 无论是否出现在 {} 中,都是“声明+定义(赋值)”。
{function foo() {}foo = 1;}console.log(foo);
答案: ƒ foo() {}
解析:function foo(){} 这段代码在全局上下文中EC(G)里面声明过一次,在私有块级上下文中声明加定义过一次,俩个地方都声明过,就会在最后一次声明的地方,把之前对于foo的操作都“映射”给全局一份。
/** EC(G)* foo** 变量提升:function foo;*/// debugger;{/** EC(BLOCK)* foo = 0x000000* = 0x000001** 变量提升:* function foo(n){}* function foo(m) {}*/function foo(n) {} //把之前对foo的操作“映射”给全局 全局foo=0x000001foo = 1;function foo(m) {} //一样要把之前对foo的操作“映射”给全局一份 全局foo=1console.log(foo); //=>1}console.log(foo); //=>1
{function foo() {}foo = 1;function foo() {}}console.log(foo);
答案: 1
解析:
/** EC(G)* foo** 变量提升:function foo;*/// debugger;{/** EC(BLOCK)* foo = 0x000000* = 0x000001** 变量提升:* function foo(n){}* function foo(m) {}*/function foo(n) {} //把之前对foo的操作“映射”给全局 全局foo=0x000001foo = 1;function foo(m) {} //一样要把之前对foo的操作“映射”给全局一份 全局foo=1console.log(foo); //=>1}console.log(foo); //=>1
{function foo() {}foo = 1;function foo() {}foo = 2;}console.log(foo);
答案:1
解析:
/** EC(G)* foo** 变量提升: function foo(n) function foo(m)*/{/** EC(BLOCK)* foo = 0x000000* = 0x000001* 变量提升:* function foo(n){}* function foo(m){}*/function foo(n) {} //映射给全局 全局foo=0x000001foo = 1;function foo(m) {} //映射给全局 全局foo=1foo = 2; //私有的处理,和全局没关系了console.log(foo); //=>2}console.log(foo); //=>1
8
var x = 1;function func(x,y=function anonymous1(){x=2}){x =3;y();console.log(x)}func(5);console.log(x);
答案:
- 2
- 1
解析:
var x = 1;function func(x,y=function anonymous1(){x=2}){var x =3;y();console.log(x)}func(5);console.log(x);
答案:
- 3
- 1

函数执行的时候:
- 条件1:有形参赋值默认值(不论是否传递参数,也不论默认值的类型)
- 条件2:函数体中有变量声明(必须是基于let/const/var, 注意let/const不允许重复声明,不能和形参变量名一致)
满足上面2个条件,除了默认形成的”函数私有上下文“,还会多创建一个”块级私有上下文“(函数体大括号包起来的)
作用域和上下文什么区别?
作用域是创建时候的环境
上下文是执行时候的环境
debuggerfunction fn(x, y) {var x=12;}fn() // 形参没有赋值默认值,只有一个私有上下文Local

debuggerfunction fn(x, y=12) {x=12;}fn() // y有默认值,但函数内x没有var,也只有一个私有上下文Local

debuggerfunction fn(x, y=12) {var x=12;}fn() // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文,一个块级上下文

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

debuggerfunction fn(x, y=12) {var x=12;y=13; // 不属于块级上下文,属于私有上下文}fn(10, 20) // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文,一个块级上下文

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

var x = 1;function func(x,y=function anonymous1(){x=2}){var x =3;var y = function anonymous2(){x=4};y();console.log(x)}func(5);console.log(x);
答案:
- 4
- 1
解析:
var x=1;function func(x,y=function anonymous1(){x=2}){/** EC(FUNC) 私有函数上下文* x = 5* y = 0x000001 [[scope]]:EC(FUNC) -> anonymous1** 作用域链:<EC(FUNC),EC(G)>* 形参赋值:x=5 y=anonymous1...*//** EC(BLOCK) 私有块级上下文* x = 5 / 3 / 4* y = 0x000001 / 0x000002 [[scope]]:EC(BLOCK) -> anonymous2** 作用域链:<EC(BLOCK),EC(FUNC)>* 变量提升:var x; var y;* 代码执行:*/var x=3;var y=function anonymous2(){/** EC(Y)* 作用域链:<EC(Y),EC(BLOCK)>* 形参赋值:--* 变量提升:--*/x=4; // x是上级上下文EC(BLOCK)};y(); //私有块级中的y,也就是anonymous2console.log(x); //=>4}func(5);console.log(x);
二、数据类型和基础知识作业
1
let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;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++ 都是转换为数字
let n = "10";console.log(n+20) // 1020;console.log(parseInt(n)+20)// 30;console.log(+n+20) // 30;
2
{}+0?alert('ok'):alert('no');0+{}?alert('ok'):alert('no');
答案: 先弹出no再弹出ok
解析: {}+0 =>0 {}不参与运算,0、NaN、’’、null、undefined都是false10+{} => "10[object Object]" 参与运算
3
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');}
答案:弹出number
解析:Number(‘12px’) => NaN
NaN属于Number类型
parseInt('12px') => 12parseInt('12px13') => 12parseInt('px12') => NaNparseInt('12.5px') => 12parseFloat('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
let arr = [27.2, 0, '0013', '14px', 123];arr = arr.map(parseInt);console.log(arr);
答案:[27, NaN, 1, 1, 27 ]
解析:
arr=arr.map(function(item,index){//每迭代数组中的一项,都会触发回调函数执行,并且把当前迭代这一项和这一项的索引传递给这个函数//回调函数的返回值会把数组这一项替换调,原始数组不变,返回一个新数组});let arr = [27.2, 0, '0013', '14px', 123];arr = arr.map(parseInt);// parseInt('27.2',0) =>27 27.2先变成字符串, 0相当于10 进制// parseInt('0',1) =>NaN 0先变成字符串// parseInt('0013',2) =>从左到右找到符合进制的'001'看作2进制最后转换为10进制 0*2^2+0*2^1+1*2^0 => 1// parseInt('14px',3) =>'1'看作3进制转换为10进制 1*3^0 => 1// parseInt('123',4) =>123先转成字符串 '123'看作4进制转换为10进制 1*4^2+2*4^1+3*4^0 = 16+8+3 => 27console.log(arr);
let arr = [27.2, 0, '0013', '14px', 123];arr.map(parseInt);console.log(arr);// [27.2, 0, "0013", "14px", 123]
三、闭包作用域的作业
1
var a = 10,b = 11,c = 12;function test(a) {a = 1;var b = 2;c = 3;}test(10);console.log(a, b, c);
答案: 10 11 3
解析:test(10) ,test形成私有上下文,形参a和声明过得变量b都是私有变量,c是全局变量。
**
2
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
- 10
- undefined
解析:
arguments是个实参集合(类数组集合),arguments传多少个实参b(1, 2, 3)就有多少个实参,不管 function b(x, y, a){} 定义多少个形参。真实结构是 {0:1,1:2,2:3,length:3} 。
知识点:
非严格模式与严格模式的区别:
- this指向window和undefined的区别
实参和形参是否建立隐射机制
/** EC(G)* a* function b(x,ya){...} b = 0x000000*/var a = 4; // 全局的a=4function b(x, y, a) {/** EC(B)* x=1* y=2* a=3* 作用域链:<EC(B),EC(G)>* 初始THIS:window* 初始arguments:[1,2,3] => {0:1,1:2,2:3,length:3} 类数组集合(不伦是否设置形参,只要传递实参,arguments就有值,不传递实参是一个空的类数组集合)* 形参赋值:x=1 y=2 a=3* 变量提升:--** 在非严格模式下,形参赋值完成,会和ARGUMENTS中的每一项建立映射机制(一个改,另外一个也会跟着改);但是严格模式下("use strict")不存在映射机制;*/console.log(a);// 3arguments[2] = 10; // a=10console.log(a);}a = b(1, 2, 3);console.log(a); // undefined 函数b没有返回值(主要看return)

var a = 4;function b(x, y, a) {a = 3;console.log(arguments[2])}a = b(1,2)
答案: undefined
var a = 4;function b(x, y, a) {/** EC(B)* 作用域链:<EC(B),EC(G)>* 初始ARGUMENTS:[1,2]=> {0:1,1:2,length:2} 类数组* 形参赋值:x=1 y=2 a=undefined* 变量提升:--** 映射机制是在函数代码执行之前完成的,那会建立了映射就有映射,如果此时没建立,映射机制后续也就不会再有了*/a = 3;console.log(arguments[2]); //=>undefined}a = b(1, 2);
知识点:剩余运算符
ES6的箭头函数已经没有arguments了,利用剩余运算符获取传入的参数(数组形式)function b(...args) {console.log(arguments) // 现在很少这么用了console.log(args) // ES6都是用剩余运算符直接获取到参数数组}b(1,2,3,4)

问题1:**改变arguments会隐射到形参,那么改变私有变量是否会改变arguments呢?
会改变argumentsvar a = 4;function b(x, y, a) {console.log(arguments, a)a=13console.log(arguments, a);}a = b(1, 2, 3);console.log(a);

问题2:如果函数形参取默认值呢?改变私有变量是否会改变arguments么?
不会,因为实参只有1和2,arguments里面根本就不包含a,所以不会影响到arguments。var a = 4;function b(x, y, a=15) {console.log(arguments, a)a=13console.log(arguments, a);}a = b(1, 2);console.log(a);
3
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);
答案:
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
4
var test = (function (i) {return function () {alert(i *= 2);}})(2);test(5);
答案: 弹出4
解析: 把自执行函数结果返回test
var test = (function (i) {/** 先把自执行函数执行,再把自执行函数执行的返回结果赋值给test* => test等于的是返回的小函数* EC(AN)* 作用域链:<EC(AN),EC(G)>* 形参赋值:i=2* 变量提升:--* 不释放的闭包(通俗说大函数里面包小函数),function(){alert(i*=2)} 形成堆内存地址,返回给test* 即就是堆内存地址被test引用,不被释放形成闭包*/return function () {/** test(5)执行 EC(TEST)* 作用域链:<EC(TEST),EC(AN)>* 形参赋值:--(虽然传了5,但是没有形参,没有用)* 变量提升:--*/alert(i *= 2); //=> i=i*2 =>'4'}})(2);test(5);
5
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);
答案:
- 9
- 10
- 10
-
6
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);
答案:
11 6
- 13
-
7
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);
答案:
undefined
- 0
- 1
- 1
- {func: f(m)}

解析:
套娃题
变量提升阶段:声明加定义fun, 声明c
**
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);
8 简述你对闭包的理解,以及其优缺点?
function test(){var age=18;function addAge(){age++;alert(age);}return addAge;}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?
var b = 10;(function b() {b = 20; // 函数b没有被重新声明,b还是初始的函数console.log(b);})();console.log(b);
答案:
b() { b=20; console.log(b) }
- 10

改造代码
var b = 10;(function b() {var b = 20; // 函数重新被声明变成 b= 20console.log(b);})();console.log(b);
解析:
var b = 10;(function () {b = 20;console.log(b); // 20})();console.log(b); // 20
如果是匿名函数的话,答案都是20。
(function fn(){})(); // 自执行函数console.log(fn); //Uncaught ReferenceError: fn is not defined
匿名函数具名化(设置了名字):
- 设置的名字只能在函数内部使用,外部是无法使用的(基于这种方式代替严格模式下不兼容的arguments.callee,并以此实现递归算法[自己调用自己])
arguments.callee代表函数本身,严格模式下会报错,用自执行函数来代替
// 自执行函数(function fn(){console.log(fn); // 函数本身console.log(arguments.callee) // 函数本身})();

- 在函数内部去修改这个名字值,默认是不能修改的,代表的依然是函数本身(除非这个函数名字在函数体中被重新声明过,重新声明后,一起都按照重新声明的为主)
// 自执行函数(function fn(){console.log(fn); // 函数本身fn = 10;console.log(fn); // 依然是函数本身})();

// 自执行函数(function fn(){function fn(){2} // 重新声明console.log(fn); // 函数被改变})();

// 自执行函数(function fn(){/** 变量提升: var fn;*/console.log(fn) // undefinedvar fn = 20;console.log(fn); // 20})();

11 实现函数fn,让其具有如下功能(百度二面)
let res = fn(1,2)(3);console.log(res); //=>6 1+2+3
答案一:
function fn(x,y) {return function(z) {return x+y+z}}let res = fn(1, 2)(3);//=>先让FN执行,执行的返回结果再执行(返回结果一定是个函数//把返回的小函数执行,最后小函数返回的结果是把,这几次传递的实参依次相加console.log(res); //=>6 1+2+3
答案二:
//基于ES6中的箭头函数来优化let fn = (x, y) => (z) => x + y + z;let res = fn(1, 2)(3);console.log(res); //=>6
答案三:
function fn() {// 执行FN传递的进来的实参集合 Array.from()把类数组转换为数组let outerArg = Array.from(arguments);return function () {// 执行返回的小函数,传递进来的实参集合let innerArg = Array.from(arguments);outerArg = outerArg.concat(innerArg);// 把数组按照"+"变为每一项相加的字符串,再基于EVAL把字符串变为表达式执行return eval(outerArg.join('+'));}}let res = fn(1, 2)(3);console.log(res); //=>6 1+2+3
参考:https://juejin.im/post/6855707169459798030
12 实现函数fn,让其具有如下功能(百度二面)
/*在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如: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函数的编写*/
答案:
参考:https://juejin.im/post/6844904160765149192
function compose(...funcs) {// funcs接收的就是所有传递进来的函数return function anonymous(val) {// val第一个函数执行时候需要的实参 0if (funcs.length === 0) return val;if (funcs.length === 1) return funcs[0](val);// return funcs.reverse().reduce((N, item) => {//两种方式//1.return typeof N === "function" ? item(N(val)) : item(N);});//2.return funcs.reverse().reduce((N, item) => {return item(N);}, val);}}let result = compose(div2, mul3, add1)(5);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的指向
document.body.onclick = fucntion() {// this是body// DOM 0级事件绑定}=================================="use strict"; //全局上下文开启严格模式===================================(function () {"use strict"; //当前上下文开启严格模式})();=================================function fn(){console.log(this)}let obj = {name: 'xxx',fn: fn};fn(); // windowobj.fn(); // {name: 'xxx', fn:f} this是obj==================================function fn(){console.log(this)}let obj = {name: 'xxx',fn: fn};fn(); // undefinedobj.fn(); // {name: 'xxx', fn:f} this是obj
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);
答案:
- 22
- 23
- 65 30
2
let obj = {fn: (function () {return function () {console.log(this);}})()};obj.fn();let fn = obj.fn;fn();
答案:
- {fn: f()}
- Window {….}

解析:
let obj = {// 等于返回的小函数fn: (function () {return function () {console.log(this);}})()};obj.fn(); // this为objlet fn = obj.fn;fn(); // this 为window
3
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());
答案:
- undefined
- language
解析:
var fullName = 'language';var obj = {fullName: 'javascript',prop: {getFullName: function () {return this.fullName;}}};// this->obj.prop// this.fullName ->obj.prop.fullName -> undefinedconsole.log(obj.prop.getFullName());var test = obj.prop.getFullName;// this-> window// this.fullName -> window.fullName => 'language'console.log(test());
4
var name = 'window';var Tom = {name: "Tom",show: function () {console.log(this.name);},wait: function () {var fun = this.show;fun();}};Tom.wait();
答案: window
解析:
var name = 'window';var Tom = {name: "Tom",show: function () {// this->windowconsole.log(this.name); // window.name -> 'window'},wait: function () {// this-> Tomvar fun = this.show; // fun = Tom.showfun(); // 没有点 this-> window}};Tom.wait(); // this -> Tom
5
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);
答案:弹出 24
window.val = 1; //2 // 4var json = {val: 10, // 20dbl: function () {this.val *= 2;}}// this->json// this.val*=2 => json.val*=2 => 10*2 = 20json.dbl();var dbl = json.dbl;// this-> window =>this.val*=2 => window.val*=2 => 1*2 = 2dbl();// this->window// this.val *= 2 => window.val*=2 => window.val = 2*2 = 4json.dbl.call(window);alert(window.val + json.val); // 4+20 = 24
6
(function () {var val = 1;var json = {val: 10,dbl: function () {val *= 2;}};json.dbl();alert(json.val + val);})();
答案:弹出12
解析:
(function () {var val = 1; // 2var json = {val: 10,dbl: function () {// this-> json// val = val*2 val不是自己私有的变量,是自执行函数执行创建出来的上下文中的变量val *= 2;}};json.dbl();alert(json.val + val); // 10 +2 = 12 =>最后弹出的是字符串“12” alert的原因})();
