Li Mei

个人技术博客

嗨,我是李梅,一名前端开发者。


主要是记录个人对技术的理解和开发过程中遇到的问题,欢迎了解更多。

JS:核心基础知识点

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 转换规则: js-common

Number

JS 中的 Number 同时用来表示 integers 和 floating point numbers, 比如:98 和 123.45

需要注意的是, 对于 integers 是指没有小数的浮点数:

98 === 98.0 // true

用于 Number 的操作符有: js-common

对于 + 号,除了用于加法计算以外,还有以下的用法:

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 的转换规则: js-common

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 的转换规则: js-common

枚举对象属性

有三种原生的方法可以列出或者枚举对象的属性。

  • 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]

如何判断相等性

下一篇

JS:Map 和 Set