第 33 题:下面的代码打印什么内容,为什么?
var b = 10; (function b() { b = 20; console.log(b) })()
针对这题,在知乎上看到别人的回答说:
- 函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定。
- 对于一个常量进行赋值,在 strict 模式下会报错,非 strict 模式下静默失败。
- IIFE中的函数是函数表达式,而不是函数声明。
实际上,有点类似于以下代码,但不完全相同,因为使用 const 不管在什么模式下,都会 TypeError 类型的错误
const foo = function () { foo = 10; console.log(foo) } (foo)() // Uncaught TypeError: Assignment to constant variable.
我的理解是,b 函数是一个相当于用 const 定义的常量,内部无法进行重新赋值,如果在严格模式下,会报错 Uncaught TypeError: Assignment to constant variable.
例如下面的:
var b = 10; (function b() { 'use strict' b = 20; console.log(b) })() // "Uncaught TypeError: Assignment to constant variable."
不知道我这回答有没有问题
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(43)
@wasonal
按照你这种说法,外面 b 变量的值会变成 20,但是最后验证发现:外面 b 变量的值仍然是 10。所以从侧面验证,你上面的这句话是错误的。
这句话是正确的,具体解释可以去看这篇文章
这句话是错误,b 变量指的是函数表达式的函数名。试想一下:如果你在函数里面拿不到该函数的函数名,那你怎么进行递归?
// 验证1
var b = 10; // 报错
function b () {
b =20;
console.log(b);
}
b();
// 验证2
var b =10;
(function b(b){
b = 20;
console.log(b); // 20,说明如果函数作用域挂载了名为b的变量,那么就会赋值改变
})()
// 验证3
var b =10;
(function b(){
var b = 20;
console.log(b); // 20
})()
// 验证4
var b =10;
(function a(b){
b = 20;
console.log(b); // 20
})()
// 验证5
function b () {
alert(0)
}
(function b(){
b()
console.log(b); // 无限循环,而不是alert(0)
})()
从上面的验证其实就可以看到,在自执行函数b的执行环境中是没有变量b,但是有函数b的,因为没有进行实参传入和var,let声明,所以b=20会顺着作用域链往外找,就找到了b=10,那个地方,对应b=20,改变的也是全局环境的b变量.所以,当你在函数b中打印b,这时是不分变量和函数的,因为b的执行环境中挂载的有函数b,函数b就会被打印出来.你可以参考我最后一个验证,不是b=20,而是直接调用b,就会形成死循环,递归调用.所以这是一个执行环境的问题
《在JavaScript的立即执行的具名函数A内修改A的值时到底发生了什么?》
这篇文章的解释还比较清晰。
我觉得这个最棒的也最好理解的回答 如果执行体内加了var声明 那就是声明的值
我简单点再说下自己的看法:
因为具名函数的函数名标识符(这在例子中的b)只能在函数体内可以访问,并且是不可修改的。所以对b重新赋值也是无效的,所以输出的结果是一个函数。
详细解析请访问:https://segmentfault.com/q/1010000002810093/a-1020000002810564
当JS 解释器遇到非匿名立即执行函数时,会创建一个辅助的特定对象,然后将函数名称当作这个对象的属性,因此函数内部才可以访问到b,但是这个值又是只读的,所以对他的赋值并不生效,所以打印的结果还是这个函数,并且外部的值也没有发生更改
这里说的很清楚了,各位大佬可以看看~~(咦,这个链接不能直接在这里打开,要复制到浏览器中才能打开?)
https://yuchengkai.cn/docs/frontend/#%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87
非匿名自执行函数,函数名只读
var b = 10;
(function b(){
b = 20;
console.log(this.b);
})();
// 打印:10
var b = 10;
(function b(){
var b = 20;
console.log(b);
})();
// 打印:20
问:这个函数还是不是立即执行函数,立即执行函数不得是一个匿名函数吗
上式function用括号括起来了,说明这是一个函数表达式,而不是一个函数声明。括号可以改变运算符的优先级,在这里它是一个分组符。可参考文章:https://www.cnblogs.com/TomXu/archive/2011/12/29/2290308.html
具名函数表达式中的函数名在函数内部是可以访问的,但函数外面是无法访问到的,这点和函数声明有很大的不同。没有括号的话,全局变量是可以访问到b的。至于函数里面的b是全局变量自然不必细说。因此,console.log里面的b肯定现在函数内部找,然后再在全局找,所有b就是funcition b{}。
因此,代码等价于:
常量的话没必要,这只是严格模式下为了规范代码,防止b被修改罢了。但是在非严格模式下,就是说可以修改的,不必当成常量。因为要考虑到具名函数声明的情况下,函数的名称在其所在的执行环境下,都是可以访问的,也是可修改的。
只读状态为什么没报错呢?
var b = 10;
(function b() {
var b = 20;
console.log(b)
})()
为什么用了 var b = 20,输出变成20呢。不用var输出b()
@ravencrown
两种情况:
var b 则在函数 b 内申明了一个局部变量,当执行 b = 20 时,顺着作用域链向上找,于是在函数内找到了局部变量 b(也就是 var b 的), 将其修改为 20。console.log(b)同理,顺着作用域链向上找,找到了局部变量 b,且其值为 20.
执行 b = 20 时,顺着作用域链向上找,找到函数 b, 尝试给 b 赋值为 20,由于函数 b 是函数表达式,而函数表达式的函数名是常量,无法二次赋值(在正常模式下静默失效,在严格模式下报错),赋值失败,所以输出的还是该函数
@daiyunchao 严格模式下会报错
把()中IIFE函数执行过程这样理解试一下:
An anonymous function that is called immediately is most often called an immediately invoked function expression (IIFE). It resembles a function declaration, but because it is enclosed in parentheses it is interpreted as a function expression.
这段话引用自:《javascript高级程序设计第4版》大概意思是立即执行函数,写法上看着像是个“函数声明” 实际上是按“函数表达式”解析的。
回过头来,我们看IIFE为什么要这样设计?
这里的局部变量b(函数体b)我们外部能调用吗?
不能,该变量只能在函数体内部被访问,它代表命名空间函数本身。
我们没有理由去声明一个与命名空间函数名冲突的变量名,也没有理由更改这个变量,很显然这与命名空间的思想冲突,我们应该保持该变量的纯净。
因此,这种“命名空间函数名”被作为常量的设计是合理的,建议使用新的变量名存储自己的变量。
至于具体是如何实现的,懂C的同学可以去扒一下v8的源码,臣妾真的是做不到了。
Note:其中不明白的术语,请自行Google
我有个疑问,函数声明的确优于变量声明,但是却不优于变量赋值,所以我的疑问是跟优先级没有关系,不知对不对
可以写成这个,配合作用域和变量提升,就可以知道为什么打印的是函数b了
1.函数表达式中的函数名只在函数体内部可用,指代函数表达式本身,其他地方都不可用
2.函数表达式中的函数名是只读的,所以b=20;会赋值失败
good answer
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
1.IIFE中的函数是一个函数表达式,不是函数声明。
区分函数声明和表达式最简单的方法是看function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。比如这里的第一个词是"(",不是function,所有这里的函数是一个函数表达式。
2.函数声明中的的函数名被绑定在它声明所在的作用域中。函数表达式中的函数名被绑定在函数自身的函数体中。
在IIFE中的函数名不会污染外部作用域。
这里函数名"b"只在函数内部有效,它是函数内部的局部变量。
3.在函数表达式的内部只能通过函数名访问该函数,但是不能通过函数名对该函数重新赋值
你真棒棒。。。麽麽噠
下面的代码打印什么内容,为什么
输出函数体,原因:
IFFE中的函数是一个函数表达式,不是函数声明,类似于 const a = function(){} 常量绑定,
加入对一个常量进行赋值,非strict模式默认无效,strict模式报错,
所以a=20不会覆盖原有的函数名b,而是会在全局,也就是window下添加一个名为b的属性,值为20,
输出 function(){ xxx }
我可能搞错了,但是我觉得以下这个代码片段
证明了它和 Function.name 的 Writable 属性 是没有关系的
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/name
Function.name 只是一个 Function 的命名和它是否可以被赋值无关
同时,另外一种说法,也就是《命名函数》如 function a() {...} 内《和命名函数同名的变量》会优先指向命名函数本身
这种说法应该也是错误的,为什么呢,因为以上的代码打印的是 a1: 2, a2: 2 也就是说,不仅《命名函数》/* part 2 */ function a() {...} 内的 a 可以被修改,打印为2,实际上 window.a 也被修改了,最终打印为2
也就是说实际上只有单纯有《命名函数》,是不构成函数内《和命名函数同名的变量》会优先指向命名函数本身这个现象,而以上片段实际上应该相当于
那么我们发现只有在《自启动命名函数》或《被赋值的命名函数》,也就是:
时,函数的内部作用域中的 《和命名函数同名的变量》 a 才会优先指向《命名函数》本身
实际上无论哪种写法,都是需要该命名函数《不被挂载在window对象上》,换而言之,需要使得《和命名函数同名的变量》 a 才会优先指向《命名函数》本身,《命名函数》本身必须处于一种“流离失所”的状态
自执行函数会被编译成函数表达式,函数表达式的函数名所在的作用域为函数本身,且函数名是只读的,不可被重新赋值。
// function b(){
// b=20;
// console.log(b)
// }
// var b;
// b=10;
// b()
执行是这样执行的 在window环境下查找 函数执行 环境决定的预估跟左右查询变量有关系 但说不上问题出在哪里了
我记得
你不知道的js
里面有说这个,但是找不到在哪里了。首先运行结果是打印 b 函数;
我觉得这个问题想要考察的是:一个是 IIFE 有独立的词法作用域,第二是b=20是将 b 定义为全局变量。
词法检查的结果为:
在执行 IIFE 的时候,第一行
b = 20
,会将b挂到window上,并不会在这个IIFE 的词法作用域内,所以最终打印的是 b 函数。另外,如果把函数b 中的
b = 20
改成var b = 20
那么结果就会打印20了。@jefferyE 匿名函数是可以递归调用的,只不过在严格模式下会报错
所以重点是:非匿名自执行函数,函数名只读。
针对这题,在知乎上看到别人的回答说:
函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定。
对于一个常量进行赋值,在 strict 模式下会报错,非 strict 模式下静默失败。
IIFE中的函数是函数表达式,而不是函数声明。
实际上,有点类似于以下代码,但不完全相同,因为使用const不管在什么模式下,都会TypeError类型的错误
(foo)() // Uncaught TypeError: Assignment to constant variable.
我的理解是,b函数是一个相当于用const定义的常量,内部无法进行重新赋值,如果在严格模式下,会报错"Uncaught TypeError: Assignment to constant variable."
例如下面的:
})() // "Uncaught TypeError: Assignment to constant variable."
不知道我这回答有没有问题
这个回答主要表达的是:函数表达式的函数名只在该函数内部有效,且绑定是常量类似 const,不能修改
解答了一部分疑问,但是这个是ES 的规范吗?
var b = 10; (function b() { b = 20; console.log(b); })();
具名自执行函数的变量为只读属性,不可修改
几个例子:
所以严格模式下能看到错误:
Uncaught TypeError: Assignment to constant variable
其他情况例子:
有
window
:有
var
:@jefferyE
如果是这样的话,那很好奇预编译后的代码了...
ps: 我第一眼也认为是IIFE是函数表达式,而不是函数声明,但发现是个具名函数了...
@jjeejj
这个目前暂时还没有去确认下是不是ES5规范,只是看到了一篇博客上是这么说的
@jessie-zly
这个上面有回答
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript
里面说明一个潜在问题——既然匿名函数没有名字,那该怎么递归调用它呢?在这一点上,JavaScript 允许你命名这个函数表达式。你可以命名立即调用的函数表达式(IIFES——Immediately Invoked Function Expressions)
例如:
如上所提供的函数表达式的名称的作用域仅仅是该函数自身。这允许引擎去做更多的优化,并且这种实现更可读、友好。该名称也显示在调试器和一些堆栈跟踪中,节省了调试时的时间。
关键
function b() { b = 20; console.log(b) }
这是一个具名函数,(function b() { b = 20; console.log(b) })()
那这还是IIFE吗?这个回答主要表达的是:函数表达式的函数名只在该函数内部有效,且绑定是常量类似 const,不能修改
解答了一部分疑问,但是这个是ES 的规范吗?
1打印结果内容如下:
ƒ b() {
b = 20;
console.log(b)
}
2原因:
作用域:执行上下文中包含作用于链:
在理解作用域链之前,先介绍一下作用域,作用域可以理解为执行上下文中申明的变量和作用的范围;包括块级作用域/函数作用域;
特性:声明提前:一个声明在函数体内都是可见的,函数声明优先于变量声明;
在非匿名自执行函数中,函数变量为只读状态无法修改;
我的理解是,先不看函数自执行,直接fn b()
首先函数声明比变量要高,其次b = 20 没有var 获取其他,说明是window最外层定义的变量。
js作用域中,先找最近的 那就是b fn ,直接打印了,如果 b = 20 有var 那就是打印20