This article was first published on my javascript-unknown/github

The Getify bigwig’s JavaScript You Don’t Know series of books basically lists the mistakes most front-end practitioners make. Read the last volume, picked four kinds of problems, see you JS basic skills solid.

  1. Js basic types
  2. This, this pointer
  3. The property, 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);
Copy the code
Answer

Output:

4;
Copy the code

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);
Copy the code
Answer

Output:

[1.3];
Copy the code

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));
Copy the code
Answer

Output:

Uncaught SyntaxError: Invalid or unexpected token
Copy the code

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);
Copy the code
Answer

Output:

false;
Copy the code

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.220446049250313 e-16
}
let a = 0.1 + 0.2;
let b = 0.3;
numbersCloseEnoughToEqual(a, b);
Copy the code


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

Output:

129 string
3 "number"
Copy the code

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 });
Copy the code
Answer

Output:

undefined
undefined
[1.null.null.4."The 2019-08-14 T01:52:25. 428 z"]
{"a":2."d":1}
Copy the code

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) {
    returni; }));console.log(
  b.map(function(v, i) {
    returni; }));console.log(
  c.map(function(v, i) {
    returni; }));console.log(
  d.map(function(v, i) {
    returni; }));Copy the code
Answer

Output:

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

[the empty...3] [empty x3]
[0.1.2]
[0.1.2]
Copy the code

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);
Copy the code
Answer

Output:

[2.2, { a: 4.b: 2}] [(2.2, { a: 4.b: 2 })][(1.2, { a: 4.b: 2 })];
Copy the code

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("-"));
Copy the code
Answer

Output:

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

--
--
Copy the code

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, "-"); / / "--"
Copy the code


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);
Copy the code
Answer

Output:

getA:  1
getA:  undefined(a timerId number)getA:  undefined
Copy the code

According to:

  • 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);
Copy the code
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();
Copy the code
Answer

Output:

undefined;
Copy the code

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));
Copy the code
Answer

Output:

boss1;
boss1;
boss1;
Copy the code

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));
Copy the code
Answer

Output:

Window;
Window;
Window;
Copy the code

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)());
Copy the code
Answer

Output:

2;
1;
1;
1;
Copy the code

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)());
Copy the code
Answer

Output:

2;
undefined;
undefined;
undefined;
Copy the code

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);
Copy the code


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);
}
Copy the code
Answer

Output:

0;
1;
2;
3;
4;
5;
b;
Copy the code

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);
}
Copy the code
Answer

Output:

2;
3;
4;
5;
6;
7;
Copy the code

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);
Copy the code
Answer

Output:

`{x: 1} {x: 1} false false`;
Copy the code

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);
Copy the code
Answer

Output:

key: c;
key: a;
["b"."c"];
["c"]
{c: 3}
"{"c": 3}"
false true
true true
Copy the code

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"));
Copy the code
Answer

Output:

false;
true;
Copy the code

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());
Copy the code
Answer

Output:

111 111 111
Copy the code

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();
Copy the code
Answer

Output:

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

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__);
Copy the code
Answer

Output:

F {}
{}
true
Copy the code

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";
};
Copy the code


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"));
Copy the code
Answer

Output:

Cat is eating fish
undefinedIs eating rice (Thank you @sunny Jun@Angelayun for correcting me, I mistakenly wrote "dog is eating rice")Copy the code

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.