第5章 引用类型

54 阅读12分钟

Object 类型

创建Object实例的方式有两种

  • new Object() 方式
  • 对象字面量方式
var obj1 = new Object()  // new 关键字创建Object对象

var obj2 = {}  // 对象字面量方式

console.log(obj1, obj2);

Array类型

创建数组的方式有两种

  • new Array() 方式
  • 数组字面量方式
var arr = new Array();  // 构造函数
var colors = ["red", "green"];  // 数组字面量

检测数组: Array.isArray()

转换方法

数组是对象,拥有Object原型上自带的7个属性。

数组调用toString():数组的每一项都调用toString()转为字符串形式,再用逗号拼接成一个字符串

数组调用valueOf(): 还是数组

var p1 = {
  toString: () => "p1 toString",
  toLocaleString: () => "p1 toLocaleString",
};

var p2 = {
  toString: () => "p2 toString",
  toLocaleString: () => "p2 toLocaleString",
};

var arr = [p1, p2];

console.log(
  arr.toString(), // p1 toString,p2 toString
  arr.toLocaleString() // p1 toLocaleString,p2 toLocaleString
);

从上面示例可以看出,数组调用toString()经历了两个步骤

  1. 每一项都调用toString(),转为字符串表示
  2. 再把每一项字符串表示,用逗号拼接为一个字符串

栈方法

栈是后进先出,用数组方法来模拟栈

push(),接收多个参数,把参数推入末尾;返回推入后的数组长度

pop(), 没有参数,从数组末尾移除最后一项;返回移除的项

var arr = [];
//push 推入多个元素,返回新推入后的数组长度
var count = arr.push("red", "blue");

console.log(count, arr);  // 2 ['red', 'blue']

// pop 弹出尾部最后一个元素
var item = arr.pop();

console.log(item, arr); // blue ['red']

队列方法

队列:先进先出

push(),接收多个参数,从队列尾部推入;返回推入之后的数组

shift(), 没有参数,从队尾移除一个元素,返回移除的元素

var arr = [];

var count = arr.push("red", "green");

console.log(count, arr); // 2 ['red', 'green']

// shift 是从头部移除一个元素,返回移除的值
var item = arr.shift();

console.log(item, arr); // red ['green']

反过来,用unshift()从队列头部推入多个参数;用pop() 从队列尾部移除一个元素

var arr = [];

// unshift(), 从队列头部添加多个元素
var count = arr.unshift("red", "green");

console.log(count, arr); // 2 ['red', 'green']

// pop 是从队列尾部移除一个元素,返回移除的值
var item = arr.pop();

console.log(item, arr); // green ['red']

栈和队列的示意图

数组方法作用返回值
push从尾部推入多个参数推入后的数组长度
unshift从头部推入多个参数推入后的数组长度
pop从尾部弹出一个参数弹出的参数
shift从头部弹出的一个参数弹出的参数

推入的push、unshift都是接收多个参数,且返回推入之后的数组长度

弹出的pop、shift都是没有参数,直接弹出最后一个或者第一个元素;返回的是弹出的那个元素

重排序方法

数组调用reverse(), 直接翻转了数组;而且原数组也被改变,它不是一个纯函数

返回的是对翻转后的数组的引用,不是复制了数组,所以原数组也被改变了

var arr = [1, 2, 3, 8, 9, 10, 12, 21, 30];

//  [30, 21, 12, 10, 9, 8, 3, 2, 1] [30, 21, 12, 10, 9, 8, 3, 2, 1]
// reverse 改变了原数组
console.log(arr.reverse(), arr);

数组的sort() 是对数组每一项都调用toString(), 再比较字符串的大小;就算是数字数组,也会先把每一项转为字符串,再比较字符串。

sort() 也是返回排序后的数组的引用,就是返回的同一个数组,所以看到原数组也被改变了。

var arr = [1, 2, 3, 8, 9, 10, 12, 21, 30];

