javascript 中的object array

[TOC]

1. Object 数据类型

与python中的dict类型一样,object数据类型也是由减值以及数值组成的集合,属于一种无序序列对象的 所有键值均为字符串,所以加不加引号效果都一样,但是在进行引用时必须要加引号,如果键值是数值类型,也会转化为字符串。

键值的注意事项

  • 如果键值不符合标识名的条件,(第一个字符为数值,包含空格或者运算符)则键值必须加上引号,否则会报错,所以建议所有键值都加引号

  • 对象的每一个键名又被称为属性,其value可以是任何数据类型,通常把这个属性叫做方法,可以像函数一样进行调用,如果属性的值还是一个对象就可以形成链式引用

let function_sets = {
    double : (x) => {
        return 2 * x;
    },
    abs : (a) => {
        if(a < 0) return -a;
        else return a;
    },
    inner_func:{
        debug : () => console.log('You are using the inner function'),
    },
};

console.log(`Object test: ${function_sets.double(11)}, ${function_sets.abs(-1000)}`);
function_sets.inner_func.debug();

对象的每一个属性都可以进行动态指定,也可以进行动态更改,在进行引用时可以直接采用. 或者采用python dict的引用方式,但是注意对于键值标识名不合乎规定的需要采用后者引用方式

但是需要注意,如果使用方括号进行引用,必须加上引号,否则会被当做变量处理

let dict = {
    1:'kobe bryant',
    test:123,
    'another':111
}
console.log(`The dict is :${dict['test']}`);
dict[1] = 123;
dict.wander = 'Amazing language';
console.log(`Change dict ${dict['1']}, ${dict['wander']}`);

let foo = 'bar';
let obj = {
    foo: 1,
    bar:2,
};

obj.foo // 1
obj[foo] // 2

表达式还是语句

由于对象采用大括号表示,就导致了一个问题,如果行首是一个大括号,到底是一个对象还是一个代码块。

{ foo: 123 }

JavaScript 引擎读到上面这行代码,会发现可能有两种含义。第一种可能是,这是一个表达式,表示一个包含foo属性的对象;第二种可能是,这是一个语句,表示一个代码区块,里面有一个标签foo,指向表达式123

为了避免这种歧义,V8 引擎规定,如果行首是大括号,一律解释为对象。不过,为了避免歧义,最好在大括号前加上圆括号。

({ foo: 123})

这种差异在eval语句(作用是对字符串求值)中反映得最明显。

eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}

上面代码中,如果没有圆括号,eval将其理解为一个代码块;加上圆括号以后,就理解成一个对象。

对象的属性参数

通过对象的属性的参数可以设置对象属性的行为,基本参数如下:

  • [[value]]: 保存属性的值

  • [[Writable]]: 确定属性值是否可以被更改

  • [[get]]: 保存getter ,当一个属性被read时会调用getter 函数

  • [[set]]: 保存setter 当一个属性被设置为一个值的时候,调用该函数,该函数需要传入value参数

  • [[enumerable]]: 控制某个属性是否可以被迭代

  • [[configurable]]: 为假时该属性不可被删除,不可以改变属性的其他参数(除了value), 换言之,该参数控制属性元数据的读写铨叙

默认值如下:

对象属性的操作

1. delete

delete 命令用于删除对象的属性,删除成功后会返回true ,使用该命令会删除指定的属性,这时再读取会得到undefined ,而且使用Object.keys() 函数进行属性的查看也找不到该属性。但删除一个不存在的属性时,不会报错,而是返回true

var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true
obj.p // undefined
Object.keys(obj) // []

只有一种情况,delete命令会返回false,就是使用defineProperty 函数设定的属性不可更改

Object.defineProperty(obj, propName, proDesc) :

该函数用于创建或者修改一个对象obj的属性proName,规定其描述符

let obj = Object.defineProperty({}, 'foo', {
    value: 123,
    enumerable: true,
    writable: false,
    configurable: false
});

根据该设定,将该对象的该属性设置为只读状态,不可以进行删除以及更改,进行delete时会返回false

2.属性存在

