# 问答

# 箭头函数能当构造函数吗?

箭头函数常用于回调函数中,包括事件处理器或定时器

没有 this、没有 super,没有 arguments,没有 new.target
不能通过 new 关键字调用

一个函数内部有两个方法:[[Call]][[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上

  • 当直接调用时,执行 [[Call]] 方法,直接执行函数体

  • 箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。

# URL编码

为什么要URL编码

我们知道使用的URL传参格式为: ?name1=value1&name2=value2,这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出参数值

但是如果我的参数名或才参数恰好包含了 & = 等这样的特殊字符怎么办?比如 name1=va&lu=e1,这里我想传的 name1 = va&lu=e1 的值,那么实际在传输过程中就会变成这样 name1=va&lu=e1

为了解决这个问题就需要对参数进行URL编码

URL编码只是简单的在特殊字符的各个字节前加上 %,例如对上述的字符进行URL编码后结果:“name1=va%26lu%3D”,这样服务端会把紧跟在 % 后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符。

# decodeURI与decodeURIComponent区别

encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;

encodeURIComponent() 则会对它发现的任何非标准字符进行编码

let uri="http://es.cc/search?title=哇哈哈&tag=默认分类";
console.log(encodeURI (uri));
// http://es.cc/search?title=%E5%93%87%E5%93%88%E5%93%88&tag=%E9%BB%98%E8%AE%A4%E5%88%86%E7%B1%BB
console.log(encodeURIComponent (uri));
// http%3A%2F%2Fes.cc%2Fsearch%3Ftitle%3D%E5%93%87%E5%93%88%E5%93%88%26tag%3D%E9%BB%98%E8%AE%A4%E5%88%86%E7%B1%BB

# forEach和map的区别

  • forEach: 更改原始数组的元素

  • map: 会分配内存空间存储新数组并返回,forEach() 不会返回数据

var a = [1,3]
a.map(item => item = item+1) // [2,4]
console.log(a) // [1,3]

# for, foreach,map的性能

for > forEach > map

  • for 循环是最简单的,因为它没有任何额外的函数调用栈和上下文

  • forEach 其次,因为它其实比我们想象得要复杂一些,它的函数签名实际上是

    array.forEach(function(currentValue, index, arr), thisValue)

    它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能

  • map 最慢,因为它的返回值是一个等长的全新的数组,数组创建和赋值产生的性能开销很大

# for...of可以用在对象中么

答案是不行

for...of 语句只用在可迭代对象,即实现了[Symbol.iterator]方法的对象,比如ArrayMapSetStringTypedArrayarguments对象等等)

现可迭代协议的对象:

var iterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return { value: this.i++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (var value of iterable) {
  console.log(value);
}

# indexof和includes

  • indexof:

    • 可返回某个指定的字符串值在字符串中首次出现的位置。是ES5的方法,也可以对字符串使用

    • 对大小写敏感。

    • 它内部使用相等运算符(===)进行判断,这会导致对 NaN 的误判

  • includes:

    • 检查是否数组包含某些元素,返回 truefalse,是ES6的方法,也可以对字符串使用
[1, NaN].includes(NaN) // true

[1, NaN].indexOf(NaN) // -1

indexofincludes的性能?

# ES6新增数组遍历方法

  • some()

  • findIndex()

  • find()

  • filter()

  • map()

  • every()

  • forEach() ?

  • reduce() ?

# 柯里化

Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一 个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数, 在某些编程语言中(如 Haskell),是通过 Currying 技术支持多参函数这一语言特性的。

如下例子:

function add(x, y) {
  return x+y
}
// 柯里化函数
function curringAdd(x){
  return function(y) {
     return x+y 
  }
}
function addd(x, y, z) {
  return x+y+z
}
// 柯里化函数
function curringAddd(x){
    return function(y) {
      return function (z) {
        return x+y+z
      }
    }
}

curriedAdd 进行抽象,可能会得到如下函数 currying

function currying(fn, ...arg1) {
    if(arg1.length >= fn.length){
        return fn(...arg1)
    }
    return (...arg2) => {
        return currying(fn, ...arg1, ...arg2)
    }
}
// 对add进行转换
currying(add, 1)(2)
// 对addd进行转换
currying(add, 1)(2)(3)
// 或者
currying(add, 1)(2, 3)

# currying的使用场景

参数复用

固定不变的参数,实现参数复用是 Currying 的主要用途之一,如下:

const base = currying(add, 10)
// 复用第一个参数10
base(10) // 20
base(15) // 25

# 为什么0.1 + 0.2 != 0.3,请详述理由

因为JS使用的浮点运行标准是IEEE754(64位),并且只要采用IEEE754的语言都有该问题

# 原因

计算机在计算的时候是没办法直接按十进制计算的。所以计算机在计算的时候分成的两个部分

  • 选择按照IEEE754转成相应的二进制

  • 对阶运算后,再转回十进制

1.进制转换

0.1的二进制表示为0.0001100110011001100110011001100110011001100110011001101(无限循环)

0.2的二进制表示为0.001100110011001100110011001100110011001100110011001101(无限循环)

但是由于IEEE754尾数位限制,需要将后面多余的位截掉,那么尾数部分就会发生进位,所以精度就发生了损失

2.对阶运算

对阶运算这部分也可能产生精度损加上之前转二进制时精度就发生了变化,所以对阶运算后结果得到的结果是

0.0100110011001100110011001100110011001100110011001100

这个二进制转换成十进制之后就是0.30000000000000004

# 怎么解决精度问题

方式一:使用toFixed()方法截取位数

parserFloat(0.1+0.2).toFixed(2) // 0.30

方式二:将计算元素都转换成整数,得到结果再除以相应的位数

0.1+0.2 => (1+2)/10

其它方式:如果精度要求高,那就使用第三个库Math.js (opens new window)big.js (opens new window)

# 其它思考

为什么x=0.1能等到0.1

标准中规定尾数f的固定长度是52位,再加上省略的一位,这53位是JS精度范围。它最大可以表示2^53(9007199254740992), 长度是 16, 所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理

(0.1).toPrecision(16) // 0.1000000000000000

(0.1).toPrecision(21) // 0.100000000000000005551

js最大安全数是 Number.MAX_SAFE_INTEGER == Math.pow(2,53) - 1, 而不是Math.pow(2,52) - 1, why?

这是因为二进制表示有效数字总是1.xx…xx的形式,尾数部分f在规约形式下第一位默认为1(省略不写,xx..xx为尾数部分f,最长52位)。 因此,JavaScript提供的有效数字最长为53个二进制位(64位浮点的后52位+被省略的1位)

# 总结

精度损失可能出现在进制转化和对阶运算过程中,只要这两步产生了精度损失,计算结果就会出现偏差。

平时工作如果有涉及到浮点数的计算时,一定要注意一下了呐

# super的作用

super表示父类的构造函数,用来新建父类的this对象.

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的 this 对象必须先通过父类的构造函数完成塑造, 得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象

# 如何在 ES5 环境下实现一个const

由于ES5环境没有block的概念,所以是无法百分百实现const,只能是挂载到某个对象下,要么是全局的window,要么就是自定义一个object来当容器

  var __const = function __const (data, value) {
    window.data = value // 把要定义的data挂载到window下,并赋值value
    Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
      enumerable: false,
      configurable: false,
      get: function () {
        return value
      },
      set: function (data) {
        if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
          throw new TypeError('Assignment to constant variable.')
        } else {
          return value
        }
      }
    })
  }
  __const('a', 10)
  console.log(a)
  delete a
  console.log(a)
  for (let item in window) { // 因为const定义的属性在global下也是不存在的,所以用到了enumerable: false来模拟这一功能
    if (item === 'a') { // 因为不可枚举,所以不执行
      console.log(window[item])
    }
  }
  a = 20 // 报错

