《你不知道的JavaScript》小题集

3,204 阅读5分钟

本文首发于我的 javascript-unknown/github

Getify大佬的《你不知道的JavaScript》系列丛书,基本上列出了大多数前端从业人员易犯的错误。读完上卷,摘取了四类问题,看你js基本功是否扎实。

  1. js basic types,js基础类型
  2. this,this指针
  3. property,属性
  4. proto && prototype,原型链

1. js basic types

1.1 What's the output?
a = [1, 2, 3, 4];
delete a[1];
console.log(a.length);
Answer

Output:

4;

Why?

  • After delete a[1], a becomes [1, empty, 3, 4]


1.2 What's the output?
let list = [1, 2, 3, 4];
let alreadyList = [2, 3];

let cloneList = [...list];

for (let i = 0; i < list.length - 1; i++) {
  let item = list[i];
  if (alreadyList.includes(item)) {
    cloneList.splice(i, 1);
  }
}

console.log("...", cloneList);
Answer

Output:

[1, 3];

Why?

  • After delete 2 - cloneList[1], cloneList becomes [1, 3, 4]
  • When delete 3 - cloneList[2], cloneList becomes [1, 3]


1.3 What's the output?
console.log(42.toFixed(3));
Answer

Output:

Uncaught SyntaxError: Invalid or unexpected token

Why?

Within 42.toFixed(3), the . will be regarded as a part of number, so (42.)toFixed(3) throws error.

// Correct:

  • (42).toFixed(3); // "42.000"
  • num = 42; num.toFixed(3); // "42.000"
  • 42..toFixed(3); // "42.000"


1.4 What's the output?
console.log(0.1 + 0.2 === 0.3);
Answer

Output:

false;

Why?

For lauguage following IEEE 754 rule such as javascript, 0.1 + 0.2 outputs 0.30000000000000004.

A safe way to comprare values:

function numbersCloseEnoughToEqual(n1, n2) {
  return Math.abs(n1 - n2) < Number.EPSILON; // Number.EPSILON: 2.220446049250313e-16
}
let a = 0.1 + 0.2;
let b = 0.3;
numbersCloseEnoughToEqual(a, b);


1.5 What's the output?
a = "12" + 9;
console.log(a, typeof a);
b = "12" - 9;
console.log(b, typeof b);
Answer

Output:

129 string
3 "number"

Why?

  • string + number will transform number to string, outputs string
  • string - number will transform string to number, outputs number


1.6 What's the output?

Run seperately:

JSON.stringify(undefined);

JSON.stringify(function() {});

JSON.stringify([1, undefined, function() {}, 4, new Date()]);

JSON.stringify({ a: 2, b: function() {}, c: Symbol.for("ccc"), d: 1 });
Answer

Output:

undefined
undefined
[1,null,null,4,"2019-08-14T01:52:25.428Z"]
{"a":2,"d":1}

Why?

JSON.stringify will ignore undefined, function, symbol


1.7 What's the output?
a = Array(3);
b = new Array(3);
c = Array.apply(null, { length: 3 });
d = [undefined, undefined, undefined];

console.log(
  a.map(function(v, i) {
    return i;
  })
);
console.log(
  b.map(function(v, i) {
    return i;
  })
);
console.log(
  c.map(function(v, i) {
    return i;
  })
);
console.log(
  d.map(function(v, i) {
    return i;
  })
);
Answer

Output:

Different browsers may behave differently, while within current Chrome, the output is:

[empty × 3]
[empty × 3]
[0, 1, 2]
[0, 1, 2]

Why?

  • Array(num) is as same as new Array(num), since the browser will auto add new in before of Array(num)
  • new Array(3) create a array, in which every member is empty unit (undefined type).
  • a.map(..) & b.map(..) will be failed, as the array is full of empty, map will not iterate them.


1.8 What's the output?
x = [1, 2, { a: 1 }];
y = x;
z = [...x];
y[0] = 2;
(y[2].b = 2), (z[2].a = 4);
console.log(x, y, z);
Answer

Output:

[2, 2, { a: 4, b: 2 }][(2, 2, { a: 4, b: 2 })][(1, 2, { a: 4, b: 2 })];

Why?

  • z = [...x] is shallow copy


1.9 What's the output?
a = new Array(3);
b = [undefined, undefined, undefined];

console.log(a.join("-"));
console.log(b.join("-"));
Answer

Output:

Different browsers may behave differently, while within current Chrome, the output is:

--
--

Why?

join works differently with map:

function fakeJoin(arr, connector) {
  var str = "";
  for (var i = 0; i < arr.length; i++) {
    if (i > 0) {
      str += connector;
    }
    if (arr[i] !== undefined) {
      str += arr[i];
    }
  }
  return str;
}
var a = new Array(3);
fakeJoin(a, "-"); // "--"


2. this

