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 * 3
var 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 14
a = 100;
c = 200;
console.log(a, b, c); // 100 13 200
}
b = fn(10); //函数执行没有返回结果(RETURN)结果是undefined
console.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); //=>1
console.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的值赋值为2
function 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); // =>undefined
var a=12;
function fn(){
/*
* EC(FN)
* a = 13
*
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:--
* 变量提升:var a;
*/
console.log(a); //=>undefined
var 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); //=>undefined
var a = 12; //全局的a=12
function fn() {
/*
* EC(FN)
*
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:--
* 变量提升:--
*/
console.log(a); //=>12
a = 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=0x000001
foo = 1;
function foo(m) {} //一样要把之前对foo的操作“映射”给全局一份 全局foo=1
console.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=0x000001
foo = 1;
function foo(m) {} //一样要把之前对foo的操作“映射”给全局一份 全局foo=1
console.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=0x000001
foo = 1;
function foo(m) {} //映射给全局 全局foo=1
foo = 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个条件,除了默认形成的”函数私有上下文“,还会多创建一个”块级私有上下文“(函数体大括号包起来的)
作用域和上下文什么区别?
作用域是创建时候的环境
上下文是执行时候的环境
debugger
function fn(x, y) {
var x=12;
}
fn() // 形参没有赋值默认值,只有一个私有上下文Local
debugger
function fn(x, y=12) {
x=12;
}
fn() // y有默认值,但函数内x没有var,也只有一个私有上下文Local
debugger
function fn(x, y=12) {
var x=12;
}
fn() // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文,一个块级上下文
debugger
function fn(x, y=12) {
var x=12; // 只有在大括号里面申明过得才能形成块级上下文
y=13; // 不属于块级上下文,属于私有上下文
}
fn() // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文Local,一个块级上下文Block
debugger
function fn(x, y=12) {
var x=12;
y=13; // 不属于块级上下文,属于私有上下文
}
fn(10, 20) // y有默认值,且函数内有var,形成俩个上下文:一个私有上下文,一个块级上下文
debugger
function fn(x, y=12) {
let x=12;
y=13; // 不属于块级上下文,属于私有上下文
}
// x重复声明了,报错。
fn(10, 20) // Uncaught SyntaxError: Identifier 'x' has already been declared
debugger
function 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,也就是anonymous2
console.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') => 12
parseInt('12px13') => 12
parseInt('px12') => NaN
parseInt('12.5px') => 12
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
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 => 27
console.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=4
function 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);// 3
arguments[2] = 10; // a=10
console.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=13
console.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=13
console.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= 20
console.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) // undefined
var 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第一个函数执行时候需要的实参 0
if (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(); // window
obj.fn(); // {name: 'xxx', fn:f} this是obj
==================================
function fn(){
console.log(this)
}
let obj = {
name: 'xxx',
fn: fn
};
fn(); // undefined
obj.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为obj
let 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 -> undefined
console.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->window
console.log(this.name); // window.name -> 'window'
},
wait: function () {
// this-> Tom
var fun = this.show; // fun = Tom.show
fun(); // 没有点 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 // 4
var json = {
val: 10, // 20
dbl: function () {
this.val *= 2;
}
}
// this->json
// this.val*=2 => json.val*=2 => 10*2 = 20
json.dbl();
var dbl = json.dbl;
// this-> window =>this.val*=2 => window.val*=2 => 1*2 = 2
dbl();
// this->window
// this.val *= 2 => window.val*=2 => window.val = 2*2 = 4
json.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; // 2
var json = {
val: 10,
dbl: function () {
// this-> json
// val = val*2 val不是自己私有的变量,是自执行函数执行创建出来的上下文中的变量
val *= 2;
}
};
json.dbl();
alert(json.val + val); // 10 +2 = 12 =>最后弹出的是字符串“12” alert的原因
})();