# 图片base64压缩后变大还是变小,如何提高性能的

BASE64 搞出来的图片通常尺寸会大上 30% 左右

优化的话:

  • 小图片才用base64

  • 如果 base64 是被编码到 css/js 中,是可以缓存的,因为 css/js 文件可以缓存

# 判断输出

Q

function Foo(){}
Foo.prototype.constructor === Foo // true

Q

if (!("a" in window)) {
    var a = 1;
}
alert(a) // undefined

注意点:

  • 所有的变量声明都在范围作用域的顶部

    alert("a" in window);
    var a
    // 等价于
    var a
    alert("a" in window);
    
  • 变量声明被提前了,但变量赋值没有,因为这行代码包括了变量声明和变量赋值,进入执行上下文会声明这些变量(除了函数声明),运行执行上下文才会对变量赋值

  • 所有的全局变量都是 window 的属性,语句var a = 1 ;等价于 window.a = 1

Q

var a = 1,
    b = function a(x) {
        x && a(--x);
    };
alert(a) // 1

题目注意点:

  • 变量提升中,函数声明会覆盖同名的变量

  • 变量提升中,声明及函数表达式不会覆盖之前的同名变量

  • 命名表达式的名字只在新定义的函数作用域内有效,因为规范规定了标示符不能在外围的作用域内有效

