# 数据类型-对象类型

# Object

# 对象的生成

  • 对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型。

    var obj = {
      foo: "Hello",
      bar: "World"
    };
    

    该对象内部包含两个键值对(又称为两个“成员”),第一个键值对是 foo: 'Hello',第二个键值对是 bar: 'World',两个键值对之间用逗号分隔。
    “键名”都是字符串,如果键名是数值,会被自动转为字符串。
    “键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

# 对象的引用

  • 如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

    var o1 = {};
    var o2 = o1;
    o1.a = 1;
    o2.a; // 1
    o2.b = 2;
    o1.b; // 2
    

# 对象的属性

  • 读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

    var p = "bar";
    var obj = {
      p: "Hello World"
    };
    console.log(obj.p); // "Hello World"  点号后面p为对象的属性
    console.log(obj["p"]); // "Hello World"  []里加引号p为对象的属性
    
  • 点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

    var obj = {};
    obj.foo = "Hello";
    obj["bar"] = "World";
    
  • 看一个对象本身的所有属性,可以使用 Object.keys 方法

    var obj = {
      key1: 1,
      key2: 2
    };
    console.log(Object.keys(obj));
    // ['key1', 'key2']
    

    JavaScript 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

    var obj = {};
    obj.foo = 123;
    obj.foo; // 123
    
  • delete 命令 用于删除对象的属性,删除成功后返回 true。

    var obj = {};
    delete obj.p // true
    // 注意,删除一个不存在的属性,delete不报错,而且返回true。
    
  • 属性是否存在:

    in 运算符用于检查对象是否包含某个属性,如果包含就返回 true,否则返回 false。

    in 运算符 (属性 in 对象);
    var obj = { p: 1 };
    console.log('p' in obj);// true obj对象中有p
    console.log('toString ' in obj); //false
    
  • for...in 循环 用来遍历一个对象的全部属性。

    var obj = { a: 1, b: 2, c: 3 };
    for (var k in obj) {
      console.log("键名:", i);
      console.log("键值:", obj[k]);
    }
    
  • with 语句 操作同一个对象的多个属性时,提供一些书写的方便。

    with (对象) {
      语句;
    }
    

    建议不要使用 with 语句,可以考虑用一个临时变量代替 with

# 对象的方法

  1. obj.valueOf() 返回值为该对象的原始值。
  2. obj.toString() 方法返回一个表示该对象的字符串。

# Array

对象允许存储键值集合,这很好。

但很多时候我们发现还需要 有序集合,里面的元素都是按顺序排列的。

这时一个特殊的数据结构数组(Array)就派上用场了,它能存储有序的集合。

# 声明

let arr = new Array();
// 绝大多数情况下使用的都是第二种语法
let arr = [];

# 元素

数组(array)是按次序排列的一组值。每个值的位置都有编号(从 0 开始),整个数组用方括号表示。 任何类型的数据,都可以放入数组。

var arr = [
  { a: 1 },
  [1, 2, 3],
  function () {
    return true;
  }
];

arr[0]; // Object {a: 1}
arr[1]; // [1, 2, 3]
arr[2]; // function (){return true;}

如果数组的元素还是数组,就形成了多维数组。(一般使用不超过三维,超过三维用其他结构体)

var a = [
  [1, 2],
  [3, 4]
];
a[0][1]; // 2
a[1][1]; // 4

# 长度

  1. 数组的length属性,返回数组的成员数量。 只要是数组,就一定有length属性。该属性是一个动态的值,等于键名中的最大整数加上1

  2. 数组的数字键不需要连续,length属性的值总是比最大的那个整数键大1。 也表明数组是一种动态的数据结构,可以随时增减数组的成员。

  3. 清空数组的一个有效方法,就是将length属性设为 0。 当然也可以直接 var arr =[];


var arr = ["a", "b"];
arr.length; // 2
arr[2] = "c";
arr.length; // 3
arr[9] = "d";
arr.length; // 10

# 队列

队列(queue)是最常见的使用数组的方法之一。在计算机科学中,这表示支持两个操作的一个有序元素的集合

  • pop 取出并返回数组的最后一个元素
  • push 在数组末端添加元素
  • shift 取出数组的第一个元素并返回它
  • unshift 在数组的首端添加元素

# 数组的本质

本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object。 可以看到数组的键名就是整数 0、1、2...。

typeof [1, 2, 3]; // "object"
var arr = ["a", "b", "c"];
Object.keys(arr);
// ["0", "1", "2"]

但是数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。

记住,在 JavaScript 中只有 8 种基本的数据类型。数组是一个对象,因此其行为也像一个对象。

数组真正特殊的是它们的内部实现。JavaScript 引擎尝试把这些元素一个接一个地存储在连续的内存区域,就像本章的插图显示的一样,而且还有一些其它的优化,以使数组运行得非常快。

# 探究数组的性能

push/pop 方法运行的比较快,而 shift/unshift 比较慢。

为什么作用于数组的末端会比首端快呢?让我们看看在执行期间都发生了什么:

shift 操作必须做三件事:

  • 移除索引为 0 的元素。
  • 把所有的元素向左移动,把索引 1 改成 0,2 改成 1 以此类推,对其重新编号。
  • 更新 length 属性。

数组里的元素越多,移动它们就要花越多的时间,也就意味着越多的内存操作

# Function

# 函数的声明

function 命令

function print(s) {
  console.log(s);
}

函数表达式

var print = function x() {
  console.log(typeof x);
};
console.log(x); // ReferenceError: x is not defined
print(); // function

注意:若在 function 后面加上函数名,该函数名只在该函数体内部有效

注意

  • JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。 由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
  • JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。

# 函数的属性和方法

  1. 函数的name属性返回函数的名字。
  2. 函数的length属性返回函数的形参个数
  3. 函数的toString方法返回一个字符串,内容是函数的源码。

# 函数的作用域

  • 在 ES5 的规范中,JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
  • var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
  • 函数的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。