JS

JavaScript 基础

By John Han
Picture of the author
Published on
async 异步

JavaScript 基础

基础扎实,走得更稳、更远。

1. 数据类型

1.1 数据类型分类

JS数据类型
JS数据类型

JS 数据分为两类:

  • 基础类型:String、Number、Undefined、Null、Boolean、Symbol、BigInt

    基础类型存储在栈内存,被引用或拷贝时会创建一个完全相等的变量

  • 引用类型:Object(Function、Array、Math) 引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址

例 1:

let a = {
  name: "kobe",
  age: 18
}

// b和a指向同一内存地址
let b = a;
// 修改b中name的值
b.name = "bryant";
console.log(a.name); // "bryant"

function getObj(o) {
  o.name = "kk";
  o = {
    name = "kkbb",
    age = 19
  }
  return o;
}
let c = getOjb(a);

console.log(c.name); // "kkbb"
console.log(a.name); // "kk"
let a = {
  name: "kobe",
  age: 18,
};
function change(o) {
  // 相当于修改实参的值,会改变实参
  o.age = 24;
  // 这里给o重新赋值,和传入的实参已经无关了
  o = {
    name: "lbj",
    age: 30,
  };
  return o;
}

let b = change(a);
console.log(b.age); // 30
console.log(a.age); // 24

1.2 数据类型判断

1.2.1 typeof

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

例:

console.log(typeof "abc"); // string
console.log(typeof 2); // number
console.log(typeof false); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof [3]); // object
console.log(typeof { a: 1 }); // object
console.log(typeof Symbol()); // symbol
console.log(typeof console); // object
console.log(typeof console.log); // function

结论:

  • typeof 能判断基本数据类型(null 除外)
  • typeof 判断引用类型只能判断出函数为 function,其他引用类型数据都为 object

1.2.2 instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上.

翻译成人话就是,a instanceof Fa,检测 a 是不是 Fa 的实例。

例:

let a = new String("kobe");
console.log(a instanceof String); // true

console.log("abc" instanceof String); // false
console.log(2 instanceof Number); // false
console.log(false instanceof Boolean); // false
console.log(Symbol() instanceof Symbol); // false
console.log([3] instanceof Array); // true
console.log({ a: 1 } instanceof Object); // true
console.log(console instanceof Object); // true
console.log(console.log instanceof Function); // true

可以看出,instanceof 判断数据类型是不准确的:

  • instanceof 能有效判断引用数据类型
  • instanceof 不能准确判断基础数据类型

1.2.3 Object.prototype.toString()

Object.prototype.toString() 方法返回一个表示该对象的字符串.

Object.prototype.toString({}); // "[object Object]"
Object.prototype.toString.call({}); // 同上结果,加上call也ok
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call("1"); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(function () {}); // "[object Function]"
Object.prototype.toString.call(null); //"[object Null]"
Object.prototype.toString.call(undefined); //"[object Undefined]"
Object.prototype.toString.call(/123/g); //"[object RegExp]"
Object.prototype.toString.call(new Date()); //"[object Date]"
Object.prototype.toString.call([]); //"[object Array]"
Object.prototype.toString.call(document); //"[object HTMLDocument]"
Object.prototype.toString.call(window); //"[object Window]"

1.2.4 实现一个数据类型判断函数

function getType(data) {
  // 先使用typeof判断基础数据类型
  let type = typeof data;
  if (type === "object") {
    // 使用
    type = Object.prototype.toString
      .call(data)
      .replace(/^\[object (\S+)\]$/, "$1") // 注意正则中间有个空格
      .toLowerCase(); // 返回转化为小写的数据类型,和 typeof 一样
  }

  return type;
}

console.log(getType("abc"));
console.log(getType(1));
console.log(getType(console));
console.log(getType(console.log));
console.log(getType([]));

// string
// number
// object
// array