// (9) [1, 10, 12, 2, 21, 3, 30, 8, 9]
// (9) [1, 10, 12, 2, 21, 3, 30, 8, 9]
// 数组的sort()把每一项都调用了toString(),再比较字符串大小
// sort() 也改变了原数组,不是一个纯函数
console.log(arr.sort(), arr);

比较函数,

  • 如果第一个参数应该位于第二个参数之前,返回一个负数
  • 如果两个参数相等,返回0
  • 如果第一个参数应该位于第二个参数之后,返回一个正数
var arr = [1, 2, 3, 8, 9, 10, 12, 21, 30];

function sortFn(first, second) {
  if (first < second) {
    return -1;
  } else if (first > second) {
    return 1;
  } else {
    return 0;
  }
}

// [1, 2, 3, 8, 9, 10, 12, 21, 30]
// [1, 2, 3, 8, 9, 10, 12, 21, 30]
// 传入函数以后,按照大小来比较
console.log(arr.sort(sortFn), arr);

更为简单的方法

var arr = [1, 2, 3, 8, 9, 10, 12, 21, 30];

function sortFn(first, second) {
  return first - second;
}

// [1, 2, 3, 8, 9, 10, 12, 21, 30]
// [1, 2, 3, 8, 9, 10, 12, 21, 30]
// 传入函数以后,按照大小来比较
console.log(arr.sort(sortFn), arr);

操作方法

concat()

先创建当前数组的一个副本,将接收到的参数添加到副本末尾,返回这个新构建的数组

如果concat() 不传入参数,相当于创建了一个当前数组的副本

var arr = ["red", "green"];
// concat() 不传任何参数,相当于把调用数组复制了一个副本,赋给了newArr
var newArr = arr.concat();

console.log(newArr, arr); //   ['red', 'green']  ['red', 'green']

如果是多个参数,追加到数组末尾;如果是数组,把数组中的所有元素都追加到新数组

var arr = ["red", "green"];

// 参数可以是单个值,也可以是数组,如果是数组,可以把里面每个元素都是追加到新数组中
var newArr = arr.concat('blue','yellow',['black','white']);

// ['red', 'green', 'blue', 'yellow', 'black', 'white'] ['red', 'green']
console.log(newArr, arr);
slice(start, end)

新建一个副本,返回start开始,到end结束的一个数组,end不包含在内。原数组不变

var arr = ["red", "green", "blue", "yellow"];

// 获取第12个元素
var newArr = arr.slice(1, 3);

// ['green', 'blue']  ['red', 'green', 'blue', 'yellow']
console.log(newArr, arr);
splice(start, count, insert1, insert2)

splice(开始位置,删除个数,插入值1,插入值2...)

截取/删除: 返回删除值组成的数组,原数组被改变 splice(start, count)。返回值作为截取数组,元素组是删除后的数组

插入: 第3个参数开始,就是在start的位置上,插入这些元素。返回值永远都是删除的元素组成的数组。只插入,返回值就是空数组。

替换: 第2个参数代表删除几个元素,再从第3个元素开始,依次插入到start位置,替换元素

var arr = ["red", "green", "blue", "yellow"];

// 从1位开始,删除2个元素; 返回删除的元素组成的数组
var newArr = arr.splice(1, 2);

// ['green', 'blue'] ['red', 'yellow']
console.log(newArr, arr);
var arr = ["red", "green", "blue", "yellow"];

// 从第1位开始,删除0个元素,并插入两个元素
var newArr = arr.splice(1, 0, "pink", "skyBlue");

// ['red', 'pink', 'skyBlue', 'green', 'blue', 'yellow']
console.log(newArr, arr);
var arr = ["red", "green", "blue", "yellow"];

// 删除2位,再插入2个元素
var newArr = arr.splice(1, 2, "pink", "skyBlue");

//  ['green', 'blue'] ['red', 'pink', 'skyBlue', 'yellow']
console.log(newArr, arr);

位置方法

indexOf(value): 查找value在数组中的位置,找不到返回-1

迭代方法

every()、some()、map()、filter()、forEach()

参数一:函数;参数二:运行该函数的作用域对象

归并方法

reduce()和reduceRight()

  • 参数1: 函数(前一个值,当前值,项的索引,数组对象)
  • 参数2: 初始值