题目中的函数表达式 a 不存在于全局上下文的,也就不会覆盖之前 var a ,代码等价于下面的代码:

var a = 1,
    b = function(x) {
        x && b(--x);
    };
alert(a)

Q

function value(){
    return 1;
}
var value;
alert(typeof value); //"function"

注意点:

  • 变量提升中,函数声明会覆盖,同名的变量

  • 变量提升中,声明及函数表达式不会覆盖之前的同名变量

function value(){
    return 1;
}
var value = 1;
alert(typeof value);    //"number"

注意点:

  • 变量提升中,函数声明会覆盖,同名的变量

  • 变量提升中,声明及函数表达式不会覆盖之前的同名变量

当进入变量赋值阶段 value 被生新定义,上面代码等价于:

function value(){
    return 1;
}
value = 1;

下面这个代码运行结果是一样的

var value = 1;
function value(){
    return 1;
}
alert(typeof value) // //"number"

Q

function a(x) {
    return x * 2;
}
var a;
alert(a); // function a ()

注意点:

  • 变量声明不会影响同名的变量

Q

function b(x, y, a) {
    arguments[2] = 10;
    alert(a); // 10
}
b(1, 2, 3)

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性的值是Arguments 对象:

AO = {
  arguments: <ArgO>
}

Arguments 对象是活动对象的一个属性,它包括如下属性:

  • callee — 指向当前函数的引用

  • length — 真正传递的参数个数

  • properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列).properties-indexes 内部元素的个数等于 arguments.length . properties-indexes 的值和实际传递进来的参数之间是共享的

这个共享其实不是真正的共享一个内存地址,而是2个不同的内存地址,使用 JavaScript 引擎来保证2个值是随时一样的,当然这也有一个前提,那就是这个索引值要小于你传入的参数个数,也就是说如果你只传入2个参数,而还继续使用 arguments[2] 赋值的话,就会不一致,例如:

function b(x, y, a) {
    arguments[2] = 10;
    alert(a); // undefined
}
b(1, 2)

此时 arguments.length 其实是 2 个, arguments[2] 与参数 a 不是一个东西,所以赋值 10 以后, alert(a) 的结果依然是 undefined ,而不是 10 , 但如下代码弹出的结果依然是 10,因为和 a 没有关系

function b(x, y, a) {
    arguments[2] = 10;
    alert(arguments[2]); // 10
}
b(1, 2)

Q

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

输出

ƒ b(){
  b = 20;
  console.log(b);
}

说明:

  • 立即调用函数表达式(IIFE)是函数表达式,而不是函数声明

  • 函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定

扩展

var b = 10;
(function b() {
    window.b = 20; // 
    console.log(b); //  [Function b]
    console.log(window.b); // 20
})();

Q

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] length 为 4

解释:Array.prototype.push 将根据 length 将元素填充到对应位置并修改 length 属性 +1,所以输出的结果就是上述结果。

Q

function a() {
    alert(this); // [object Window]
}
a.call(null)

如果 call 第一个参数传入的对象调用者是 null 或者 undefined 的话,call 方法将把全局对象(也就是 window )作为 this 的值。所以,不管你什么时候传入 null ,其 this 都是全局对象 window ,所以该题目可以理解成如下代码:

function a() {
    alert(this);
}
a.call(window);

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

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	conso.log(1);
}
var a = {
  i: 1,
  toString() {
    return a.i++;
  }
}

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

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

var a = {n: 1};
var b = a;
a.x = a = {n:2} ;

console.log(a.x) // undefined
console.log(b.x) // {n: 2}
  • a和b同时引用了{n:1}对象

  • 接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x, 相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}

  • 之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象

自声明函数

一般是在函数内部,重写同名函数代码,比如:

var scareMe = function () {
    alert("Boo!");
    scareMe = function () {
        alert("Double boo!");
    };
};

运行结果:

// 1. 添加新属性
scareMe.property = "properly";
// 2. scareMe赋与一个新值
var prank = scareMe;
// 3. 作为一个方法调用
var spooky = {
    boo: scareMe
};
// 使用新变量名称进行调用
prank(); // "Boo!"
prank(); // "Boo!"
console.log(prank.property); // "properly"
// 使用方法进行调用
spooky.boo(); // "Boo!"
spooky.boo(); // "Boo!"
console.log(spooky.boo.property); // "properly"


scareMe(); // Double boo!
scareMe(); // Double boo!
console.log(scareMe.property); // undefined

Q

判断输出

var a = 10;
(function () {
    console.log(a) // undefine
    a = 5
    console.log(window.a) // 10
    var a = 20;
    console.log(a) // 20
})()

A

这个要注意自执行函数的这行代码var a = 20

因为这里又声明一个a,导致这个变量提升,所在当前块级作用域中且在var a=20之前访问的a将为undefine

如果没有var a = 20,那么将访问全局的变量window.a

var a = 10;
(function () {
    console.log(a) // 10
    a = 5
    console.log(window.a) // 5
    a = 20;
    console.log(a) // 20
})()
[1 < 2 < 3, 3 < 2 < 1]

解析:这个题会让人误以为是 2 > 1 && 2 < 3 其实不是的.

这个题等价于

 1 < 2 => true;
 true < 3 =>  1 < 3 => true;
 3 < 2 => false;
 false < 1 => 0 < 1 => true;

答案是 [true, true]

Q

2 == [[[2]]]

解析: [[[2]]] 应该会不断得 toString(), [[[2]]] => '2' '2'==2

答案 true

Q

var a = new Date("2014-03-19"),
    b = new Date(2014, 03, 19);
[a.getDay() === b.getDay(), a.getMonth() === b.getMonth()]

解析:这种题目只想弃之

a.getDay() //3
b.getDay() //6
a.getMonth() //2
b.getMonth() //3

答案 [false, false]

Q

3.toString() // 报错
3..toString() // '3'
3...toString() // 报错

解析:

  1. 因为当原始数据类型(boolean,Number、String)在调用方法时,JS 将会创建对象,以便调用方法属性,而在使用完毕后将会销毁该对象
  • 3.toString():这是因为javascript引擎在解释代码 1.toString() 时认为 . 是浮点符号,此时但因小数点后面的字符是非法的,所以报语法错误

  • 3..toString(): javascript引擎认为第一个 . 是表示小数点,(JS 中 1..1 这样形式是合法),之后第二个 . 被解析为属性访问语法,所以都能正确解释执行

  • 3...toString(): 继上面之后属性访问语法访问的是一个 .,所以报语法错误

Q

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

解析:

  • var xx 是函数作用域

  • y: 没有 var,那么将变成 window.y

答案是 1, error

Q

var a = /123/,
    b = /123/;
a == b
a === b

解析:正则是引用类型

typeof a // object

答案 false, false

Q

var a = [1, 2, 3],
    b = [1, 2, 3],
    c = [1, 2, 4]
a == b
a === b
a > c
a < c

解析:前面两个是引类型的判断,不会相等,后面两个在比较大小的时候会 toString() 后按照字典序比较

[1, 2, 3].toString() === '1,2,3'
[1, 2, 4].toString() === '1,2,4'
'1,2,3' < '1,2,4' // true

答案 false, false, false, true

Q

var a = {}, b = Object.prototype;
[a.prototype === b, Object.getPrototypeOf(a) === b]

解析:

  1. 只有函数才拥有 prototype 的属性. 所以 a.prototypeundefined

  2. Object.getPrototypeOf(obj) 相当于 obj.__proto__

答案 false, true

Q

function foo() { }
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name]

解析: name 是函数本来就有的属性,且名字不可变.

答案 ['foo', 'foo']

Q

"1 2 3".replace(/\d/g, parseInt)

str.replace(regexp|substr, newSubStr|function), 如果 replace 函数传入的第二个参数是函数, 那么这个函数将接受如下参数

  • match: 首先是匹配的字符串

  • p1, p2 ....: 正则的分组

  • offset: match 匹配的 index

  • string: 整个字符串

由于题目中的正则没有分组, 所以等价于问

parseInt 函数可解析一个字符串,并返回一个整数。第二个参数表示要解析的数字的基数。该值介于 2 ~ 36 之间,具体表示规则如下:

  • 如果省略该参数或其值为 0,则数字将以 10 为基础来解析

  • 如果它以 “0x” 或 “0X” 开头,将以 16 为基数。

  • 如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN

parseInt('1', 0)
parseInt('2', 2)
parseInt('3', 4)