1.3 数据类型转换

  1. 强制数据类型转换
  • Number()

    创建一个 Number 对象

  • parseInt()

    parseInt(string, radix);

    解析一个字符串并返回指定基数十进制整数

  • parseFloat()

    全局方法,解析一个参数(必要时先转换为字符串)并返回一个浮点数

  • toString()

    将参数转化为 String 对象

    将参数转化为 String 对象;每个构造函数都有自己的 toString 方法,如 Function.prototype.toString()

  • String()

    创建一个 String 对象

  • Boolean()

    创建一个 Boolean 对象;

    undefined、null、false、“”、0、NaN 会转化为 false,其他则都为 true

强制--数据类型转换:

console.log(Number("1")); // -> 1
console.log(Number("1test")); // -> NaN
console.log(Number(null)); // -> 0
console.log(Number("")); // -> 0
console.log(Number(undefined)); // -> NaN

console.log(parseInt("")); // -> NaN

// parseFloat() 返回 3.14
console.log(parseFloat(3.14)); // -> 3.14
console.log(parseFloat("3.14")); // -> 3.14
console.log(parseFloat("   3.14   ")); // -> 3.14
console.log(parseFloat("314e-2")); // -> 3.14
console.log(parseFloat("0.0314E+2")); // -> 3.14
console.log(parseFloat("3.14some non-digit characters")); // -> 3.14
console.log(parseFloat({ toString: function() { return "3.14" } }); // -> 3.14
// parseFloat() 返回 NaN
console.log(parseFloat("abc123")); // -> NaN

// parseInt() 返回 16
parseInt("0xF", 16); // -> 16
parseInt("F", 16); // -> 16
parseInt("17", 8); // -> 16
parseInt(021, 8); // -> 16
parseInt("015", 10);  // -> 16
parseInt(15.99, 10); // -> 16
parseInt("15,123", 10); // -> 16
parseInt("FXX123", 16); // -> 16
parseInt("1111", 2); // -> 16
parseInt("15 * 3", 10); // -> 16
// parseInt() 返回 NaN
console.log(parseFloat("abc"), 8); // -> NaN (”abc“不是数字)
console.log(parseFloat("123"), 2); // -> NaN (格式错误导致)

console.log(Boolean("")); // -> false
console.log(Boolean(null)); // -> false
console.log(Boolean(NaN)); // -> false
console.log(Boolean("1")); // -> true

  1. 隐式数据类型转换
  • 运算符: +、-、*、/
  • 逻辑运算符: &&、||、!
  • 关系操作符: 大于、小于、大于等于、小于等于
  • 相等运算: ==
  • 条件语句: if/while

隐式--数据类型转换:

// 一方为 null 或 undefined,只用另一方同样为 null 或 undefined 才返回 true
null == undefined; // => true
null == 0; // => false
null == ""; // => false

// 有 Symbol 则返回 false
Symbol() == "";

// 一方为 Number,一方为 Boolean,会将 Boolean 转换为 Number
0 == false; // true
1 == true; // => true
2 == true; // => false

// 一方为 String, 一方为 Number,将 String 转换为 Number
"123" == 123; // => true

1.3.1 手动实现一个 JSON.stringify 方法

我们知道 JSON.stringify() 方法将一个 JavaScript 对象转换为 JSON 字符串.

  1. JSON.stringify 转化规则
stringify1
stringify1
stringify2
stringify2
function jsonStringify(data) {
  let type = typeof data;

  // 根据不同数据类型做转换
  if (type !== "object") {
    let result = data;
  }
}

2. 引用类型

2.1 基本引用类型

  • Date

  • RegExp

  • 原始值包装类型:Boolean、Number、String

    用到原始值的方法时,后台会自动创建一个原始包装类型,如:let s1 = "abcdefg"; let s2 = s1.substring(2);

    第二次调用时,如 s1.substring() 会读取 s1;

    读取操作会执行以下 3 个步骤:

      1. 创建一个 String 类型
      1. 调用实例上的特定方法
      1. 销毁实例
  • 单例内置对象:Global、Math

    • Global 是 ECMAScript 中的全局对象
    • 全局作用域中定义的变量都会变成 Global 的属性
    • 浏览器的 window 实现了 Global,但不只有 Global

对日期的处理:

Moment.js -- 经典的时间处理库

// 获取当前时间
// new Date 返回一个对象
let now = new Date();
console.log(now); // Mon Mar 08 2021 01:24:00 GMT+0800 (中国标准时间)
console.log(typeof now); // object

// 获取当前时间戳
let time1 = Date.parse(new Date()); // 精确到秒
let time2 = new Date().valueOf(); // 精确到毫秒
let time3 = new Date().getTime(); // 精确到毫秒
// ES5新增API,获取当前毫秒时间戳,返回一个数字
let start = Date.now();
console.log(start); // 1615138155922
console.log(typeof start); // number

// 获取当前月份,0开始计数
let month = now.getMonth();

// 日期转换
// 转为时间戳
Date.parse();

2.2 集合引用类型

  • Object:对象

  • Array:数组

  • 定型数组:

  • Map:可以像 Object 一样存储键值对

    • Map 可以使用任意数据类型作为键
    • Map 会维护键值对插入顺序
    • Map 和 Object 没什么大的差别,除了使用方式不同,Object 的查询性能略好,Map 增、删、改性能更好
  • WeakMap

  • Set:允许你存储任何类型的唯一值,Set 中的元素是唯一的

  • WeakSet

注意

1.Map 和 Set 本质上都是哈希表,Object 同样也是

2.Map 像 Object,略有区别

3.Set 像 Array,Set 中元素具有唯一性

4.Set 没有索引且无序,因为他数据结构属于哈希表;而 Array 的数据结构属于链表

// Set
let mySet = new Set([1, 2, 3, 1]);

// Set 会忽略重复的值
console.log(mySet); // {1, 2, 3}

3. 引用类型的操作

3.1 Array 的操作

数组操作是前端数据处理的重点。

Array 应该是 JS 中第二常用的数据类型吧,第一是 Object。

3.1.1 数组 API 汇总

数组方法分类:

Array method
Array method

数组方法汇总:

Array method
Array method

脑图地址,拿走不谢

3.1.2 类数组

JS 中存在一种类数组的对象。

他们不能直接调用数组的所有方法,但具有数组的某些特征。

常见的类数组

  • 函数中的 arguments
  • getElementsByTagName/ClassName 等获得的 HTML 集合
  • querySelect 获得的 NodeList

示例:

function sum(a, b) {
  let args = [...arguments];

  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);
// => 3
function sum(...args) {
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);
// => 3

3.1.3 数组去重

方法 1: 傻瓜方法 for/forEach 遍历

function remArr(arr) {
  let res = [];

  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }

  return res;
}

