JavaScript 系列博客(四)

JavaScript 系列博客之(四)

前言

本篇介绍 JavaScript 中的对象。在第一篇博客中已经说到 JavaScript 是一种‘’对象模型‘’语言。所以可以这样说,对象是 JavaScript 语言的核心概念,也是最重要的数据类型。

概述

生成方法

在 JavaScript 中声称对象相当方便,直接定义一个空字典就 ok。想要添加属性或者方法的话可以在定义结束之后动态添加。注意:对象时无序的复合数据集合。

上面代码中,大括号就可以直接定义一个对象,被赋值给变量 a,所以 a 就指向一个对象。该对象为一个空对象,但是会有一些默认的方法,像 constructor 是构造方法,想要动态的添加属性和方法就是这个方法的功劳。

在这里添加了一个属性为name,那么 name 是键名(成员名称),字符串 musibii 是键值(成员的值)。键名与键值之间用冒号分隔。如果再添加一个属性,那么属性之间使用逗号分隔。

具体生成方法

// 1.单一对象
var obj = {
    // 属性
    name: 'Zero',
    // 方法
    teach: function () {
        console.log("教学");
    }
};
obj.name | obj.teach()

// 2.构造函数
function Person(name) {  // 类似于python中的类一样来使用
   	// this代表Person构造函数实例化出的所有具体对象中的某一个
    this.name = name;  
    this.teach = function () {
        console.log(this.name + "正在教学");
    }
}
// ①通过构造函数实例化出具体对象
// ②通过对象.语法调用属性与方法
var p1 = new Person("张三"); 
p1.name  // 张三, this指向p1对象
var p2 = new Person("李四"); 
p2.teach  // 李四正在教学, this指向p2对象

// 3.ES6类语法
class Student {
    // 需要构造器(构造函数)来完成对象的声明与初始化
    constructor (name) {
        // 属性在构造器中声明并完成初始化
        this.name = name;
    }
    // 类中规定普通方法
    study () {
        console.log(this.name + "正在学习");
    }
    // 类方法
    static fn() {
        console.log("我是类方法")
    }
}
// 类中的普通方法由类的具体实例化对象来调用
// 类中的类方法由类直接来调用(这类型的方法大多是功能性方法,不需要对象的存在)

键名

对象的所有键名都是字符串(ES6又引入了 Symbol 值也可以作为键名:还没了解过),所以加不加引号都可以。如果键名是数值,会被自动转为字符串。如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。

对象的每一个键名又称为‘’属性‘’,它的键值可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为方法,调用方法和函数一样。

特别的如果属性的值指向的还是一个对象,那么就行成了链式引用。对象的属性之间用逗号分隔,最后一个属性后面可以加逗号,也可以不加。

对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有的引用。和 JavaScript 中的基本数据类型不一样,复合数据类型是传址传递。

a 和 b指向同一个对象,因此为其中任何一个变量添加属性,另一个变脸都可以读写该属性。如果取消某一个对象的引用,不会影响到其他变量。

这种引用只局限于对象,在之前的博客也提到,两个变量指向同一个原始类型(基本数据类型)的值,那么变量只是对值得拷贝(传值传递)。

属性的操作

属性的读取

读取对象的属性,有两种方法,一种是使用点运算符;另一种是使用方括号运算符。(在 python 中,字典只能通过方括号取值;对象只可以通过点运算符取值。不过可以通过自定义字典类改写 getattr 魔术方法改变。)

注意:如果使用方括号运算符,键名必须放在引号里面,否则会被当做变量处理。

var foo = 'bar';

var obj = {
    foo: 1,
    bar: 2
};

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

上面代码中,引用对象obj 的 foo 属性时,如果使用点运算符,foo 就是字符串;如果使用方括号运算符,但是不使用引号,那么 foo 就是一个变量,指向字符串 bar。

方括号运算符内部还可以使用表达式:

obj['hello' + 'world']
obj[3 + 3]

数字键可以不加引号,因为会自动转为字符串。

var obj = {
    0.7: 'hello world'
};
obj['0.7'] // 'Hello World'
obj[0.7] // 'Hello World'

上面代码对象的数字键0.7加不加引号都可以,因为会自动转为字符串。

var obj = {
    123: 'Hello musibii'
};

obj.123 // 报错
obj[123] // 'Hello musibii'

如果对数值键名123使用点运算符,会报错,使用方括号运算符才是正确的方式。

属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

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

属性的查看

查看一个对象的所有属性使用 Object.keys 方法。

var obj = {
    key1: 1,
    key2: 2
};

Object.keys(obj); // [key1, key2]

属性的删除

delete 命令用于删除对象的属性,删除成功后返回 true。

var obj = {
    p: 1;
}

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

上述代码中,delete 命令删除对象 obj 的 p 属性。删除后,再读取 p 属性就会返回 undefined,而且 Object.keys 方法的返回值也不再包括该属性。

注意:删除一个不存在的属性,delete 不会报错而是返回 true。因此不能根据 delete 命令的结果认为某个属性的存在。(那么到底哪种方式才可以证明某个属性的存在与否)

如果删除属性时返回 false那就说明该属性存在,但是不可以删除。

var obj = Object.defineProperty({}, 'p', {
    value: 'musibii',
    configurable: false
});

obj.p // 'musibii'
delete obj.p // false

上述代码中,通过 Object 的defineProperty方法给对象 obj创建了一个属性,属性的configurable(可配置) 的值为 false,这样的一个属性就是不可以删除的。

另外需要注意的是,delete 命令只能删除对象本身的属性,无法删除继承的属性。

可以看出虽然 delete 命令返回 true,但是删除的属性依然存在。但是如果通过 proto 删除的话就可以删除。

判断属性的存在

in 运算符用于检出对象是否包含某个属性(注意,检查的是键名,不是键值)。如果包含就返回 true,否则就返回 false。它的左边是一个字符串,表示属性名,右边则是一个对象。

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

拿上面删除的 constructor 来说:

in 运算符的一个问题是,它不能识别哪些属性时对象自身的,哪些属性是继承的。就像上面,对象 obj 本身并没有 toString 属性,但是 in 运算符会返回 true,因为这个属性是继承的。

这时可以通过对象的 hasOwnProperty 方法判断,是否为对象自身属性

var obj = {};
if ('toString' in obj) {
    console.log(obj.hasOwnProperty('toString')); // fasle
}

属性遍历

for...in 循环用来遍历一个对象的所有属性。

for...in 循环有两个注意点;

  • 它遍历的是对象所有可遍历的属性,会跳过不可遍历的属性;
  • 它不仅遍历对象自身的属性,还遍历继承的属性。

如果继承的属性是可遍历的,那么就会被 for...in 循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用 for...in 的时候,应该结合使用 hasOwnProperty 方法,在循环内部判断一下,某个属性是否为对象自身的属性。

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

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

with 语句

with 语句的格式如下:

with (对象) {
    语句;
}

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

注意:如果 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);
posted @ 2018-12-22 16:55  rsuxwvilc  阅读(489)  评论(0编辑  收藏  举报