不给初始值,默认是省去了第一次执行,直接把结果作为第2次(inde=1)的prev项

var arr = [1, 2, 3, 4, 5];

var resValue = arr.reduce((prev, cur, index) => {
  // 1  2    1  第2次, prev = 1,  cur = 2, 相加 = 3
  // 3  3    2  第3次, prev = 3,  cur = 3, 相加 = 6
  // 6  4    3  第4次, prev = 6,  cur = 4, 相加 = 10
  // 10 5    4  第5次, prev = 10, cur = 5, 相加 = 15
  // 15
  console.log(prev, cur, index);
  return prev + cur;
});

console.log(resValue);

给初始值,从index=0开始。第一轮prev为初始值,cur位第一项。

var arr = [1, 2, 3, 4, 5];

var resValue = arr.reduce((prev, cur, index) => {
  // 0  1   0 第1次, prev = 0,  cur = 1, 相加 = 1
  // 1  2   1 第2次, prev = 1,  cur = 2, 相加 = 3
  // 3  3   2 第3次, prev = 3,  cur = 3, 相加 = 6
  // 6  4   3 第4次, prev = 6,  cur = 4, 相加 = 10
  // 10 5   4 第5次, prev = 10, cur = 5, 相加 = 15
  // 15
  console.log(prev, cur, index);
  return prev + cur;
}, 0);

console.log(resValue);
var arr = [1, 2, 3, 4, 5];

var resValue = arr.reduce((prev, cur, index) => {
  // 10 1   0 第1次, prev = 10, cur = 1, 相加 = 11
  // 11 2   1 第2次, prev = 11, cur = 2, 相加 = 13
  // 13 3   2 第3次, prev = 13, cur = 3, 相加 = 16
  // 16 4   3 第4次, prev = 16, cur = 4, 相加 = 20
  // 20 5   4 第5次, prev = 20, cur = 5, 相加 = 25
  // 25
  console.log(prev, cur, index);
  return prev + cur;
}, 10);

console.log(resValue);

Date类型

console.log(
  new Date(), // Wed Nov 20 2024 00:03:55 GMT+0800 (中国标准时间) 当前日期和时间
  Date.parse(new Date()), // 1732032235000 日期毫秒数
  Date.UTC(2024, 10, 20) , // 1732060800000 接收年 月 日
  Date.now(), // 1732032459944 当前时间
  (new Date()).valueOf() // 1732032540448 也是当前时间的毫秒
);

要获取当前时间的时间戳,Date.now()和(new Date()).valueOf()

Function类型

函数是Function类型的实例对象

函数名是指针,指向函数对象

函数没有重载,是因为函数名就是一个指针,当给这个指针赋值一个函数对象后,指针指向的是函数对象;当再次给这个名字的指针赋值另一个函数对象后,相当于这个指针指向了后面的函数对象。所以也就没有重载。

函数声明与函数表达式

函数声明会有函数声明提升(function declaration hoisting),就是把函数声明提到全局作用域的顶部,所以使用在前,声明在后,也能成功调用。

函数表达式如果用var声明,其实只会提升函数变量名到作用域顶部,初始化部分不会提到上面去。所以不能在函数表达式之前使用函数。

作为值的函数

  • 函数作为另一个函数的参数
  • 函数作为一个函数的返回值
var arr = [
  {
    name: "Alice",
    age: 28,
  },
  {
    name: "Jack",
    age: 19,
  },
  {
    name: "Cindy",
    age: 25,
  },
];

// 根据传入的属性名,返回一个排序函数
function makeSortByParams(paramName) {
  return function (obj1, obj2) {
    // 比较对象的属性,小的排在前面
    if (obj1[paramName] < obj2[paramName]) {
      return -1;
    } else if (obj1[paramName] > obj2[paramName]) {
      return 1;
    } else {
      return 0;
    }
  };
}

var sortByName = makeSortByParams("name");
var sortByAge = makeSortByParams("age");

// console.log(arr.sort(sortByName));
console.log(arr.sort(sortByAge));