in 运算符用于判断属性是否存在,返回boolean类型,但是in 运算符不能判断某个属性时object本身的还是继承得到的,toString 属性是继承得到的,也会返回true

此时可以借助函数Object.hasOwnProperty() 判断是否为自身属性

let obj = {p: 1};
'p' in obj // true
'toString' in obj  // true

> obj
{ pre: [Getter/Setter] }
> pre in obj;
ReferenceError: pre is not defined
> 'pre' in obj;
true
> 'toString' in obj;
true
> 'tString' in obj;
false
> obj.hasOwnProperty('toString')
false

3.属性的遍历

使用form...in 进行遍历操作,但是需要注意的是,只有enumerable 为真的属性才可以进行遍历,会跳过为假的属性;不仅遍历自身属性,而且遍历继承的属性,例如每个object都继承了toString 属性,但是不会被遍历到

var obj = {};

// toString 属性是存在的
obj.toString // toString() { [native code] }

for (var p in obj) {
  console.log(p);
} // 没有任何输出

var person = { name: '老张' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
// name

4. with 语句

with语句的格式如下:

with (对象) {
  语句;
}

它的作用是操作同一个对象的多个属性时,提供一些书写的方便。

// 例一
var obj = {
  p1: 1,
  p2: 2,
};
with (obj) {
  p1 = 4;
  p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;

// 例二
with (document.links[0]){
  console.log(href);
  console.log(title);
  console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);

注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。

var obj = {};
with (obj) {
  p1 = 4;
  p2 = 5;
}

obj.p1 // undefined
p1 // 4

上面代码中,对象obj并没有p1属性,对p1赋值等于创造了一个全局变量p1。正确的写法应该是,先定义对象obj的属性p1,然后在with区块内操作它。

这是因为with区块没有改变作用域,它的内部依然是当前作用域。这造成了with语句的一个很大的弊病,就是绑定对象不明确。

with (obj) {
  console.log(x);
}

单纯从上面的代码块,根本无法判断x到底是全局变量,还是对象obj的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用with语句,可以考虑用一个临时变量代替with

with(obj1.obj2.obj3) {
  console.log(p1 + p2);
}

// 可以写成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);

用作属性描述符的函数

  • Object.defineProperty(obj, propName, proDesc) :

  • Object.defineProperties(obj, propDescObj)

    var obj = Object.defineProperties({}, {
        foo: {value: 111, enumerable: true},
        bar: {value: 'cdsv', enumerable: true},
    });
  • Object.create(proto, propDescObj?)

    首先根据原型参数创建一个原型为proto 的object,如果给出第二个参数,将此加入到属性参数列表中

    var obj = Object.create(Object.prototype, {
            foo: { value: 123, enumerable: true },
            bar: { value: "abc", enumerable: true }
        });
  • Object.getOwnPropertyDescriptor(obj, propName)

    Returns the descriptor of the own (non-inherited) property of

    obj whose name is propName. If there is no such property, undefined is returned.

        > Object.getOwnPropertyDescriptor(Object.prototype, "toString")
        { value: [Function: toString],
          writable: true,
          enumerable: false,
          configurable: true }
    
        > Object.getOwnPropertyDescriptor({}, "toString")
        undefined

2. eval 命令

eval() 接受一个字符串作为参数,并且将这个字符串参数当做语句运行,如果无法作为语句运行就会报错,同时放在该命令中的字符串应该有独自存在的意义,不可以与其他语句配合使用

eval('var a = 1;');
eval('dsa'); // 无意义会报错
eval('return;'); // 不可以单独使用

注意eval函数没有自己的作用域,都在当前作用域执行,因此可能会修改当前已有变量值,造成安全问题,但是在使用严格模式时,其内部声明的作用域不会影响到外部.但是仍然可以更改外部变量的值

 > "use strict";
'use strict'
> eval('let a = 12;')
undefined
> a
ReferenceError: a is not defined
> b = 123;
123
> b
123
> eval('b = 1');
1

2. Array 数组

类似于python中的列表类型,数组可以存储任意数据类型 ,其类型是Object,是一种特殊类型的,只是每个属性的键值是数字而且是自增的 ,可以先定义后赋值,也可以在定义时赋值。可以使用Object.keys() 函数得到一个数组对象的键值,即为每个元素的下标

进行数组元素读取时可以采用对象的读取方式,但是不可以对数字型键值使用点操作符,只可以使用方括号结构

> let arr1 = [
... {a: 1},
... [1, 2, 3,],
... () => {return true;}]
undefined
> arr1
[ { a: 1 }, [ 1, 2, 3 ], [Function] ]
> arr1[2]
[Function]
> arr1[2]()
true
> arr1[1]
[ 1, 2, 3 ]
> arr1[0]
{ a: 1 }

Array.length 属性

返回数组元素数量,js使用一个32位整数,保存数组的元素个数,所以最大的存储数量为(2^32^ - 1) 只要是数组就有length属性,该值是一个动态值,等于最后一个元素的键值+1

> arr1.length
3
> arr1.length = 2
2
> arr1
[ { a: 1 }, [ 1, 2, 3 ] ]
> arr1.length = 0
0
> arr1
[]

可以使用array.length = 0 将数组清空,将数组length属性人为设置为小于当前元素数量的值,将会自动删除后面多雨的元素,将其设置为不合法的值会报错。

in 运算符

in 运算符可以检查元素是否属于对象,也可以用来判断数组元素的存在情况,但是只能检查键值是否在数组中,可以用来检查某个位置是否为空位。注意length属性不过滤空位,使用delete语句删除数组元素时会将对应的位置设置为empty item 数组长度属性不会改变

读取已经删除的元素时,返回值为undefined ,对于数组中的空位,使用forEach 方法,for ... in 结构, Object.keys() 都会被过滤

> delete bu[2]
true
> bu
[ 1, 23, <1 empty item> ]
> 2 in bu
false
> bu.length
3
> delete bu[0]
true
> bu
[ <1 empty item>, 23, <1 empty item> ]
> 0 in bu
false
> 1 in bu
true
> bu[0]
undefined

for (let a in bu) {
    ...
}

数组的遍历

for... in 循环可以以遍历对象的方式遍历数组,也可以使用for循环,以及forEach() 方法

var a = [1, 2, 3];
a.foo = true;

for (var key in a) {
  console.log(key);
}

var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
  console.log(color);
});

