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]' 连接

44.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 打印的内容差距很大是什么原因?

题解

  1. console.log(Person.a); -> 'a1'
  2. console.log(yideng.a); -> 'a'
  3. console.log(1..a); -> 'a', 1. 是浮点对象,所以拿的是 Object 原型上的 a
  4. console.log(1.a); -> 报错, 1. 是浮点对象,需要写成(1).a

4-1 (20, 30).x -> undefined 括号里面的东西,在加载时会提现执行

  1. console.log(yideng.proto.proto.constructor.constructor.constructor); -> ƒ Function(){ [native code] }
  • 一般 constructor 调用多次,就是循环引用了
  • 任何proto都是{}
  • {}代表对象,对象的构造函数是 Object,都叫构造“函数”了,构造 Object 函数肯定就是 Function 了
  • Function 本身也是函数,构造函数自然也是 Function
  • Object.prototype 是个对象
  • Function.prototype 是个函数
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));

题解

  1. console.log(a[b]); -> 456
  • a[b], b 作为 key,是一个对象,所以会先 valueOf,再 toString,变成[object Object],输出 a 为 {[object Object]: 123};
  • a[c] 覆盖了 a[b]
  1. 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);

题解

  1. 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 温馨提示

  1. async 就是 yield 的语法糖;
  2. 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');

题解

  1. 先执行同步 2 - > yideng
  2. 添加了 3 个异步,其中 2 个微任务,1 个宏任务 到事件队列中
  3. 同步还没执行完,继续执行同步 end
  4. 同步任务结束,执行异步事件队列
  5. 先执行推进去的微任务,resolve(3) -> 输出 3, 此时 promise 的状态已变成 rejected,完成了,不会再改变
  6. 再执行 resolve(4) -> 4
  7. 最后执行 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]);

题解

  1. 首先 s 和 arr 都指向[];

  2. 进入第一次循环 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:[]

  1. 进入第三次循环 i=2 pusher 是{value:'item2'} 这时候其实就和第二次类似了,但是 i=2 进入不了判断, 直接把 pusher push 进 arr
[
  {
    value: 'item0',
    children: [
      {
        value: 'item1',
        children: [
          {
            value: 'item2'
          }
        ]
      }
    ]
  }
];
  1. 继续走,此时 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
Last Updated:
Contributors: kk