方法 2: 运用 Set 特性

const numbers = [2, 3, 4, 4, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 5, 32, 3, 4, 5];
// new Set去重,... 或 Array.from() 将 set再次转变为数组
console.log([...new Set(numbers)]);
console.log(Array.from(new Set(numbers)));

方法 3:运用 reduce 方法:

function remArr(arr) {
  if (!Array.isArray(arr)) return "Error: args Type Error";

  return arr.reduce((acc, cur) => {
    console.log(acc, cur);
    if (acc.indexOf(cur) === -1) {
      acc.push(cur);
    }
    return acc;
  }, []);
}

3.1.4 数组扁平化

方法 1: 运用新 API flat()

[].flat(): 默认参数扁平化深度为 1;

[].flat(Infinity): 展开任意深度.

let arr1 = [0, 1, 2, [3, [5, 6]]];

console.log(arr1.flat()); // [0, 1, 2, 3, [5, 6]]
console.log(arr1.flat(Infinity)); // [0, 1, 2, 3, 5, 6]

方法 2: 运用 forEach 或 for 循环 + 递归

function flatArr(arr) {
  let result = [];

  arr.forEach((ele) => {
    // 递归
    if (Array.isArray(ele)) {
      result = result.concat(flatArr(ele));
      // 跳出递归
    } else {
      result.push(ele);
    }
  });

  return result;
}