答案: 1, NaN, 3

Q

var lowerCaseOnly =  /^[a-z]+$/;
[lowerCaseOnly.test(null), lowerCaseOnly.test()]

解析:这里 test 函数会将参数转为字符串 null=>'nul'undefined=>'undefined' 自然都是小写了

答案: true, true

Q

[,,,].join(", ")

答案: ", , "

Q

var a = Function.length,
    b = new Function().length
a === b

解析:这题有点陷阱

函数的 length 返回该函数接收参数的个数,注意上面的 Function 是一个构造函数,默认是有定义接收一个参数的,所以 Function.length=1

而下面 new Function() 返回的一个新的函数是没有定义接口参数的所以 new Function().length = 0

答案 false

Q

var a = Date(0);
var b = new Date(0);
var c = new Date();
[a === b, b === c, a === c]
Date(0) // "Thu Feb 25 2021 16:05:15 GMT+0800 (中国标准时间)"
new Date(0) // Thu Jan 01 1970 08:00:00 GMT+0800 (中国标准时间)
new Date() // Thu Feb 25 2021 16:04:49 GMT+0800 (中国标准时间)

关于 Date 的题, 需要注意的是

  • 如果不传参数等价于当前时间.

  • 如果是函数调用返回一个字符串.

答案 false, false, false

Q

var min = Math.min(), max = Math.max()
min < max

解析:Math.min 不传参数返回 Infinity, Math.max 不传参数返回 -Infinity

答案: false

Q

Number.MIN_VALUE > 0 // true

解析: MIN_VALUE 属性是 JavaScript 中可表示的最小的数(接近 0 ,但不是负数),它的近似值为 5 x 10-324

Q

var x = [].reverse;
x();

解析:

  • reverse 赋值给 x

  • 在全局环境调用 x,相当于执行 window.reverse

答案 报错

Q

["1", "2", "3"].map(parseInt)

解析:

map的用法:

var new_array = arr.map(function callback(currentValue[, index[, array]]) { 
// Return element for new_array 
}[, thisArg])

这个 callback 一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引

parseInt的用法:

parseInt() 函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。

const intValue = parseInt(string[, radix]);
  • string:要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。

  • radix :一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。默认为10。 返回值 返回一个整数或NaN

parseInt(100); // 100
parseInt(100, 10); // 100
parseInt(100, 2); // 4 -> converts 100 in base 2 to base 10

注意:

radixundefined,或者radix为 0 或者没有指定的情况下,JavaScript 作如下处理:

  • 如果字符串 string 以"0x"或者"0X"开头, 则基数是16 (16进制).

  • 如果字符串 string 以"0"开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。

  • 如果字符串 string 以其它任何值开头,则基数是10 (十进制)。

回到题目

['1', '2', '3'].map(parseInt)

等价于:

['1', '2', '3'].map((item, index) => {
	return parseInt(item, index)
})

即返回的值分别为:

  • parseInt('1', 0) ,radix为0,默认为10,所以返回1

  • parseInt('2', 1),radix为1, 不在2到36之间,所以返回NaN

  • parseInt('3', 2) ,radix为2,将'3'作为二进制处理,逢2进1,可'3'不是二进制表示(已经超过最大值1了),所以返回NaN

变种['10','10','10','10','10'].map(parseInt)

分析如一:

  • parseInt('10', 0) ,radix为0,默认为10,所以返回10

  • parseInt('10', 1) ,radix为1, 不在2到36之间,所以返回NaN

  • parseInt('10', 2) ,radix为2,将'10'作为二进制处理,逢2进1,返回2

  • parseInt('10', 3) ,radix为3,逢3进1,所以返回3

  • parseInt('10', 4) ,radix为4,逢4进1,所以返回4

如果想要正常得循环字符串数组,怎么办?

['10','10','10','10','10'].map(Number);
// [10, 10, 10, 10, 10]

Q

[typeof null, null instanceof Object]

解析:

  • instanceof 只能用于对象, null 是基本类型

  • 作为基本类型的 nulltypeof null=object 这点确实让人很困惑,不过这是历史设计如此,硬记~

所以答案 [object, false]

Q

var val = 'smtg';
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');

解析: + 的优先级大于 ?

所以原题等价于 'Value is true' ? 'Somthing' : 'Nonthing' 而不是 'Value is' + (true ? 'Something' : 'Nonthing')

答案: Something

Q

var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