类似数组的对象

如果一个对象的所有键值都为正整数或者0,并且有length 属性,可以将该对象称为类似数组的对象。但是该对象中没有数组的push() pop() 等方法,而且length属性不是动态值,不会随着数组成员的变化而变化

常见的类似数组的对象包括函数的Arguments 对象,以及大多数的dom元素

// arguments对象
function args() { return arguments; }
var arrayLike = args('a', 'b');

arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false

// 字符串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false

可以使用Array.prototype.slice.call(arrayLike) 方法将类似数组的对象转化为数组;也可以通过call() 函数将数组的方法放到对象的上面

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);
// 对于string的类型也可以使用该方式得到进行类似操作,但是使用这种方式比直接使用原声的forEach要慢,所以需要将数组型对象转化为数组

数组方法

添加删除元素:

  • arr.push(...items) 将元素加入到最后,并且返回数组长度

  • arr.pop() 删除并返回最后一个元素

  • arr.shift() 删除并返回第一个元素

  • arr.unshift(...items) 将一个或者多个元素加入数组头部,返回数组长度

  • arr.splice(index[, deleteCount, elm1, elm2, ..., elmN]) : 从下标index的元素开始,删除deleteCount个元素,并且将之后的参数加入到数组中,返回被删除的元素

    此函数可以作为数组操作中的一种瑞士军刀方法,可以满足数组增删改的所有需求

    // delete
    arr.splice(1, 1)
    arr.splice(0)   // 删除全部
    arr.splice(1, 5)
    
    // insert
    arr.splice(0, 0, ..items)  === arr.unshift(..items)
    arr.splice(-1, 0, ..items)   //在倒数第二位插入元素
    arr.splice(1, 3, ...items)   // 删除并且插入元素arr
    
    // edit
    arr.splice(i, i, ..item)
  • arr.slice(start, end)

    复制index位于[start, end)之间的所有元素为新的数组

  • arr.concat(arg1, arg2)

    连接两个数组或者数组和单个元素,返回一个新的数组,注意原数组无变化