方法 3: 利用 toString() 处理

function flatArr(arr) {
  if (Array.isArray(arr)) {
    return Array.from(arr.toString().split(","));
  }
}

方法 4: Generator function

function* flatten(array) {
  for (const item of array) {
    if (Array.isArray(item)) {
      yield* flatten(item);
    } else {
      yield item;
    }
  }
}

var arr = [1, 2, [3, 4, [5, 6]]];
const flattened = [...flatten(arr)];

3.1.5 数组排序

方法 1: 原生方法 sort

// 升序排列
let c = a.sort((a, b) => (a <= b ? -1 : 1));

3.1.6 数组遍历

  • for 循环
  • forEach

3.1.7 map() 和 reduce()

map()、reduce()

  • map(): 创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值

    var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
    }[, thisArg])
    
  • reduce(): 对数组中的每个元素执行一个由您提供的 reducer 函数

    arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]);
    

看了上面的介绍我们可以知道,二者的作用与区别

  • map主要作用: 遍历原始数组,创建出一个新数组

    const array1 = [1, 4, 9, 16];
    
    const mapFn = (x) => x * 2;
    
    // map1 就是 array1 所有元素执行 mapFn 后的返回值组成的新数组
    const map1 = array1.map(mapFn);
    
    console.log(map1);
    // [2, 8, 18, 32]
    
  • reduce主要作用: 遍历原始数组,对数组的每个元素,做你想做的任何事

简单说就是,reduce 可以做很多。

  1. reduce -> map

map 方法接收一个回调函数,函数内接收三个参数,当前项、索引、原数组,返回一个新的数组。

const testArr = [1, 2, 3, 4];
Array.prototype.reduceMap = function (callback) {
  return this.reduce((acc, cur, index, array) => {
    const item = callback(cur, index, array);
    acc.push(item);
    return acc;
  }, []);
};
testArr.reduceMap((item, index) => {
  return item + index;
});
// [1, 3, 5, 7]
  1. reduce -> forEach

forEach 接收一个回调函数作为参数,然后执行函数,没有返回值。

const testArr = [1, 2, 3, 4];
Array.prototype.reduceForEach = function (callback) {
  this.reduce((acc, cur, index, array) => {
    callback(cur, index, array);
  }, []);
};

testArr.reduceForEach((item, index, array) => {
  console.log(item, index);
});
// 1 0
// 2 1
// 3 2
// 4 3
  1. reduce -> filter

filter 同样接收一个回调函数,回调函数返回 true 则返回当前项,反之则不返回。

const testArr = [1, 2, 3, 4];
Array.prototype.reduceFilter = function (callback) {
  return this.reduce((acc, cur, index, array) => {
    if (callback(cur, index, array)) {
      acc.push(cur);
    }
    return acc;
  }, []);
};
testArr.reduceFilter((item) => item % 2 == 0); // 过滤出偶数项。
// [2, 4]
  1. reduce -> find

find方法中 callback 同样也是返回 Boolean 类型,返回你要找的第一个符合要求的项。

const testArr = [1, 2, 3, 4];
const testObj = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }];
Array.prototype.reduceFind = function (callback) {
  return this.reduce((acc, cur, index, array) => {
    if (callback(cur, index, array)) {
      if (acc instanceof Array && acc.length == 0) {
        acc = cur;
      }
    }
    // 循环到最后若 acc 还是数组,且长度为 0,代表没有找到想要的项,则 acc = undefined
    if (index == array.length - 1 && acc instanceof Array && acc.length == 0) {
      acc = undefined;
    }
    return acc;
  }, []);
};
testArr.reduceFind((item) => item % 2 == 0); // 2
testObj.reduceFind((item) => item.a % 2 == 0); // {a: 2}
testObj.reduceFind((item) => item.a % 9 == 0); // undefined
  1. 二维数组展平为一维数组
