导航
for(var i = 0; i < 3; i++){
setTimeout(function(){
console.log(i);
},0);
};
答案:3,3,3
解决方法:
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)
})();
答案:
Uncaught TypeError: Assignment to constant variable
解析:
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);
结果:
解析参考:
结果最后变为一个稀疏数组[,,1,2]。 因为在对象中加入splice属性方法,和length属性后。这个对象变成一个类数组。属性名称可转换为数字时,会映射成为索引下标。所以对象实际可这样描述:[,,3,4],然后继续执行length 属性复制,length 原来值为4,length:2 语句将数组截断成为长度为2的数组。此时数组为[,,]。接着push(1)、push (2)。数组追加两个值,[,,1,2]。小菜看法,有错望指正!!!
参考来源:
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 }
*/
.
运算符优先,a.x
此时保持对 {n: 1}
的引用,也就是 b
也保持对 {n: 1}
的引用,于是 {n: 1} => {n: 1, x: undefined}
,此时 a
和 b
还是对原来对象的引用,只不过原来对象增加了 x
属性=
从右往左,a = {n: 2}
,此时a的引用已经变成了 {n: 2}
这个对象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题
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 这断掉了)
自己.__proto__
找到原型,为 undefined(也就是指针不指向任何原型了,就是 undefined 了)__proto__
是原型链的指针,原型链都被破坏了,这个指针也断了__proto__
是 Object 的 属性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
function a() {
var test;
test();
function test() {
console.log(1);
}
}
a();
解析:
AO {
test: undefined -> fn
}
打印1
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'
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 一个实例化对象,就报错了
new Foo.getName()
中 .
的优先级高于 new
new Foo().getName()
中 new Foo()
优先级更高new Foo()
,再调用 new 出来的实例对象的 getName 方法