数组元素查找

  • arr.indexOf(item, from) :从f索引为from的位置开始向右查找,返回第一个找到的索引

  • arr.lastIndexOf(item, from) 从右开始查找

  • arr.includes(item, from) 是否包含该元素

    需要注意的是index方法使用的是绝对等运算符===,所以如果进行NaN的查找时,由于NaN != NaN

    所以会返回-1,可以使用includes方法

    arr.indexOf(NaN)  // -1
    arr.includes(NaN)  // true
  • find and findIndex

    对于一个对象的数组,使用某些条件对与对象进行查找,可以使用find方法.该方法需要传入一个函数作为参数,返回第一个使得函数返回值为真的元素

    let result = arr.find(function (item, index, array) {
        // 如果找到符合条件的元素则返回true
    });
    // item 为待查找的元素
    // index 为索引
    // array 为原数组

    ```javascript var array1 = [5, 12, 8, 130, 44];

    function findFirstLargeNumber(element) { return element > 13; }

    console.log(array1.findIndex(findFirstLargeNumber)); // expected output: 3

arr [ 'dsad', 44, 423, 'dsad', 'dsad', 'd', 'dsad' ] arr.find((item, index, array) => index == 4); 'dsad' arr.find((item, index) => index == 4); 'dsad' arr.find((item, index, array) => array[index] == 'd'); 'd' arr.find((item, index, array) => array.length == 7); 'dsad' // 恒为真则返回第一个值 ```

  • filter

    filter 方法返回一个使得参数函数返回值为真的元素的数组

    ```javascript let results = arr.filter(function (item, index, array) {

    })

// [ 44, 423, 'dsad', 'dsad', 'd' ]

arr.filter(item => !isNaN(item)) [ 44, 423 ] arr.filter((item, index, array) => array.length == 5); [ 44, 423, 'dsad', 'dsad', 'd' ]

#### **数组元素变换**  

+ `map`

  可以使用map函数对于数组元素进行映射,通过传入一个函数作为参数,进行转化

  ```javascript
  let result = arr.map(function (item, index, array) {

  });

  var array1 = [1, 4, 9, 16];
  // pass a function to map
  const map1 = array1.map(x => x * 2);
  console.log(map1);
  // expected output: Array [2, 8, 18, 32]
  • sort(fn)

    将数组中所有元素作为字符串类型进行排序,注意数字也会转化为字符串类型,对于数字进行排序时需要传入函数进行操作.

    由于数组对象可以包含任意类型的数据,为了便于进行统一的排序操作,将所有类型都看作字符串类型进行操作,其内部实现是一个优化后的快排算法.将会遍历数组每一个元素,使用给定的函数对于数据进行重排.实际上给定的函数只要在a > b 时返回正值,反之返回负值即可

    let arr = [ 1, 2, 15 ];
    
    arr.sort(function(a, b) { return a - b; });
    
    alert(arr);  // 1, 2, 15
  • reverse, split, join

    数组的反向,分割以及拼接

    ```javascript let names = 'Bilbo, Gandalf, Nazgul';

    let arr = names.split(', ');

    for (let name of arr) { alert( A message to ${name}. ); // A message to Bilbo (and other names) } let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); // Bilbo, Gandalf

let str = 'vdsbs' undefined str.split('') [ 'v', 'd', 's', 'b', 's' ] ```

  • resuce/redueRight

    生成方式,根据传入的函数生成相应的值,并且每一个item在调用该函数时可以使用上一个itme调用的返回值,可以实现递归的操作

    let value=  arr.reduce(function (previousValue, item, index, arr)) {
    
                           }, initial);

    参数与find map filter类似,但是多了一个初始值,用于作为第一次调用的值

    let arr = [1, 2, 3, 4, 5];
    let result = arr.reduce((sum, current) => sum + current, 0);
    // 15  求元素累加和
    
    ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
      alert(`${item} is at index ${index} in ${array}`);
    });

Last updated