导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

下面代码输出的结果?

for(var i = 0; i < 3; i++){
  setTimeout(function(){
      console.log(i);
  },0);
};

答案:3,3,3

如何才能打印出 0 1 2

解决方法:

for(let i = 0; i < 3; i++){
  setTimeout(function(){
      console.log(i);
  },0);
};
// 0 1 2
for (var i = 0; i < 3; i++) {
  (function(i) {
    setTimeout(function () {
      console.log(i);
    }, 0, i)
  })(i)
};
// 0 1 2

⭐ 下面代码打印结果是什么?为什么?

var b = 10;
(function b() {
  b = 20;
  console.log(b)
})();

答案:

解析:

var b = 10;
(function b() {
   // 内部作用域,会先去查找是有已有变量b的声明,有就直接赋值20,确实有了呀。发现了具名函数 function b(){},拿此b做赋值;
   // IIFE的函数无法进行赋值(内部机制,类似const定义的常量),所以无效。
  // (这里说的“内部机制”,想搞清楚,需要去查阅一些资料,弄明白IIFE在JS引擎的工作方式,堆栈存储IIFE的方式等)
    b = 20;
    console.log(b); // [Function b]
    console.log(window.b); // 10,不是20
})();
// 严格模式下能看到错误:Uncaught TypeError: Assignment to constant variable

其他情况例子:

// 有window:
var b = 10;
(function b() {
  window.b = 20;
  console.log(b); // [Function b]
  console.log(window.b); // 20是必然的
})();
// 有var:
var b = 10;
(function b() {
  var b = 20; // IIFE内部变量
  console.log(b); // 20
  console.log(window.b); // 10
})();

解析来源 ←

⭐ 输出以下代码执行的结果并解释为什么

var obj = {
    '2': 3,
    '3': 4,
    'length': 2,
    'splice': Array.prototype.splice,
    'push': Array.prototype.push
};
obj.push(1);
obj.push(2);
console.log(obj);

结果:

Untitled

解析参考:

结果最后变为一个稀疏数组[,,1,2]。 因为在对象中加入splice属性方法,和length属性后。这个对象变成一个类数组。属性名称可转换为数字时,会映射成为索引下标。所以对象实际可这样描述:[,,3,4],然后继续执行length 属性复制,length 原来值为4,length:2 语句将数组截断成为长度为2的数组。此时数组为[,,]。接着push(1)、push (2)。数组追加两个值,[,,1,2]。小菜看法,有错望指正!!!

参考来源:

下面代码中 a 在什么情况下会打印 1?

var a = ?;
if (a == 1 && a == 2 && a == 3) {
 	console.log(1);
}

解析:因为 == 会进行隐式类型转换 所以我们重写 toString 方法就可以了

答案:

var a = {
  i: 1,
  toString() {
  	return a.i++;
  }
}

if(a == 1 && a == 2 && a == 3){
 	console.log(1);
}

⭐ 输出以下代码的执行结果并解释为什么

var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x);
console.log(b.x);

答案:

a.x   // --> undefined
b.x   // --> {n: 2}

解析:

var a = { n: 1 }; // a保持对{n:1}对象的引用
var b = a; // b保持对{n:1}对象的引用
a.x = a = { n: 2 }; // a的引用被改变

a.x 	// --> undefined
b.x 	// --> {n: 2}

// 画图
var a = { n: 1 }; // a 是个对象
var b = a;
a.x = a = { n: 2 };
console.log(a.x); // undefined
console.log(b.x); // { n: 2 }

/**
 * 栈             堆
 * a:002            001: { n: 1, x: 002 }
 * b:001            002: { n: 2 }
 */
  1. . 运算符优先,a.x 此时保持对 {n: 1} 的引用,也就是 b 也保持对 {n: 1} 的引用,于是 {n: 1} => {n: 1, x: undefined},此时 ab 还是对原来对象的引用,只不过原来对象增加了 x 属性
  2. = 从右往左,a = {n: 2},此时a的引用已经变成了 {n: 2} 这个对象
  3. a.x = a,此时 a.x 是保持对 { n: 1, x: undefined } 中的 x 引用,也就是 b.x ,于是 { n: 1, x: undefined } => {n: 1, x: { n: 2}},即 b.x = { n: 2 }题目来源:Daily-Interview-Question 第53题

输出以下代码运行结果

