简介
这段时间一直在准备秋招,在这个过程中学习了非常多知识,如果不整理下来的话未免太过零散,于是笔者决定做个简单的面试相关知识点整理。原理啥的就不细写,有非常多大佬的文章写到了,此处主要是对部分题目的解析。
涉及
变量提升与函数提升,变量作用域。
例题
1.关于变量的作用域
// 会输出什么 ?
let x = 5;
function fn(x) {
// var x; //(1)
// var x = 10; //(2)
// let x = 10; //(3)
return function (y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7);
console.log(x);
首先是全局作用域中,用let声明了变量x。而后声明了函数fn。
调用fn,此处的x值应为 6 。原因是fn中的x已经是由参数传入的局部变量了。fn返回一个函数。
调用返回的该函数。传入参数y = 7。该匿名函数先遇到y变量,为本函数参数,得到y=7。
遇到x,发现x并不在本域中定义,转向函数定义处的上级寻找,在fn函数中找到了关于x的定义,得到x = 6。进行运算。
最后打印x,自然是全局变量的x。
结果为:14 5
那如果添加了语句(1)呢?(只添加语句1)
结果为:14 5
原因:函数内部局部变量是不能覆盖函数参数的。所以此处x不为undefined。
那如果添加了语句(2)呢?(只添加语句2)
结果为:18 5
不是说不能覆盖???
原因:没有覆盖但重新赋值了吖。var x的声明无效,但x = 10有效吖。
那如果添加了语句(3)呢?(只添加语句3)
结果为: 报错,重复定义的错。
// Uncaught SyntaxError: Identifier 'x' has already been declared
这和 let 的特性有关,不允许定义前使用(报引用错误,原因是它是块级作用域,有暂时性死区),不允许重复声明等等。
2.关于变量的作用域链条
// 会输出什么 ?
let v = 1;
function fn(){
alert(v);
}
function fac(){
let v = 3;
fn();
}
fac()
让我们回顾一下上一道题写的那句:
遇到x,发现x并不在本域中定义,转向函数定义处的上级寻找,在fn函数中找到了关于x的定义,得到x = 6。
此处就是:运行函数 fn 时遇到 v,发现v并不在本域中定义,转向函数定义处的上级寻找,在全局作用域中找到了关于 v 的定义,得到 v = 1。
所以找寻变量作用域链的时候应该从定义处往上找,找到全局了还没找着就报错。
结果是:1
3.关于变量/函数提升
永远记得当初只知道变量提升不知函数提升,被面试官完虐的痛 QAQ
那道题当初没记下来,这找了道不知是哪个大厂的面试题
function Foo() {
log = function () {
console.log(1);
};
return this;
}
Foo.log = function () {
console.log(2);
}
Foo.prototype.log = function () {
console.log(3);
}
var log = function () {
console.log(4);
}
function log() {
console.log(5);
}
Foo.log();
log();
Foo().log();
log();
new Foo.log();
new Foo().log();
一堆定义看的人头晕。先上结论吧。
结果是:2 4 1 1 2 3
还是结合着题目来讲吧
// 首先,定义了函数 Foo。当然在js中,函数也是对象。
// 函数定义被提升到了顶部
// 注意,函数还没真正运行之前,里面不论写了啥、定义了啥,那都是黑盒子,在Foo运行前都可以忽略里边写的乱七八糟的玩意儿
function Foo() {
//没有用var/let/const声明的变量都是全局变量,window.xx那种
//显然window.xx的等级是最高的,可以盖掉其他函数/变量的定义
//道理很好理解,如果权限不是最高的,window的属性不就能随便改随便写了嘛
//举个例子:var history = { state: 1 }; console.log(history);打印的还是原来的history对象。
log = function () {
console.log(1);
};
return this;
}
// 给Foo这个函数对象增加了个属性log
Foo.log = function () {
console.log(2);
}
// 原型链。简单地说,凡是用new Foo()的方式构建出的对象,它都能继承此log方法
Foo.prototype.log = function () {
console.log(3);
}
// 定义了一个log变量,用的是函数表达式。变量提升。
// 相同的函数名与变量名,函数提升变量也提升,但函数提升的等级更高。
var log = function () {
console.log(4);
}
// 定义了一个全局的log函数
function log() {
console.log(5);
}
// 现在我们来看题目
Foo.log();//调用Foo的属性的函数,2
log();// 全局定义了log函数,后面log又被赋值,所以打印 4
Foo().log(); // Foo运行后,window.log覆盖了前边的定义,此处this就是window。打印 1
log();//同上,调用的window.log。打印 1
new Foo.log();//意思就是把 function () {console.log(2);} 当作构造函数使,打印 2
new Foo().log();//new Foo()出来的对象继承自Foo.prototype。打印 3
这中间还有个之前困扰了我很久的问题,大家都说函数提升优先于变量提升,那为啥我的log函数(打印5的)会被log变量(打印4的)给覆盖掉?
此处把它拿出来独立看。
log();
var log;
log()
var log = function () {
console.log(4);
}
function log() {
console.log(5);
}
log();
结果是: 5 5 4
原因还是在于,提升的是定义,不是整个赋值表达式。
我们说的函数提升优先于变量提升体现在,声明了函数log,第二句语句var log是没有效果的,第三句log()照样打印得出 5 。
但是后面赋值了吖。
所以它提升后的顺序应该是这样的。
function log() {
console.log(5);
}
var log; // 写了但是没有用,log还是函数
log();
log();
log = function () {
console.log(4);// 赋值是有用的
}
log();
总结
坑还是挺多的,只是整理了一小部分放在这里。还是要多看书多理解。
有问题欢迎指出。感谢阅读。