本文首發於個人 javascript-unknown/githubjavascript
Getify大佬的《你不知道的JavaScript》系列叢書,基本上列出了大多數前端從業人員易犯的錯誤。讀完上卷,摘取了四類問題,看你js基本功是否紮實。前端
a = [1, 2, 3, 4];
delete a[1];
console.log(a.length);
複製代碼
Output:java
4;
複製代碼
Why?git
delete a[1]
, a becomes [1, empty, 3, 4]
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);
複製代碼
Output:github
[1, 3];
複製代碼
Why?markdown
delete 2 - cloneList[1]
, cloneList becomes [1, 3, 4]
delete 3 - cloneList[2]
, cloneList becomes [1, 3]
console.log(42.toFixed(3));
複製代碼
Output:app
Uncaught SyntaxError: Invalid or unexpected token
複製代碼
Why?dom
Within 42.toFixed(3)
, the .
will be regarded as a part of number, so (42.)toFixed(3)
throws error.oop
// Correct:ui
console.log(0.1 + 0.2 === 0.3);
複製代碼
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);
複製代碼
a = "12" + 9;
console.log(a, typeof a);
b = "12" - 9;
console.log(b, typeof b);
複製代碼
Output:
129 string
3 "number"
複製代碼
Why?
string + number
will transform number to string, outputs stringstring - number
will transform string to number, outputs numberRun 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 });
複製代碼
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
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;
})
);
複製代碼
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.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);
複製代碼
Output:
[2, 2, { a: 4, b: 2 }][(2, 2, { a: 4, b: 2 })][(1, 2, { a: 4, b: 2 })];
複製代碼
Why?
z = [...x]
is shallow copya = new Array(3);
b = [undefined, undefined, undefined];
console.log(a.join("-"));
console.log(b.join("-"));
複製代碼
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, "-"); // "--"
複製代碼
obj = {
a: 1,
getA() {
console.log("getA: ", this.a);
}
};
obj.getA();
x = obj.getA;
x();
setTimeout(obj.getA, 100);
複製代碼
Output:
getA: 1
getA: undefined
(a timerId number)
getA: undefined
複製代碼
Why:
Implicitly Lost
default binding
applies.default binding
makes this
the global(Window
) or undefined (depends on if this is strict mode
).Question:How to change x(); setTimeout(obj.getA, 100);
, make it output getA: 1
.
obj = {
a: 1,
getA: () => {
console.log("getA: ", this.a);
}
};
setTimeout(obj.getA.bind(obj), 100);
複製代碼
Output: getA: undefined
.
Arrow functions can never have their own this bound. Instead, they always delegate to the lexical scope (Window).
function foo() {
let a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();
複製代碼
Output:
undefined;
複製代碼
Why?
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));
複製代碼
Output:
boss1;
boss1;
boss1;
複製代碼
Why?
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));
複製代碼
Output:
Window;
Window;
Window;
複製代碼
Why?
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)());
複製代碼
Output:
2;
1;
1;
1;
複製代碼
Why?
this
to be global(window).// 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)());
複製代碼
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);
複製代碼
x = Symbol("x");
a = [2, 3, 4, 5, 6, 7];
a.b = 1;
a[x] = 0;
for (let key in a) {
console.log(key);
}
複製代碼
Output:
0;
1;
2;
3;
4;
5;
b;
複製代碼
Why?
for ... in
loop will iterates all enumerable, non-Symbol properties.x = Symbol("x");
a = [2, 3, 4, 5, 6, 7];
a.b = 1;
a[x] = 0;
for (let val of a) {
console.log(val);
}
複製代碼
Output:
2;
3;
4;
5;
6;
7;
複製代碼
Why?
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);
複製代碼
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
.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);
複製代碼
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__
a = { x: 2 };
b = Object.create(a);
console.log(b.hasOwnProperty("x"));
b.x++;
console.log(b.hasOwnProperty("x"));
複製代碼
Output:
false;
true;
複製代碼
Why?
b.x++
will run b.x = b.x + 1
, which will add own property x
for b
.__proto__
&& prototype
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());
複製代碼
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}
.z.__proto__
( === C.prototype
) has no myName
, so z.myName
will be z.__proto__.__proto__.myName
( === C.prototype.__proto__.myName
)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)
class C {
constructor() {
this.num = Math.random();
}
}
c1 = new C();
C.prototype.rand = function() {
console.log("Random: " + Math.round(this.num * 1000));
};
c1.rand();
複製代碼
Output:
Random: 890; // a random number between 0~1000
複製代碼
Why?
class
in js is made with [[Prototype]], so c1.__proto__
=== C.prototype
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__);
複製代碼
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__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";
};
複製代碼
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"));
複製代碼
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.