// example 1
var a = {}, b = '123', c = 123;
a[b] = 'b';
a[c] = 'c';
console.log(a[b]);
---------------------
// example 2
var a = {}, b = Symbol('123'), c = Symbol('123');
a[b] = 'b';
a[c] = 'c';
console.log(a[b]);
---------------------
// example 3
var a = {}, b = { key:'123' }, c = { key:'456' };
a[b] = 'b';
a[c] = 'c';
console.log(a[b]);

分析:这题考察的是对象的键名的转换。

解答:

// example 1
var a={}, b='123', c=123;
a[b]='b';
// c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。
a[c]='c';
// 输出 c
console.log(a[b]);
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 类型,不需要转换。
a[b]='b';
// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b。
a[c]='c';
// 输出 b
console.log(a[b]);
// example 3
var a={}, b={key:'123'}, c={key:'456'};
// b 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。
a[b]='b';
// c 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。
a[c]='c';
// 输出 c
console.log(a[b]);

题目来源:Daily-Interview-Question 第76题

以下代码中,当 foo 是哪些值的时候,会执行 alert ?

var foo = 'false';
    foo = 'true';
    foo = 1;
    foo = 0;
    foo = -1;
    foo = "";
    foo = undefined;
    foo = null;
    foo = NaN;
    foo = false;
    foo = [];
    foo = {};

if (!foo) { alert('foo') }

答案:

var foo = 'false'; // 不行 	非空字符串的字符串,Boolean() 后为真
    foo = 'true'; // 不行
    foo = 1; // 不行
    foo = 0; // 行
    foo = -1; // 不行				只有数字 0 Boolean 后才为 false ,其余数字都为 true
    foo = ""; // 行
    foo = undefined; // 行
    foo = null; // 行
    foo = NaN; // 行
    foo = false; // 行
    foo = []; // 不行
    foo = {}; // 不行

⭐ 以下代码运行输出结果是什么

var num = 100;
var fn = function() {
  var num = ++num;
  console.log(num);
}
fn();

解析:

函数执行时,寻找变量和形参提升,发现只有 num
所以 fn 的 ao 中 num: undefined
接着 num = ++num 也就是 num = ++undefined;
++undefined 为 NaN
所以最终打印 NaN

下面打印是什么

var obj1 = Object.create(null);
obj1.a = 1;

var obj2 = Object.create(obj1);
obj2.b = 2;

console.log(obj2.__proto__);

原型存在在原型链上,但现在链不存在了(就是最终到达 Object.prototype 的这个链儿 在 obj1 这断掉了)

Untitled

⭐ 打印预测

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2);
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})
// 答案在下

// true
// false
// false
// false
// p2=1
// p1=1
// p4=1

Promise.resolve(...)可以接收一个值或者是一个Promise对象作为参数。
当参数是普通值时,它返回一个resolved状态的Promise对象,对象的值就是这个参数;
当参数是一个Promise对象时,它直接返回这个Promise参数。因此,p1 === p2。
但通过new的方式创建的Promise对象都是一个新的对象,因此后面的三个比较结果都是false。
另外,为什么p4的then最先调用,但在控制台上是最后输出结果的呢?
因为p4的resolve中接收的参数是一个Promise对象p1,resolve会对p1”拆箱“,获取p1的状态和值,
但这个过程是异步的

⭐ 预测结果

console.log(Array.apply(null, { length: 2 }));

解析:

打印 [undefined, undefined]

Array.apply(null, { length: 2 })
// 等价于
Array.apply(null, [undefined, undefined])
// 等价于
Array(undefined, undefined)
// 等价于
new Array(undefined, undefined)

Array.apply(null, { length: 2 }) 是什么意思

预测打印结果 - 参数默认值

var x = 1;

function test(x, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x);
  var x = 2;
  y();
  console.log(x);
}

test();
console.log(x);
var x = 1;

function test(x, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x); // x 未赋值,打印undefined
  var x = 2; // 把 x 改为 2
  y(); // 执行y函数,改的是参数作用域中的x,打印 3
  console.log(x); // 函数内部 x 现在为 2
}

test();
console.log(x); // 全局x,打印 1

var x = 2; 删掉之后:

var x = 1;

function test(x, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x);
  y();
  console.log(x);
}

test();
console.log(x);
var x = 1;

function test(x, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x); // 形参 x 此刻 undefined
  y(); // 依然改的是参数作用域中 x ,改后为 3
  console.log(x); // 局部作用域中没有声明 x ,在参数作用域中找,找到了 3
}

test();
console.log(x); // 打印全局x 1