以上函数作为了返回值,而且该返回的函数用到了外层函数中的局部变量。导致外层函数执行结束后,弹栈并清空外层函数的变量对象及其变量和函数时,因为内层函数对paramName还有引用,所以这个变量不会被销毁。这就形成了闭包。

函数内部属性

arguments和this

arguments.callee 指向拥有这个arguments对象的函数,也就是 函数名

function factorial(num) {
  if (num === 1) {
    return 1;
  }
  // arguments.callee 指向拥有这个arguments对象的函数,也就是 factorial
  return num * arguments.callee(num - 1);
}

console.log(factorial(5)); // 120

函数属性和方法

length: 形参个数

apply(运行函数的作用域,参数数组):参数可以是数组,也可以是arguments对象

call 和apply一样,只是从第二个参数开始,都是一个个参数,而非数组

function sum(num1, num2) {
  return num1 + num2;
}

function callSum1(num1, num2) {
  // this是window对象; 参数是arguments
  return sum.apply(this, arguments);
}

function callSum2(num1, num2) {
  // this是window对象;参数是数组
  return sum.apply(this, [num1, num2]);
}

console.log(
  callSum1(10, 20), // 30
  callSum1(10, 20) // 30
);

基本包装类型

var s1 = 'hello'
var s2 = s1.substring(2) // llo
console.log(s2);

字符串是基本类型变量,照理应该不能调用方法和属性。其实第二行访问s1时,访问过程属于读取模式,也就是要从内存中读取这个字符串的值。读取模式中访问字符串,后台都会进行三个步骤

  1. 创建String类型的一个实例
  2. 在实例上调用指定的方法
  3. 销毁这个实例
var s1 = new String("hello"); // 创建String类型的实例
s1.substring(2); // 用实例对象调用指定的方法
s1 = null; // 销毁这个String实例对象,空对象用null

引用类型和基本包装类型的主要区别是对象的生存期。引用实例是在当前作用域销毁之前都会保存在内存中,而基本包装类型,只存在于一行代码的执行瞬间。

var s1 = 'hello'
s1.color = 'red' // 读取模式,创建了String基本包装类型,String实例对象创建了color属性以后,立即销毁了这个实例
console.log(s1.color); // undefined, 又进入读取模式,又新建了String实例对象。这个实例对象上没有color属性

Object()构造函数,会根据传入值的类型,返回相应基本包装类型的实例

String类型

1. 字符方法

charAt(val): 返回指定位置的字符

charCodeAt(val): 返回指定位置的字符编码

var str = "hello";

console.log(
  str.charAt(1), // e
  str.charCodeAt(1) // 101
);
2. 字符串操作方法

concat(val1,va2...):字符串拼接,返回新的字符串

slice(start, end) 返回start到end的新字符串,end不包括

substring(start, end) 返回start到end的新字符串,end不包括

substr(start, count) 返回start开始,count个字符的字符串

var str = "hello";

console.log(
  str.slice(1, 3), // el
  str.substring(1, 3), // el
  str.substr(1, 3) // ell
);
3. 字符串操作方法

indexOf(字符串):查询搜索字符串在原本字符串中的位置

单体内置对象

URI编码方法

encodeURI() 主要用于整个URI,不会对本身属于URI的特殊字符进行编码,如冒号、正斜杠、问号和井字号。

encodeURIComponent() 用于URI中的某一段,如查询字符串。对所有非字母数字的字符进行编码。

var uri = "https://e2.aliyun.com/go/portal/opportunities?keywords=螺纹钢";

// https://e2.aliyun.com/go/portal/opportunities?keywords=%E8%9E%BA%E7%BA%B9%E9%92%A2
// 保留了 本身属于URI的特殊字符,: / ? =
console.log(encodeURI(uri));
// https%3A%2F%2Fe2.aliyun.com%2Fgo%2Fportal%2Fopportunities%3Fkeywords%3D%E8%9E%BA%E7%BA%B9%E9%92%A2
// 所有非字母数字的字符都被编码了
console.log(encodeURIComponent(uri));