Primitive values vs. Objects
Primitive values 是指:undefined, null, boolean, number, bigint, string, symbol。除此之外的都是 Objects。
- Primitive values 是通过值传递的,两个 Primitive 比较的时候,是比较它们两个的值。
- Objects 是通过引用传递,两个 Objects 比较的时候,是比较它们两个的引用。
const a = {}; // fresh empty object // Pass the identity in `a` to `b`: const b = a; // Now `a` and `b` point to the same object // (they “share” that object): console.log(a === b); // true // Changing `a` also changes `b`: a.foo = 123; console.log(b.foo) // 123; const obj = {}; // fresh empty object console.log(obj === obj); // true // same reference console.log({} === {}); //false // different references, same content
- Primitive 的属性是不可以改变的
let str = 'abc'; str.length = 1; // TypeError: cannot assign to read only property 'length'
- Objects 属性是可以改变的
typeof vs. instanceof
typeof 对于原始值类型来说,除了 null 都可以显示正确的类型:
typeof 42; // "number"
typeof "abc"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" -- oops, JS bug!
typeof { "a": 1 }; // "object"
typeof [1,2,3]; // "object"
typeof function Hello(){}; // "function"
typeof Symbol; // "symbol"
typeof 对于对象来说,除了函数,都会显示成 object:
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
如果我们想要判断一个正确的对象类型,就需要用 instanceof,它的内部机制是通过原型链来判断的:
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
typeof 是用来判断 primitive values;instanceof 是用来判断 objects
instanceof 的原理
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
我们也可以试着实现一下 instanceof
function myInstanceof(left, right) {
let prototype = right.prototype
left = left.__proto__
while (true) {
if (left === null || left === undefined)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
以下是对实现的分析:
- 首先获取类型的原型
- 然后获得对象的原型
- 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
== vs. ===
对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换,这也就用到了我们上一章节讲的内容。
假如我们需要对比 x 和 y 是否相同,就会进行如下判断流程:
- 首先会判断两者类型是否相同。相同的话就是比大小了
- 类型不相同的话,那么就会进行类型转换
- 会先判断是否在对比 null 和 undefined,是的话就会返回 true
- 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
1 == '1'
↓
1 == 1
判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
'1' == true
↓
'1' == 1
↓
1 == 1
判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断
'1' == { name: 'yck' }
↓
'1' == '[object Object]'
需要注意的是:
undefined == null // true
undefined === null // false
对于 === 来说就简单多了,就是判断两者类型和值是否相同。需要注意的是有两个特殊情况:
NaN === NaN; // false
0 === -0; // true
对于NaN
的比较,用Number.isNaN()
;对于-0
的比较,用Object.is()
。
实际写代码的时候,建议用 ===
比 === 还要严格的是Object.is()
> Object.is(123, 123)
true
> Object.is(123, '123')
false
> Object.is(NaN, NaN)
true
> NaN === NaN
false
Symbols
Symbols 是 Primitive values,用Symbol();
来创建
const mySymbol = new Symbol('mySymbol');
每个 Symbols 都是唯一的,并且不能通过值来比较:
> Symbol() === Symbol()
false
Symbols 通常有两种用法:
- 用来定义常量
- 用来定义 Objects 里唯一的 property key
Symbols 通常用来定义常量的值
比如我们需要用常量聊表示颜色,通常我们会这么定义:
const COLOR_BLUE = 'Blue';
如果我们不小心写错了,多加一个蓝色的常量:
const MOOD_BLUE = 'Blue';
这个时候这两个常量就没办法区分了:
> COLOR_BLUE === MOOD_BLUE // true
我们可以用 Symbol 来解决这个问题:
const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');
console.log(COLOR_BLUE === MOOD_BLUE) // false
const COLOR_RED = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN = Symbol('Green');
const COLOR_BLUE = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
throw new Exception('Unknown color: '+color);
}
}
Symbols 用来定义 Objects 里唯一的 property key
比如下面这个对象:
const pt = {
x: 7,
y: 4,
toString() {
return `(${this.x}, ${this.y})`;
},
};
其中 x 和 y 是base level
的 key,toString()
是meta-level
的key,这两种层次的 key 绝对不能定义成相同的名称,这个时候就可以用Symbol
来定义对象的属性名称,因为Symbol
的值是唯一的。
const specialMethod = Symbol('specialMethod');
const obj = {
_id: 'kf12oi',
[specialMethod]() {
return this._id;
}
};
undefined vs. null
- undefined 是指声明但是未被初始化的值,它有以下使用场景:
let myVar1; // undefined var myVar2; // undefined const obj = { }; // property .unknowProp is also undefined function func() {} // function with no return statment, is also return undefined
- null 是指故意赋值为null,有目的使值为null,它有以下使用场景:
Object.getPrototypeOf(Object.prototype); // 对于对象的 protptype 要不为对象,要不就是null /a/.exec('x'); // 对于正则表达式,如果没有匹配那就是null JSON.stringify({a: undefined, b: null}); //'{"b": null}' //JSON data format 只支持null不支持undefined
如何来判断是 undefined 还是 null 呢?
if(x === null)
if(x === undefined)
if(x !== undefined && x !== null)
if(x)
if(x === undefined || x === null)
if(!x)
boolean
在if(value){}
语句判断是否为 true 的时候,实际代码是if(Boolean(value) === true)
:
以下的值在转为 Boolean 的时候,全是 false:
undefined
null
Boolean: false
Number: 0, NaN
Bigint: 0n
string: ''
除此之外的(包括所有的 objects)在转为 Boolean 的时候,全是true:
Boolean('abc') // true
Boolean([]) // true
Boolean({}) // true
boolean 转换规则:
Number
JS 中的 Number 同时用来表示 integers 和 floating point numbers, 比如:98 和 123.45
需要注意的是, 对于 integers 是指没有小数的浮点数:
98 === 98.0 // true
用于 Number 的操作符有:
对于 + 号,除了用于加法计算以外,还有以下的用法:
1. 表示正数:+n
+(-7) -> -7
-(-7) -> 7
2. 转换为数字
+'5' -> 5
+'-12' -> -12
-'9' -> -9
+'abc' -> NaN
NaN
对于 NaN 的判断:
const n = NaN;
console.log(n === n); // false
Number.isNaN(n, NaN); // true
Object.is(n, NaN); // true
如果需要在数组里找到 NaN:
[NaN].indexOf(NaN); // -1, 这个方法不能用来找 NaN
[NaN].findIndex(x => Number.isNaN(x)); // 0, 可以找到NaN
[NaN].find(x => Number.isNaN(x)); // NaN
Infinity
如果 Number 太大的话,就会返回 Infinity
Math.pow(2, 1023); // 8.98846567431158e+307
Math.pow(2, 1024); // Infinity
5 / 0 // Infinity
-5 / 0 // -Infinity
Infinity 比所有的数字都大(除了 NaN), 推荐用法:
function findMinimum(numbers) {
let min = Infinity;
for (const n of numbers) {
if (n < min) min = n;
}
return min;
}
findMinimum([5, -1, 2]) // -1
findMinimum([]) // Infinity
对于 Infinity 判断:
const x = Infinity;
console.log(x === Infinity); // true
console.log(Number.isFinite(x)); // true
Number 的转换规则:
String
字符串可以通过数组的方式访问:
const str = 'abc';
console.log(str[1]); //b
console.log(str.length); //3
也可以通过 for-of 或者展开运算符(…)来访问:
for (const item of 'xuz') {
console.log(item);
}
// output:
// x
// u
// z
可以通过...
把字符转换成数组:
console.log([...'xuz']); // ['x','u','z']
通过 + 号来连接字符串,推荐使用这种方式,因为 JS 引擎对这种方式已经优化过了:
console.log(3 + ' times ' + 4); // '3 times 4'
let str = '';
str += 'say it';
str += ' one more';
str += ' time';
console.log(str); // 'say it one more time'
转换为 string 有以下三种方式:
1. String(x); (推荐使用这种方式)
2. '' + x
3. x.toString() (这种方式对 undefined 和 null 不起作用)
String 的转换规则:
枚举对象属性
有三种原生的方法可以列出或者枚举对象的属性。
- for…in循环,该方法会依次访问一个对象以及其原型链中所有可枚举的属性
obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
obj2 = Object.create(obj);
obj2.e = 5;
for (var proptery in obj2) {
console.log('proptery is ' + proptery + ' value is ' + obj2[proptery]);
}
//result:
//proptery is e value is 5
//proptery is a value is 1
//proptery is b value is 2
//proptery is c value is 3
//proptery is d value is 4
- Object.keys(o),该方法返回对象自身(不包括原型中)的所有属性的名称的数组。
obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
obj2 = Object.create(obj);
obj2.e = 5;
obj2.f = 6;
var items = Object.keys(obj2);
console.log(items); // ["e","f"]
- Object.getOwnPropertyNames(o),该方法返回一个数组,它包好了对象o所有的属性(无论是否可枚举)的名称。
obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
obj2 = Object.create(obj);
obj2.e = 5;
obj2.f = 6;
var items = Object.getOwnPropertyNames(obj2);
console.log(items); // ["e","f"]
判断对象是否为空
function isEmpty(obj) {
for (var pro in obj) {
if (obj.hasOwnProperty(pro)) {
return false;
}
}
return JSON.stringify(obj) === JSON.stringify({});
}
function isEmpty(obj) {
return Object.keys(obj).length === 0 && obj.constructor === Object;
}
四则运算符
加法运算符不同于其他几个运算符,它有以下几个特点:
运算中其中一方为字符串,那么就会把另一方也转换为字符串
如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
如果你对于答案有疑问的话,请看解析:
-
对于第一行代码来说,触发特点一,所以将数字 1 转换为字符串,得到结果 ‘11’
-
对于第二行代码来说,触发特点二,所以将 true 转为数字 1
-
对于第三行代码来说,触发特点二,所以将数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3
另外对于加法还需要注意这个表达式 ‘a’ + + ‘b’
'a' + + 'b' // -> "aNaN"
因为 + ‘b’ 等于 NaN,所以结果为 “aNaN”,你可能也会在一些代码中看到过 + ‘1’ 的形式来快速获取 number 类型。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
map, filter, reduce
map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。
[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]
另外 map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组
['1','2','3'].map(parseInt)
//第一轮遍历 parseInt('1', 0) -> 1
//第二轮遍历 parseInt('2', 1) -> NaN
//第三轮遍历 parseInt('3', 2) -> NaN
filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素
let array = [1, 2, 4, 6]
let newArray = array.filter(item => item !== 6)
console.log(newArray) // [1, 2, 4]
和 map 一样,filter 的回调函数也接受三个参数,用处也相同。
最后我们来讲解 reduce 这块的内容,同时也是最难理解的一块内容。reduce 可以将数组中的元素通过回调函数最终转换为一个值。
如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码
const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
total += arr[i]
}
console.log(total) //6
但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码
const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)
对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程
- 首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
- 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
- 在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入
- 所以在第二次执行回调函数时,相加的值就分别是 1 和 2,以此类推,循环结束后得到结果 6
想必通过以上的解析大家应该明白 reduce 是如何通过回调函数将所有元素最终转换为一个值的,当然 reduce 还可以实现很多功能,接下来我们就通过 reduce 来实现 map 函数
const arr = [1, 2, 3]
const mapArray = arr.map(value => value * 2)
const reduceArray = arr.reduce((acc, current) => {
acc.push(current * 2)
return acc
}, [])
console.log(mapArray, reduceArray) // [2, 4, 6]