继续改造,把形参 x 改为 a

var x = 1;

function test(a, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x);
  var x = 2;
  y();
  console.log(x);
}

test();
console.log(x);
var x = 1;

function test(a, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x); // 打印形参内部 x,此刻为 undefined
  var x = 2;
  y(); // 参数作用域中没有 x ,外层找,全局有 x ,改全局 x 为 3 ,打印 3
  console.log(x); // 局部作用域中 x 已经改为了 2,打印 2
}

test();
console.log(x); // 全局x已改为3

继续改造,把形参 a 改为 x = 4

var x = 1;

function test(x = 4, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x);
  var x = 2;
  y();
  console.log(x);
}

test();
console.log(x);
var x = 1;

function test(x = 4, y = function() {
  x = 3;
  console.log(x);
}) {
  console.log(x); // 预编译阶段,x先为undefined,然后实参赋值给形参,没有实参,用4赋值,所以为4
  var x = 2;
  y(); // 更改参数作用域x为3
  console.log(x); // 局部作用域中x,打印2
}

test();
console.log(x); // 全局还是1

继续改造,参数 y 函数中我不更改 x 值了,直接打印 x:

var x = 1;

function test(x = 4, y = function() {
  console.log(x);
}) {
  console.log(x);
  var x = 2;
  y();
  console.log(x);
}

test();
console.log(x);
var x = 1;

function test(x = 4, y = function() {
  console.log(x); // 参数作用域中x还是4
}) {
  console.log(x); // 预编译阶段x为默认值4
  var x = 2; // 把局部作用域x改为2
  y();
  console.log(x); // 局部作用域中x改为了2
}

test();
console.log(x);// 全局1

再来改造,我们把原先参数 y 函数提到外边去:

var x = 1;
function yy() {
	x = 3;
  console.log(x);
}

function test(x = 4, y = yy) {
	console.log(x); // 预编译阶段x为默认值4
  var x = 2; // 局部作用域x改为2
  y(); // 相当于定义在全局的yy函数引用执行 -> 全局的x 改为 3
  console.log(x);
}

test();
console.log(x); // 全局改为3了

function test() {
	console.log(foo);
  var foo = 2;
  console.log(foo);
  console.log(a);
}

test();

解析:

AO: {
	foo: undefined -> 2
}

// 1. 变量声明提升 foo: undefined
// 2. 执行test
// 3. 第一行打印 foo 为 undefined
// 4. foo 赋值为 2
// 5. 再次打印 foo , 此刻为 2
// 6. 打印变量 a ,而 test 的 AO 中不存在 a ,所以报错 a is not defined

Untitled


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

解析:

AO {
	test: undefined -> fn
}

打印1

this 指向

var name = '222';
var a = {
	name: '111',
  say: function() {
    console.log(this);
  	console.log(this.name);
  }
}
var fun = a.say;
fun(); // ①
a.say(); // ②
var b = {
	name: '333',
  say: function(fn) {
    console.log(fn, this)
  	fn();
  }
}
b.say(a.say); // ③
b.say = a.say;
b.say(); // ④

解析:

fun()
把 a.say 这个函数赋值给 fun
然后 fun(); 此刻默认调用者是 window, so 此刻 this 指向 window
所以 1 处打印 window, '222'

a.say()
调用 say 方法的对象是 a ,所以 this 指向为 a
所以 2 处打印 a对象, '111'

b.say(a.say)
把 a.say 这个函数扔进 b.say() 参数中
此刻 this 为 b
所以 3 处 先打印 a.say 函数,b对象;
再执行 fn() 此刻没有 this.fn(), 所以是 window 调用 a.say 函数
所以最后打印 window, '222'

b.say()
在此之前把 a.say 函数赋值(覆盖)给 b.say 函数
b.say() 调用者是 b,所以 4 处打印 b对象, '333'

Untitled


function test() {
	var marty = {
  	name: 'marty',
    printName: function(){
    	console.log(this.name);
    }
  }
  
  var test1 = {
  	name: 'test1'
  }
  
  var test2 = {
  	name: 'test2'
  }
  
  var test3 = {
  	name: 'test3'
  }
  
  test3.printName = marty.printName;
  marty.printName.call(test1);
  marty.printName.apply(test2);
  marty.printName();
  test3.printName();
}

test();

解析:

marty.printName.call(test1); 相当于把 marty.printName 方法借给了 test1
所以执行时 this 指向改变为了 test1 而不是原本的 marty
所以   marty.printName.call(test1); 打印 'test1'