2.1 What's the output?
obj = {
  a: 1,
  getA() {
    console.log("getA: ", this.a);
  }
};
obj.getA();
x = obj.getA;
x();
setTimeout(obj.getA, 100);
Answer

Output:

getA:  1
getA:  undefined
(a timerId number)
getA:  undefined

Why:

  • It's Implicitly Lost
  • Even though getA appears to be a reference to obj.getA, in fact, it's really just another reference to getA itself. Moreover, the call-site is what matters, and the call-site is getA(), which is a plain, un-decorated call and thus the default binding applies.
  • default binding makes this the global(Window) or undefined (depends on if this is strict mode).

reference

Question:How to change x(); setTimeout(obj.getA, 100);, make it output getA: 1.


1.2 What's the output?
obj = {
  a: 1,
  getA: () => {
    console.log("getA: ", this.a);
  }
};
setTimeout(obj.getA.bind(obj), 100);
Answer

Output: getA: undefined.

Arrow functions can never have their own this bound. Instead, they always delegate to the lexical scope (Window).

reference


2.3 What's the output?
function foo() {
  let a = 2;
  this.bar();
}

function bar() {
  console.log(this.a);
}

foo();
Answer

Output:

undefined;

Why?

  • Every time you feel yourself trying to mix lexical scope look-ups with this, remind yourself: there is no bridge.

reference


2.4 What's the output?
let boss1 = { name: "boss1" };
let boss2 = { name: "boss2" };
let boss1returnThis = function() {
  return this.name;
}.bind(boss1);
console.log(boss1returnThis.bind(boss2)());
console.log(boss1returnThis.apply(boss2));
console.log(boss1returnThis.call(boss2));
Answer

Output:

boss1;
boss1;
boss1;

Why?

  • For binded this, it cannot be reassigned, even with .bind(), .apply() or .call()

reference


2.5 What's the output?
let boss1 = { name: "boss1" };
let boss2 = { name: "boss2" };
// Begin pay attention
let boss1returnThis = (() => {
  return this;
}).bind(boss1);
// End pay attention
console.log(boss1returnThis.bind(boss2)());
console.log(boss1returnThis.apply(boss2));
console.log(boss1returnThis.call(boss2));
Answer

Output:

Window;
Window;
Window;

Why?

  • Arrow functions can never have their own this bound. Instead, they always delegate to the lexical scope (Window).
  • For arrow functions, this can't be reassigned, even with .bind(), .apply() or .call()

reference


2.6 What's the output?
var value = 1;
var foo = {
  value: 2,
  bar: function() {
    return this.value;
  }
};

console.log(foo.bar());
console.log((foo.bar = foo.bar)());
console.log((false || foo.bar)());
console.log((foo.bar, foo.bar)());
Answer

Output:

2;
1;
1;
1;

Why?

  • Last 3 console.log do apply GetValue to the result of evaluating Expression.
  • GetValue(lref) changes this to be global(window).

reference


2.7 What's the output?
// Begin pay attention
let value = 1;
let foo = {
  value: 2,
  bar: function() {
    return this.value;
  }
};
// End pay attention
console.log(foo.bar());
console.log((foo.bar = foo.bar)());
console.log((false || foo.bar)());
console.log((foo.bar, foo.bar)());
Answer

Output:

2;
undefined;
undefined;
undefined;

Why?

  • let is not global while var is.

So the following code will output 1 undefined 2

let a = 1;
var b = 2;
console.log(a, window.a, window.b);


3. property

3.1 What's the output?
x = Symbol("x");
a = [2, 3, 4, 5, 6, 7];
a.b = 1;
a[x] = 0;
for (let key in a) {
  console.log(key);
}
Answer

Output:

0;
1;
2;
3;
4;
5;
b;

Why?

  • for ... in loop will iterates all enumerable, non-Symbol properties.


3.2 What's the output?
x = Symbol("x");
a = [2, 3, 4, 5, 6, 7];
a.b = 1;
a[x] = 0;
for (let val of a) {
  console.log(val);
}
Answer

Output:

2;
3;
4;
5;
6;
7;

Why?

  • The for...in statement iterates over the enumerable, non-Symbol properties of an object, in an arbitrary order.
  • The for...of statement iterates over values that the iterable object defines to be iterated over.

reference


3.3 What's the output?
class A {
  x = 1;
  getX() {
    return this.x;
  }
}
a = new A();
b = Object.assign({}, a);
c = { ...a };
console.log(b, c, "getX" in b, "getX" in c);
Answer

Output:

`{x: 1} {x: 1} false false`;

Why?

  • Object.assign & ... & ...in... only iterates enumerable, non-Symbol properties of the given object directly, excluding the properties of x.__proto__, getter and setter.


