1. 请写出如下代码输出值,并解释为什么?
console.log({} + []);
{} + [];
[] + {};
{} + {};
console.log([] == false);
console.log({} == false);
if ([]) {
console.log([] == false);
}
('b' + 'a' ++ 'a' + 'a').toLocaleLowerCase();
0 == '0';
Boolean('0') == Boolean(0);
NaN == 0
NaN <= 0
题解
1. console.log({} + []); -> '[object Object]'
- 在console.log里面把他们都看作是字符串相加减, 表达式求值
- ({}).valueOf.toString() -> '[object Object]'
- [].valueOf.toString() -> ''
2. {} + []; -> 0
- 此时{}, 被看作是代码块
- [].valueOf().toSting() -> ''
- Number('') -> 0
3. [] + {}; -> '[object Object]'
- [] 得出是0
- {}, 前面有数组加它,它被当作是对象
- ({}).valueOf().toString() -> '[object Object]'
4. {} + {}; -> NaN
- 有分号,被看作是完整的代码。
- 第一个{}, 被看作是代码块
- 第二个{}, 被当作是对象
- ({}).valueOf().toString() -> '[object Object]'
- Number('[object Object]') -> NaN
4.1 {}+{} ->
- 没有分号结尾,被看作是同类型相加,都会先被转成基本类型再相加。
- ({}).valueOf().toString() -> '[object Object]'
- 所以是两个 '[object Object]' 连接
4和4.1 在不同浏览器表现形式不一样, Google和Safari浏览器表现为 NaN, '[object Object][object Object]', 而firefox浏览器表现为 NaN, NaN;
5. console.log([] == false); -> true
- [].valueOf -> []
- [].toString() -> ''
- '' == false -> true
6. console.log({} == false); -> 报错了
- {} 是个代码块
7. if ([]) {
console.log([] == false);
} -> true
- 虽然是个空数字,但是它有内存地址,所以会进去
- [] == false, true
8. ('b' + 'a' ++ 'a' + 'a').toLocaleLowerCase(); -> 'banana'
- +'a' -> NaN
- ('b' + 'a' + NaN + 'a') -> 'baNaNa'
9. 0 == '0'; -> true 隐式转换
10. Boolean('0') == Boolean(0); -> false
- Boolean('0'), 字符串为true
- 0, 为false
11. NaN == 0 -> false
12. NaN <= 0 -> false
- NaN 不等于任何
12.1 null <= 0 -> true
- Number(null) -> 0
总结
/**
* JavaScript等号与运算符运算规则
* 1.对象比较和运算使用ToPrimitive运算转换左与右运算元为原始数据类型值(primitive)
* 2.字符串在加号运算有最高的优先运算 然后是数字 最后是原始类型
* 3. ToPrimitive运算
* ① -> valueOf 能得到原始数据类型的值,则返回这个值。
-> toString 能得到原始数据类型的值,则返回这个值。
-> 报错 TypeError( null.f())
* ② [] == 0 流程 -> [].valueOf().toString() == 0 -> "" == 0
简单字符比较的时候直接 0 == "0" (Number("0"))
* ✨4.null 只等于undefined, null 使用关系运算符(+, -, >, < 而不是==)的时候会转为 0
* ✨5.NaN 不等于NaN ,[] 更不等于 [],注意if(块内的比较是确认是否有意义)
*/
let obj = {
valueOf: function() {
console.log('valueOf');
//return 1
return {}; // object
},
toString: function() {
console.log('toString');
return 'obj'; // string
}
};
console.log(1 + obj); //valueOf -> toString -> '1obj'
2. 请写出如下输出值,并完成扩展题
function fn() {
console.log(this.length);
}
var yideng = {
length: 5,
method: function() {
'use strict';
fn();
arguments[0]();
}
};
const result = yideng.method.bind(null);
result(fn, 1);
/**
* yideng.method.bind(null); 这句话是说yideng.method 不绑定任何东西,那就是指向window
* 执行result() -> window.fn() 此时this.length, this指向window, 所以window.length默认为0
* arguments[0] -> 拿到匿名函数 fn() {console.log(this.length)}; -> argument[0] 可以看作是 argument.fn, 所以此时的长度是2
*
*/
// 输出结果: 5 2
扩展 2-1
function bar() {
console.log(myName);
}
function foo() {
var myName = '老袁';
bar();
}
var myName = '京程一灯';
foo();
题解
// 解析变成以下这样:
var myName;
function bar() {
console.log(myName);
}
function foo() {
var myName = '老袁';
bar();
}
myName = '京程一灯';
foo();
/**
* 1. 作用域链是在定义的时候就已经确定了
* 2. js是词法作用域,不是动态作用域
* 3. 函数初始化与解析的时候,bar()中myName就能找到myName了
*/
// 输出结果: 京城一灯
// 改成以下就可以输出'老袁'
var myName;
function bar() {
console.log(myName);
}
function foo() {
myName = '老袁';
bar();
}
myName = '京程一灯';
foo();
3. 请问变量 a 会被 GC 回收么,为什么呢?
function test() {
var a = 'yideng';
return function() {
eval('');
};
}
test()();
// 不会被回收,eval机制导致。
// eval() 会把它塞到全局词法作用域,所以不会被回收。
扩展 3-1
var obj = { a: 30 };
with (obj) {
console.log(a);
b = 10;
}
console.log(b); // 10
console.log(obj); // {a: 30}
题解
with(expression), 大括号里面会自动加上 expression 的作用域。
因此 a 是 30,由于 obj 对象里面没有 b,所以会在 window 作用域下 var b;
obj 里面还是只有 a;
4. 请写出如下代码输出值,并解释为什么?
Object.prototype.a = 'a';
Function.prototype.a = 'a1';
function Person() {};
var yideng = new Person();
console.log(Person.a);
console.log(yideng.a);
console.log(1..a);
console.log(1.a);
console.log(yideng.__proto__.__proto__.constructor.constructor.constructor);
Object.prototype 和 Function.prototype 打印的内容差距很大是什么原因?
题解
- console.log(Person.a); -> 'a1'
- console.log(yideng.a); -> 'a'
- console.log(1..a); -> 'a', 1. 是浮点对象,所以拿的是 Object 原型上的 a
- console.log(1.a); -> 报错, 1. 是浮点对象,需要写成(1).a
4-1 (20, 30).x -> undefined 括号里面的东西,在加载时会提现执行
- console.log(yideng.proto.proto.constructor.constructor.constructor); -> ƒ Function(){ [native code] }
- 一般 constructor 调用多次,就是循环引用了
- 任何proto都是{}
- {}代表对象,对象的构造函数是 Object,都叫构造“函数”了,构造 Object 函数肯定就是 Function 了
- Function 本身也是函数,构造函数自然也是 Function
- Object.prototype 是个对象
- Function.prototype 是个函数

