看js代码说结果并分析(1-10)

157 阅读6分钟

看js代码说结果并分析

1. let和var在for循环体的区别?

for (let i=0; i<3; i++) {
    setTimeout(() => {
        console.log(i);
    })
}

for (var j=0; j<3; j++) {
    setTimeout(() => {
        console.log(j);
    })
}

答案:

0
1
2
3
3
3

解析:

var说明的变量没有块级作用域,在常规任务队列for循环体执行后,由于变量保存所有的setTimeout调用的i都指向迭代后同一个i, 即3;

let声明的变量,js引擎会为每一个迭代循环声明一个新的迭代变量,每一个setTimeout引用的j都是不同变量,即0 1 2

2. let和var的区别?

if (!('a' in window)) {
    var a = 1;
}
if (!('b' in window)) {
    let b = 1;
}

console.log(a);
console.log(b);

答案:

undefined
Uncaught ReferenceError: b is not defined

解析:

var声明的变量没有块级作用域,并且重载变量提升,以此该题代码等价于:

var a;
if (!('a' in window)) {
    a = 1;
}

此时a为全局变量,而由var声明的全局变量会成为window的属性,因此if语句块中的代码不会执行,所以console.log(a)输出undefined

let声明的变量存在块级作用域的概念, 所以let b不会成为全局变量,即使其成为全局变量, 也不会成为window的属性,因为let声明的全局变量不会成为window的属性, 所以在console.log(b)所在的作用域(全局)中, 未声明的变量b,因此会报错:Uncaught ReferenceError: b is not defined

3. 闭包的理解

function fn() {
    var i = 0;
    return function (){
        console.log(i++);
    }
}
var f1 = fn();
var f2 = fn();
f1();
f1();
f2();

答案:

0
1
0

解析:

var 声明的变量是不存在块级作用域,但存在局部作用域,题中的变量 i 的作用域为 fn() 作用域;

每次调用fn() 都会形成一个闭包,不同闭包之间是独立的不会互相干扰;

闭包会将 i 的之保存下来,多次调用fn()返回的函数会修改i的值,最终输出:0 1 0

拓展:

什么是闭包,什么场景下需要用闭包?如何使用闭包?闭包解决什么问题?

什么作用域?

4. 命名函数表达式的理解

var foo = function bar() {
    return 123;
}
console.log(typeof foo);
console.log(typeof foo());
console.log(typeof bar());

答案:

function
number
Uncaught ReferenceError: bar is not defined

解析:

  1. 这是一个函数表达式,并赋值到foo变量上,typeof foofunction
  2. foo() 作为一个函数来调用返回结果为123,typeof 123number
  3. 函数表达式在MDN文档描述如下:

命名函数表达式(Named function expression) 如果你像在函数体内部引用当前函数,则需要创建一个命名函数表达式。 然后函数名称将会(且只会)作为函数体(作用域内)的本地变量

命名函数表达式的函数名只在其函数体内有效,所以调用bar()会报错。

5. this的绑定规则

var obj = {
    a: 1,
    foo: function () {
        return this.a;
    }
}
var fun = obj.foo;
console.log(obj.foo());
console.log(fun());

答案:

1
undefined

解析:

  1. 直接调用 obj.fun() 时,它的前面加上了对哦 obj 的引用,当函数引用由上下文对象时, 隐式绑定规则会把函数调用的this绑定到这个上下文对象。所以this.a等价于obj.a,故输出:1

  2. 虽然fun是obj.foo的一个引用,但是它引用的是foot函数本身, 因此fun()实际上是一个不带任何修饰的函数调用, 此时引用默认绑定规则,即this指向全局对象。而全局对象和不存在属性 a ,故输出:undefined

6. 原型和原型链

function A(x) {
    this.x = x;
}
function B(x) {
    this.x = x;
}
A.prototype.x = 1;
B.prototype = new A();
var a = new A(2);
var b = new B(3);
delete b.x;

console.log(a.x);
console.log(b.x);

答案:

2
undefined

解析:

先了解什么是原型链

  1. js中每个函数都有一个prototype属性,指向一个对象(成为原型对象);
  2. 每个对象都有一个__proto__属性,指向该对象构造函数的原型对象;
  3. 原型对象也有__proto__属性,于是一个对象跨域沿着__proto__逐层向上, 直到这个对象的原型对象为null,null没有原型,即为原型链的终点。

回到题目:

  1. a.x,对象a上存在属性x为2,故输出:2
  2. b.x,对象b的属性x被delete,所以向上寻找,首先查看b.__proto__,即B.prototype,为new A(), 因为没有传入x,所以x为undefined,找到属性x,最终输出:undefined

7. 如果钩子函数显式返回一个对象,使用new创建的对象会怎么样?

const obj = {
    flag: false
};
function A() {
    this.flag = true;
    console.log(this)
    console.log(obj)
    return obj;
}

const a = new A();
console.log(a.flag);

答案:

false

解析:

  1. 考察new运算符
  2. 如果构造函数显式返回一个对象,那么该对象胡覆盖new创建的对象
  3. 于是变量a指向obj,因此a.flag等价于obj.flag

8. 事件循环、微任务、宏任务的理解

console.log(1);
setTimeout(() => {
    console.log(2);
}, 0);
console.log(3);
Promise.resolve(4).then((b) => {
    console.log(b);
});
console.log(5);

答案:

1
3
5
4
2

解析:

先了解考察范围:

  1. 微任务、宏任务、事件循环
  2. 浏览器中事件循环如下:
      -----➔ 宏任务
     |          |
     |          ↓
     |   1. 按照顺序执行同步任务
     |   2. 将微任务和宏任务分别加入队列
     |          |
     |          ↓
     |       /      \
     |      | 有微任务 |-----------
     |       \      /            |
     |          |                |
     |          ↓                |
     |   按照入队顺序执行所有微任务   |
     |          |                |
     |          ↓                |
     |------ 浏览器渲染 ←----------|

回到题目:

  1. 整体代码作为第一个宏任务;
  2. 先按顺序执行同步任务,此时输出:1 3 5 并将微任务和宏任务压入任务队列, 此处微任务为then方法,宏任务为setTimeout方法;
  3. 判断有无微任务,有then执行,此时输出:4
  4. 浏览器渲染完成进入下一个循环,执行宏任务队列中的setTimeout,此时输出:2

9. 变量声明提升和函数声明提升

var a = 1;
function a() {}
console.log(typeof a);

答案:

number

解析:

  1. 考察变量提升和函数声明提升
  2. var和function都会提升,且function优先级高于变量,作用在变量之前,原代码等价于:
function a() {}
var a;
a = 1;
console.log(typeof a);
  1. 并且变量不会重复声明,于是a = 10赋值给了原本指向函数的变量a,故输出:number

10. 形参、局部变量、引用类型、立即执行函数

var obj = {
    a: 1
};
((obj) => {
    console.log(obj.a);
    obj.a = 3;
    var obj = {
        a: 2
    }
    console.log(obj.a);
})(obj);
console.log(obj.a);

答案:

1
2
3

解析:

  1. 考察形参、局部变量、引用类型、立即执行函数
  2. 立即执行函数内部的var声明变量会提升到函数顶部,代码等价于:
var obj = {
    a: 1
};
((obj) => {
    var obj;
    console.log(obj.a); // obj入参是引用类型,a的值为全局变量属性
    obj.a = 3;  // 入参obj的a属性被修改为3
    obj = {  // 函数内部给局部变量obj赋值
        a: 2
    }
    console.log(obj.a); // a的值为2
})(obj);
console.log(obj.a);  // a的值已经被修改为3