同理 marty.printName.apply(test2); 打印 'test2'

marty.printName(); 调用者是 marty 所以打印 'marty'

test3.printName(); 调用者是 test3 ,所以打印 'test3'

⭐ 经典

function Foo() {
	getName = function() {
  	console.log(1);
  }
 	a = 3;
  return this;
}
Foo.a = 1;
Foo.getName = function() {
	console.log(2, this);
}
Foo.prototype.getName = function() {
	console.log(3, this);
}
var getName = function() {
	console.log(4);
}
function getName() {
	console.log(5);
}
Foo.getName();
getName();
Foo().getName();
console.log(Foo.a, window.a);
new Foo.getName();
new Foo().getName();
new new Foo().getName();
new new Foo();

解析:

// 分析

// 大写 F ,表明把此函数当做构造函数看待
function Foo() {
  // 不执行Foo,下边对 getName和a的赋值不执行
  // getName和a前都没有var,而且参数中也没有同名变量,所以改的是全局window下的属性getName和a
	getName = function() {
  	console.log(1);
  }
 	a = 3;
  return this;
}
// 给 Foo 添加一个静态属性 a,此属性在实例化 Foo 时,不存在于实例化对象的属性中
Foo.a = 1;
// 给 Foo 添加一个静态方法 getName,此方法不存在于实例化对象的属性中
Foo.getName = function() {
	console.log(2, this);
}
// 扩展函数原型上的方法
Foo.prototype.getName = function() {
	console.log(3, this);
}
// 给全局变量 getName 赋值一个新函数
var getName = function() {
	console.log(4);
}
// 函数声明
function getName() {
	console.log(5);
}

Foo.getName();
getName();
Foo().getName();
console.log(Foo.a, window.a);
new Foo.getName();
new Foo().getName();
new new Foo().getName();

// 预编译
GO: {
	Foo: Foo构造函数,
  getName: undefined (var变量提升) -> 
    			 getName() { log5 } (函数声明覆盖了)
}

// 开始执行:

// 1. Foo.a = 1; 给 Foo 构造函数添加一个静态属性 a = 1
// 2. Foo.getName = function(){console.log(2)},给Foo构造函数添加一个静态方法getName
// 3. Foo.prototype.getName=function() {console.log(3);}, 扩展Foo原型方法getName
// 4. var getName = function() {console.log(4);} 覆盖全局属性getName为新函数
GO: {
	Foo: Foo构造函数,
  getName: undefined (var变量提升) -> 
    			 getName() { log5 } (函数声明覆盖了) ->
    			 fn() { log4 } (表达式覆盖)
}

// ------------------

// 5. Foo.getName(): 执行 Foo 函数静态方法, 打印 2, 打印的 this 为此刻 getName 调用者 Foo
// 6. getName(): 执行全局 getName 打印 4
// 7. Foo().getName(): Foo()隐式window调用,this是window 执行 Foo 形参 AO,修改 GO
AO: {
	啥都没有,没有变量提升和函数声明提升,也没有形参提升
}
GO: {
	Foo: Foo构造函数,
  getName: undefined (var变量提升) -> 
    			 getName() { log5 } (函数声明覆盖了) ->
    			 getName() { log1 },
  a: 3
}
// 调用 Foo() 后修改了全局 window 的 getName,最后 return this 其实 return 的是 window
// 所以 Foo().getName() 相当于 window.getName(),所以打印 1

// 8. console.log(Foo.a, window.a): 打印 Foo 静态属性a 为 1,window下a为3
// 9. new Foo.getName(): new 了 Foo 的静态方法 getName,打印 2,
// 打印的this是一个新对象,它不是 window 也不是 Foo 本身

// 10. new Foo().getName(): 相当于 (new Foo()).getName() 即先实例化 Foo,再调用
// 实例化对象foo的getName方法,即调用 Foo.prototype.getName 打印 3,
// 打印的 this 为新的 Foo 实例化对象

// 11. new new Foo().getName(): 可以看做 new ( (new Foo()).getName() ), 也即相当于
// new Foo.prototype.getName() ,也就是把 Foo 原型上的 getName 当做构造函数来new ,
// 还是打印 3, 打印的 this 为一个新对象

// 12. new new Foo(); 报错 , 先执行 new Foo() 得到一个实例化对象,
// 再用 new 去 new 一个实例化对象,就报错了

Untitled

总结

来源