解析:考变量提升,代码等价于:

var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
        name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

所以答案是 'Goodbye Jack'

Q

var ary = [0,1,2];
ary[10] = 10;
ary.filter(function(x) { return x === undefined;});

解析:使用 filter 迭代这个数组的时候, 首先检查了这个索引值是不是数组的一个属性,也就是说 从 3 - 9 的这些索引并不存在与数组中. 在 array 的函数调用的时候是会跳过这些不存的索引

答案是 []

Q

function showCase(value) {
    switch(value) {
    case 'A':
        console.log('Case A');
        break;
    case 'B':
        console.log('Case B');
        break;
    case undefined:
        console.log('undefined');
        break;
    default:
        console.log('Do not know!');
    }
}
showCase(new String('A'));

解析: switch 是严格比较, String 实例字符串 不一样

答案是 'Do not know!'

Q

function showCase2(value) {
    switch(value) {
    case 'A':
        console.log('Case A');
        break;
    case 'B':
        console.log('Case B');
        break;
    case undefined:
        console.log('undefined');
        break;
    default:
        console.log('Do not know!');
    }
}
showCase2(String('A'));

解析: String 作为普通函数调用直接调用返回一个字符串

答案 'Case A'

Q

Array.isArray( Array.prototype )

解析:一个鲜为人知的实事: Array.prototype => []

答案: true

Q

function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a,b,c) {
  c = 10
  sidEffecting(arguments);
  return a + b + c;
}
bar(1,1,1)

arguments 是一个 object, c 就是 arguments[2], 所以对于 c 的修改就是对 arguments[2] 的修改

所以答案是 21

function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a,b,c=3) {
  c = 10
  sidEffecting(arguments);
  return a + b + c;
}
bar(1,1,1)

当给参数添加默认值后, arguments 和形参将脱离

答案是: 12

Q

判断输出:

document.body.addEventListener('click', () => {
    Promise.resolve().then(() => console.log('a'))
    console.log('f')
} )
document.body.addEventListener('click', () => {
    Promise.resolve().then(() => console.log('a1'))
    console.log('f1')
})

上面输出:

f
a
f1
a1

解释应该是用户点击事件是异步的且回调是宏任务

第一个注册事件触发后将执行 console.log('f'),然后清理微任务队列中的 console.log('a')), 再执行另一个宏任务事件,执行 console.log('f1'), 然后再清理这里的微任务队列中的 console.log('a1')

等价于以下代码:

setTimeout(() => {
  Promise.resolve().then(() => console.log('a'))
  console.log('f')
})
setTimeout(() => {
  Promise.resolve().then(() => console.log('a1'))
  console.log('f1')
})

如果执行以下代码呢?

document.body.click()

输出结果:

f
f1
a
a1

因为 document.body.click() 是以同步的方式执行的,所以两个回调也是以同步的形式执行。脑阔疼~

为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?

  1. 没有跨域问题,一般这种上报数据,代码要写通用的;(排除ajax)

  2. 不会阻塞页面加载,影响用户的体验,只要new Image对象就好了;(排除JS/CSS文件资源方式上报)

  3. 在所有图片中,体积最小;(比较PNG/JPG)

Q: 使用base64图片的弊端是什么

  • 根据 base64 的编码原理,大小比原文件大小大 1/3

  • 尽管图片请求少了,但是 HTML 文件本身尺寸会变大,会影响首屏加载,所以要权衡

  • 代码看起来会有点丑,大量编码字符(当然也可以通过构建工具动态插入)

  • base64 无法缓存,要缓存只能缓存包含 base64 的文件,比如 HTML 或者 CSS,这相比直接缓存图片要弱很多,一般 HTM 会改动频繁,所以等同于得不到缓存效益

Q:移动端 300 ms 点击(click 事件)延迟

由于移动端会有双击缩放的这个操作,因此浏览器在 click 之后要等待 300ms,判断这次操作是不是双击。

解决方案:

  • 禁用缩放:user-scalable=no

  • 更改默认的视口宽度

  • CSS touch-action

Q:点击穿透问题

因为 click 事件的 300ms 延迟问题,所以有可能会在某些情况触发多次事件。

解决方案:

  • 只用 touch

  • 只用 click

Q:click 与tap比较

clicktap 都会出发点击事件,但是在手机web端会有点区别: click会有200-300ms延迟,所以一般用tap(轻击)代替click作为点击事件。singleTap 和 doubleTap分别代表单击和双击