3.4 What's the output?
obj = { a: 1 };
x = Object.create(obj);
Object.defineProperty(x, "b", {
  value: 2,
  enumerable: false
});
x.c = 3;
for (let k in x) {
  console.log("key: " + k);
}
console.log(Object.getOwnPropertyNames(x));
console.log(Object.keys(x));
console.log(Object.assign({}, x));
JSON.stringify(x);
console.log(x.hasOwnProperty("a"), x.hasOwnProperty("c"));
console.log("a" in x, "c" in x);
Answer

Output:

key: c;
key: a;
["b", "c"];
["c"]
{c: 3}
"{"c":3}"
false true
true true

Why?

  • x = Object.create(obj) creates a new object, using the existing object obj as the prototype of the newly created object x.

Remember the keywords:

  • for...in: excluding non-enumerable, including __proto__
  • Object.getOwnPropertyNames & hasOwnProperty: including non-enumerable, excluding __proto__
  • Object.keys & Object.assign & JSON.stringify: excluding non-enumerable & __proto__
  • ... in ...: including non-enumerable & __proto__


3.5 What's the output?
a = { x: 2 };
b = Object.create(a);
console.log(b.hasOwnProperty("x"));
b.x++;
console.log(b.hasOwnProperty("x"));
Answer

Output:

false;
true;

Why?

  • Object.create creates a new object, using the existing object as the prototype of the newly created object.
  • b.x++ will run b.x = b.x + 1, which will add own property x for b.


4. __proto__ && prototype

4.1 What's the output?
function A(name) {
  this.name = name;
}
A.prototype.myName = function() {
  return this.name;
};

function B(name, label) {
  A.call(this, name);
  this.label = label;
}

function C(name, label) {
  A.call(this, name);
  this.label = label;
}

B.prototype = A.prototype;
C.prototype = new A();
B.prototype.myName = function() {
  return 111;
};

x = new A("xxx");
y = new B("yyy");
z = new C("zzz");
console.log(x.myName(), y.myName(), z.myName());
Answer

Output:

111 111 111

Why?

  • B.prototype = A.prototype is assign the reference of object A.prototype to B.prototype, so B.prototype.myName=.... changes A.prototype.myName.
  • new A() returns {name: undefined}, C.prototype = new A() means C.prototype = {name: undefined}.
  • Since z.__proto__( === C.prototype) has no myName, so z.myName will be z.__proto__.__proto__.myName( === C.prototype.__proto__.myName)
  • Since C.prototype.__proto__ === A.prototype, so C.prototype.__proto__.myName will be A.prototype.myName, which has changed by B.prototype.myName=.....

So how to make A.prototype.myName unchanged when setting B.prototype.myName=....? Fix B.prototype = A.prototype by B.prototype = Object.create(A.prototype)


4.2 What's the output?
class C {
  constructor() {
    this.num = Math.random();
  }
}
c1 = new C();
C.prototype.rand = function() {
  console.log("Random: " + Math.round(this.num * 1000));
};
c1.rand();
Answer

Output:

Random: 890; // a random number between 0~1000

Why?

  • class in js is made with [[Prototype]], so c1.__proto__ === C.prototype


4.3 What's the output?
function Test(oo) {
  function F() {}
  F.prototype = oo;
  return new F();
}
o = {
  x: 1,
  getX: function() {
    return 111;
  }
};
p = Test(o);
q = Object.create(o);

console.log(p);
console.log(q);
console.log(p.__proto__ === q.__proto__);
Answer

Output:

F {}
{}
true

Why?

  • p.__proto__ equals (new F()).__proto__ equals F.prototype equals o
  • q = Object.create(o) makes q.__proto__ equals o
  • Test is polyfill of Object.create for browsers which doesn't support es5. reference
  • So, don't mock class by setting __proto__ / prototype / new, just use Object.create:
let Widget = {
  init: function(width, height) {
    this.width = width || 50;
  }
};
let Button = Object.create(Widget);
Button.setup = function(width, height, label) {
  this.init(width, height);
  this.label = label || "Default";
};


4.4 What's the output?
function Animal(name) {
  this.name = name || "Animal";
}
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.name = "Cat";

function Dog() {}
Dog.prototype = Object.create(Animal.prototype);

cat = new Cat();
dog = new Dog();
Animal.prototype.eat = function(food) {
  console.log(this.name + " is eating " + food);
};
console.log(cat.eat("fish"));
console.log(dog.eat("rice"));
Answer

Output:

Cat is eating fish
undefined is eating rice (感谢评论区@晴天君 @angelayun 指正,之前误写成了“dog is eating rice”)

Why?

  • cat.__proto__.__proto__ equals (Cat.prototype).__proto__ equals Animal.prototype
  • cat.eat('fish') will callcat.__proto__.__proto__.eat('fish')
  • dog.__proto__ equals Dog.prototype equals Animal.prototype
  • dog.eat("rice") will calldog.__proto__.eat('rice')

It means that properties of Animal.prototype will be shared by all instances, including those inherited earlier.