const testArr = [
  [1, 2],
  [3, 4],
  [5, 6],
];
testArr.reduce((acc, cur) => {
  return acc.concat(cur);
}, []);
// [1,2,3,4,5,6]
  1. 计算数组中每个元素出现的个数

初始化的值变成了 ,这个方法_非常常用_,变种也很多。

比如给你一个账单列表(项与项之间的消费类型有相同情况),让你统计账单列表中各个消费类型的支出情况,如 购物 、 学习 、 转账 等消费类型的支出情况。

这就用到了上述方法,去进行归类

const testArr = [1, 3, 4, 1, 3, 2, 9, 8, 5, 3, 2, 0, 12, 10];
testArr.reduce((acc, cur) => {
  if (!(cur in acc)) {
    acc[cur] = 1;
  } else {
    acc[cur] += 1;
  }
  return acc;
}, {});

// {0: 1, 1: 2, 2: 2, 3: 3, 4: 1, 5: 1, 8: 1, 9: 1, 10: 1, 12: 1}
const bills = [
  { type: "shop", money: 223 },
  { type: "study", money: 341 },
  { type: "shop", money: 821 },
  { type: "transfer", money: 821 },
  { type: "study", money: 821 },
];
bills.reduce((acc, cur) => {
  // 如果不存在这个键,则设置它赋值 [] 空数组
  if (!acc[cur.type]) {
    acc[cur.type] = [];
  }
  acc[cur.type].push(cur);
  return acc;
}, {});
// {
//   "shop": [
//     {
//       "type": "shop",
//       "money": 223
//     },{
//       "type": "shop",
//       "money": 821
//     }
//   ],
//   "study": [
//     {
//       "type": "study",
//       "money": 341
//     },
//     {
//       "type": "study",
//       "money": 821
//     }
//   ],
//   "transfer": [
//     {
//       "type": "transfer",
//       "money": 821
//     }
//   ]
// }
  1. 数组去重
const testArr = [1, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7];
testArr.reduce((acc, cur) => {
  if (!acc.includes(cur)) {
    acc.push(cur);
  }
  return acc;
}, []);
// [1, 2, 3, 4, 5, 6, 7]

总结

reduce 在你遇到数组复杂操作的时候,它就能大显身手。

3.2 对象的操作

3.2.1 对象是什么

定义一个对象:

// 字面量定义一个属性
let obj = {
  name: "nameIsObj",
  size: 123,
};

console.dir(obj);

打印结果:

对象是什么
对象是什么

我们可以看到,对象 obj 上有这些属性:

  • name 和 size: 这是我们创建对象时添加的属性
  • __proto__ : 这是对象自有的属性,用来继承构造函数 Object.prototype 上的属性

对象 obj 继承了哪些属性呢?

  • constructor: 就是 Object 构造函数 obj.__proto__.constructor === Object.prototype.constructor;

    Object.prototype.constructor === Object;

  • hasOwnProperty: 指示对象自身属性(⚠️ 不包含原型链上的属性)中是否具有指定的属性

    obj.hasOwnProperty('name'); // true

    obj.hasOwnProperty('toString'); // false

  • isPrototypeOf: 用于测试一个对象是否存在于另一个对象的原型链上

  • propertyIsEnumerable: 检测指定的属性是否可枚举

  • valueOf:返回指定对象的原始值 Number、String、Function、Array 等任何 Object 实例都有这个属性

  • toString、toLocaleString:返回一个表示该对象的字符串 Number、String、Function、Array 等任何 Object 实例都有这个属性

3.2.2 操作对象

Object 构造函数上的方法

  • Object.assign(target, obj1, obj2): 分配一个或多个对象到目标对象上
  • Object.keys(): 获取给定对象的自身可枚举属性
  • Object.create():
  • Object.defineProperties(): 直接在一个对象上定义新的属性或修改现有属性,并返回该对象

...

Stay Tuned

Want to become a Next.js pro?
The best articles, links and news related to web development delivered once a week to your inbox.