扩展 4-1
Object.prototype.name = 'kk';
function test() {}
test.name; // 'test'
1..name; // 'kk'
1.name; // 报错
// 构造函数的函数名,不可通过原型链去更改
5. 请写出如下代码执行结果
var a = {},
b = { key: 'b' },
c = { key: 'c' };
a[b] = 123;
a[c] = 456;
console.log(a[b]);
console.log(Symbol(b) == Symbol(b));
题解
- console.log(a[b]); -> 456
- a[b], b 作为 key,是一个对象,所以会先 valueOf,再 toString,变成[object Object],输出 a 为 {[object Object]: 123};
- a[c] 覆盖了 a[b]
- false
- 每个从 Symbol() 返回的 symbol 值都是唯一的,即使都是传的同一个值进去,也是唯一的;
6. 请写出你了解的 ES6 元编程
元编程是啥?就是可以改变 js 的 api!!!
用官方的话来说就是: 从 ECMAScript 2015 开始,JavaScript 获得了 Proxy 和 Reflect 对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如:属性查找,赋值,枚举,函数调用等),借助这两个对象你可以在 JavaScript 元级别编程; 可以看一下博客的 es6 新增的 proxy-和-reflect
let handler = {
get: function(target, name) {
return name in target ? target[name] : 42;
},
set: function(target, name) {
target[name] = 30;
// Reflect.set()
}
};
let p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
7. 请按找下方要求作答,并解释原理?请解释下 babel 编译后的 async 原理
let a = 0;
let yideng = async () => {
a = a + (await 10);
console.log(a);
};
yideng();
console.log(++a);
题解
- console.log(++a); -> 1 10
- 先定义后执行,一开始 a 是 0,执行 yideng 函数。里面是个异步,要先把主线程走完,所以会先跳出来执行 console。走完主线程,再走异步。
- 因为 async/await 的原理是 generator,在 generator 里面的变量全部都会被冻结,因为要保持一个干净的环境等待 yield 执行。
- 锁变量了,协程会保存调用栈信息,await 之前的会被锁定!!!! 所以 a 还是 0
扩展 7-1
let a = 0;
let yideng = async () => {
a = (await 10) + a;
console.log(a);
};
yideng();
console.log(++a); // 1 11
// await后面的不会被锁定,因为await是异步,需要等待了才能拿到值,所以这个时候可以拿到最新的a
:::waring 温馨提示
- async 就是 yield 的语法糖;
- await 之前的才会被锁住,后面的不会 :::
扩展 7-2
async function async1() {
console.log(1);
await async2();
console.log(3);
}
async function async2() {
console.log(2);
}
async1();
console.log(4);
// async不堵,await才会堵
// 1 -> 2 -> 4 -> 3
8. 请问点击 会有反应吗?为什么?能解决么?
$('#test').click(function(argument) {
console.log(1);
});
setTimeout(function() {
console.log(2);
}, 0);
while (true) {
console.log(Math.random());
}
不会,已经卡死了,用多线程 web worker。
题解
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Web Worker</title>
</head>
<body>
<button onclick="test()">点击</button>
<script>
const worker = new Worker('webworker.js');
worker.onmessage = function(event) {
console.log(event.data);
};
</script>
<script>
function test() {
alert('不占用主线程');
}
setTimeout(function() {
console.log(2);
}, 0);
</script>
</body>
</html>
// webworker.js
while (true) {
postMessage(Math.random());
}
9. 请先书写如下代码执行结果,并用 ES5 实现 ES6 PromiseA+规范的代码,同时你能解释下如何使用 Promise 完成事物的操作么?
const pro = new Promise((resolve, reject) => {
const innerpro = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
console.log(2);
resolve(3);
});
innerpro.then(res => console.log(res));
resolve(4);
console.log('yideng');
});
pro.then(res => console.log(res));
console.log('end');
题解
- 先执行同步 2 - > yideng
- 添加了 3 个异步,其中 2 个微任务,1 个宏任务 到事件队列中
- 同步还没执行完,继续执行同步 end
- 同步任务结束,执行异步事件队列
- 先执行推进去的微任务,resolve(3) -> 输出 3, 此时 promise 的状态已变成 rejected,完成了,不会再改变
- 再执行 resolve(4) -> 4
- 最后执行 setTimeout, 但是由于状态已经是 rejected,完成状态,所以不会再 resolve
输出结果: 2 -> yideng -> end -> 3 -> 4
10. 请写出如下值,并解释为什么?
var s = [];
var arr = s;
for (var i = 0; i < 3; i++) {
var pusher = {
value: 'item' + i
},
tmp;
if (i !== 2) {
tmp = [];
pusher.children = tmp;
}
arr.push(pusher);
arr = tmp;
}
console.log(s[0]);
题解
首先 s 和 arr 都指向[];
进入第一次循环 i=0 pusher 是{value:'item0'},此时 i=0 可以进入判断语句 tmp = [], pusher.children = [] -> pusher 变成{value:'item0',children:[]} 接着走到 arr.push 阶段 这时候的 arr 和 s 都指向一个地址 [], push 之后 arr 和 s 就变成了 [{value:'item0',children:[]}] 接着向下走,arr 重新赋值,等于了 tmp,此时 tmp 是[],因此 arr 和 tmp 都指向 children:[] ,-> 进入第二次循环
[
{
value: 'item0',
children: [
{
value: 'item1',
children: []
}
]
}
];
这个时候,arr 有一次等于 temp ,也就是 value:'item1'的 children:[]
- 进入第三次循环 i=2 pusher 是{value:'item2'} 这时候其实就和第二次类似了,但是 i=2 进入不了判断, 直接把 pusher push 进 arr
[
{
value: 'item0',
children: [
{
value: 'item1',
children: [
{
value: 'item2'
}
]
}
]
}
];
- 继续走,此时 i=3,进入不了循环了,就结束了 因此打印出的结果就是
{
value:'item0',
children:[{
value:'item1',
children:[{
value:'item2'
}]
}]
}
扩展 10-1
描述你理解的函数式编程,并写出如下值的输出结果
var Container = function(x) {
this.__value = x;
};
Container.of = x => new Container(x);
Container.prototype.map = function(f) {
return Container.of(f(this.__value));
};
Container.of(3)
.map(x => x + 1)
.map(x => 'Result is' + x);
// 结果是 Result is 4
// of 是静态方法,吐出去一个新的值 3
// .map 又把这个处理过的 3,变成了 4,又被吐出去了
// 继续.map 拿到这个 4,再处理这个 4 就变成 result is 4