黄子涵

5.17 对象与数据类型

对于基于类的程序设计语言,对象的类型是由作为模型的类以及其对其进行实现的接口共同决定的。而在 JavaScript 中,不存在这种意义上的对象类型的概念。这是因为在 JavaScript 中根本就不存在类与接口的概念。不过从原理上来说,对象类型的概念与对象的操作这一概念存在不少共性,由此,也可以认为在 JavaScript 中其实是存在对象类型的概念的。

首先需要说明的是如何判断明确的对象类型,也就是判断基本类型。这种判断可以通过 typeof 运算符实现。

对于 Object 类型,typeof 运算的结果是字符串值 "object"。

var hzh = {name: "黄子涵"};
console.log("Object类的数据类型是:");
console.log(typeof hzh);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
Object类的数据类型是:
object

[Done] exited with code=0 in 0.336 seconds

JavaScript 的语言规范没有对 Object 类型进一步细分。开发者可以根据自己的需要,设计出任意的具有共同执行方式的对象。面向对象技术的核心思想之一就是要考虑对象的操作,所以在面向对象程序设计的过程中必须重视这个问题。

5.17.1 数据类型判定(constructor 属性)

可以通过使用对象的 constructor 属性来从对象处获取其构造函数。如果能获知对象的构造函数,也就能够知道该对象的原型继承情况了,于是便可以了解这个对象的一部分操作。虽然 JavaScript 并不适合以基于类的观点来分析,不过可以姑且认为代码清单 5.15 中代码的含义是对对象的类进行确认。

代码清单 5.15  通过 constructor 属性判断类型的例子
var hzhD = new Date();
console.log("使用constructor属性判断hzhD:");
console.log(hzhD.constructor);    // 对象 hzhD 的 constructor 属性引用了 Date
console.log("");
var hzhArr = [1, 2, 3];
console.log("使用constructor属性判断hzhArr:");
console.log(hzhArr.constructor);  // 对象 hzhArr 的 constructor 属性引用了 Array
console.log("");
var hzh = {};
console.log("使用constructor属性判断hzh:");
console.log(hzh.constructor);     // 通过字面量生成的对象的 constructor 属性引用了 Object
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用constructor属性判断hzhD:
[Function: Date]

使用constructor属性判断hzhArr:
[Function: Array]

使用constructor属性判断hzh:
[Function: Object]

[Done] exited with code=0 in 0.181 seconds

5.17.2 constructor 属性的注意点

constructor 属性不是对象的直接属性,而是通过原型链查找到的属性。因此,下面这段模拟了派生继承的代码,实现了相当于基类 Base 的功能。不过需要注意的是,它并不一定总能按照设想的方式执行功能。

function Derived() {};   // 该构造函数相当于派生类
function Base() {};      // 该构造函数相当于基类
Derived.prototype = new Base();

var hzh = new Derived(); //通过 Derived 构造函数生成 object 对象
console.log("使用constructor属性判断hzh数据类型:");
console.log(hzh.constructor);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用constructor属性判断hzh数据类型:
[Function: Base]

[Done] exited with code=0 in 0.183 seconds

与 obj.constructor 的原型链相连的实体是 Derived.prototype.constructor。

function Derived() {};   // 该构造函数相当于派生类
function Base() {};      // 该构造函数相当于基类
Derived.prototype = new Base();

var hzh = new Derived(); //通过 Derived 构造函数生成 object 对象
console.log("判断hzh.constructor和Derived.prototype.constructor是否严格相等:");
console.log(hzh.constructor === Derived.prototype.constructor);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
判断hzh.constructor和Derived.prototype.constructor是否严格相等:
true

[Done] exited with code=0 in 0.196 seconds

所以只需像下面这样对其进行显式的修改,就可以使这类派生继承获得期望的结果。

function Derived() {};        // 该构造函数相当于派生类
function Base() {};           // 该构造函数相当于基类
Derived.prototype = new Base();

var hzh = new Derived();      //通过 Derived 构造函数生成 object 对象
Derived.prototype.constructor = Derived;
console.log("使用constructor属性判断hzh数据类型:");
console.log(hzh.constructor); // 对象 hzh的 constructor 属性引用了 Derived
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用constructor属性判断hzh数据类型:
[Function: Derived]

[Done] exited with code=0 in 0.199 seconds

5.17.3 数据类型判定(instance 运算与 isPrototypeOf 方法)

虽然也可以通过 constructor 属性来判断对象类型,不过更为常见的做法是使用 instanceof 运算来进行判断。具体方法为在运算符左侧书写对象引用,在右侧书写相应的构造函数。如果对象是通过右侧的构造函数生成的,则运算结果为真。对于通过原型链进行派生继承的情况,instanceof 运算也是有效的。

代码清单 5.16 是个具体例子。

代码清单 5.16 通过 instanceof 运算来判断类型的例子
var hzhD = new Date(); // 通过 Date 构造函数生成对象 hzhD
console.log("使用instanceof运算符来判断hzhD是否属于Date:");
console.log(hzhD instanceof Date);
console.log("");
console.log("使用instanceof运算符来判断hzhD是否属于Object:");
console.log(hzhD instanceof Object);
console.log("");
function Derived() {} // 该构造函数相当于派生类
function Base() {}    // 该构造函数相当于基类
Derived.prototype = new Base();

var hzh = new Derived();
console.log("使用instanceof属性判断hzh对象是否属于Derived:");
console.log(hzh instanceof Derived);
console.log("");
console.log("使用instanceof属性判断hzh对象是否属于Base:");
console.log(hzh instanceof Base);
console.log("");
console.log("使用instanceof属性判断hzh对象是否属于Object:");
console.log(hzh instanceof Object);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用instanceof运算符来判断hzhD是否属于Date:
true

使用instanceof运算符来判断hzhD是否属于Object:
true

使用instanceof属性判断hzh对象是否属于Derived:
true

使用instanceof属性判断hzh对象是否属于Base:
true

使用instanceof属性判断hzh对象是否属于Object:
true

[Done] exited with code=0 in 0.197 seconds

可以通过 Object 类的 isPrototypeOf 方法来确认原型对象,该方法将搜索原型链。

代码清单 5.16 sPrototypeOf 方法的例子
var hzhD = new Date(); // 通过 Date 构造函数生成对象 hzhD
function Derived() {} // 该构造函数相当于派生类
function Base() {}    // 该构造函数相当于基类
Derived.prototype = new Base();

var hzh = new Derived();
console.log("使用isPrototypeOf方法判断hzh对象是否属于Derived:");
console.log(Derived.prototype.isPrototypeOf(hzh));
console.log("");
console.log("使用isPrototypeOf方法判断hzh对象是否属于Base:");
console.log(Base.prototype.isPrototypeOf(hzh));
console.log("");
console.log("使用isPrototypeOf方法判断hzh对象是否属于Object:");
console.log(Object.prototype.isPrototypeOf(hzh));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用isPrototypeOf方法判断hzh对象是否属于Derived:
true

使用isPrototypeOf方法判断hzh对象是否属于Base:
true

使用isPrototypeOf方法判断hzh对象是否属于Object:
true

[Done] exited with code=0 in 0.174 seconds

5.17.4 数据类型判定(鸭子类型)

如果类与对象(实例)之间只是静态关系,只需通过 instanceof 运算就能够解决所有的对象类型判断问题。然而,在 JavaScript 中,对象是一种动态的概念。例如,像下面这样,为生成的对象新增属性或不通过构造函数生成对象,都很常见。

var hzh = {}                   // 生成空对象
hzh.huangzihan = function() {  // 新增属性
    console.log("黄子涵");
}
console.log("输出hzh对象:");
console.log(hzh);
console.log("");
console.log("输出hzh对象的huangzihan方法:");
console.log(hzh.huangzihan);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh对象:
{ huangzihan: [Function] }

输出hzh对象的huangzihan方法:
[Function]

[Done] exited with code=0 in 0.205 seconds
// 不通过构造函数生成对象
var hzh = { huangzihan: function() {
    console.log("黄子涵");
}}
console.log("输出hzh对象:");
console.log(hzh);
console.log("");
console.log("输出hzh对象的huangzihan方法:");
console.log(hzh.huangzihan);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh对象:
{ huangzihan: [Function: huangzihan] }

输出hzh对象的huangzihan方法:
[Function: huangzihan]

[Done] exited with code=0 in 1.098 seconds

在上面这个对象中有一个名为 huangzihan 的方法。对象所拥有的方法是用于描述对象操作的重要指标,然而一个方法是否存在却是无法通过 constructor 属性或 instanceof 运算来判断的。

判断在对象中含有哪些属性,是比 instanceof 运算更为普遍的类型判断方式。这种直接分析对象的操作以判断其类型的方法俗称为鸭子类型判断。

in 运算是一种可以用于判断鸭子类型的方法。in 运算需要在运算符左侧书写属性名字符串,在右侧指定对象的引用。如果对象拥有所指定的属性,运算结果即为真。对于通过原型链继承的属性,也能够通过这一方式判断。

// 不通过构造函数生成对象
var hzh = { huangzihan: function() {
    console.log("黄子涵");
}}
console.log("判断huangzihan方法是否在对象hzh中:");
console.log('huangzihan' in hzh);
console.log("");
console.log("判断toString方法是否在对象hzh中:");
console.log('toString' in hzh);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
判断huangzihan方法是否在对象hzh中:
true

判断toString方法是否在对象hzh中:
true

[Done] exited with code=0 in 0.335 seconds

5.17.5 属性的枚举(原型继承的相关问题)

for in 语句、for each in 语句以及 in 语句都会对原型链进行搜索。对于类型判断来说,搜索原型链是一种方便的做法。不过,我们有时仅希望对直接属性是否存在进行判断,并不需要对原型链进行搜索。这时,可以使用 hasOwnProperty 方法。下面是个具体例子。

// 仅列举直接属性的代码示例
var hzh = {
    name: "黄子涵",
    tall: "177cm",
    tel: 19124896017,
    brother: '黄春钦',
    mother: '陈兰英'
}

for (var key in hzh) {
    if (hzh.hasOwnProperty(key)) {
        console.log(key);
    }
}
[Running] node "e:\HMV\JavaScript\JavaScript.js"
name
tall
tel
brother
mother

[Done] exited with code=0 in 0.334 seconds

下面是 Object 类的 keys 方法与 getOwnPropertyNames 方法的具体例子。

// 仅列举直接属性的代码示例
var hzh = {
    name: "黄子涵",
    tall: "177cm",
    tel: 19124896017,
    brother: '黄春钦',
    mother: '陈兰英'
}

console.log("使用keys方法输出hzh的属性:");
console.log(Object.keys(hzh));
console.log("");
console.log("使用getOwnPropertyNames方法输出hzh的属性:");
console.log(Object.getOwnPropertyNames(hzh));
console.log("");
// 数组也是一种对象
var hzhArr = [
    "黄子涵",
    "177cm",
    19124896017,
    '黄春钦',
    '陈兰英'
]
console.log("使用keys方法遍历hzh数组的元素:");
console.log(Object.keys(hzhArr));
console.log("");
console.log("使用getOwnPropertyNames方法遍历hzh数组的元素:");
console.log(Object.getOwnPropertyNames(hzhArr));
console.log("");
// 对 Object.prototype 对象的考察
console.log("使用keys方法对Object.prototype对象的考察:");
console.log(Object.keys(Object.prototype));
console.log("");
console.log("使用getOwnPropertyNames方法对Object.prototype对象的考察:");
console.log(Object.getOwnPropertyNames(Object.prototype));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用keys方法输出hzh的属性:
[ 'name', 'tall', 'tel', 'brother', 'mother' ]

使用getOwnPropertyNames方法输出hzh的属性:
[ 'name', 'tall', 'tel', 'brother', 'mother' ]

使用keys方法遍历hzh数组的元素:
[ '0', '1', '2', '3', '4' ]

使用getOwnPropertyNames方法遍历hzh数组的元素:
[ '0', '1', '2', '3', '4', 'length' ]

使用keys方法对Object.prototype对象的考察:
[]

使用getOwnPropertyNames方法对Object.prototype对象的考察:
[
  'constructor',
  '__defineGetter__',
  '__defineSetter__',
  'hasOwnProperty',
  '__lookupGetter__',
  '__lookupSetter__',
  'isPrototypeOf',
  'propertyIsEnumerable',
  'toString',
  'valueOf',
  '__proto__',
  'toLocaleString'
]

[Done] exited with code=0 in 3.269 seconds

顺带一提,对于属性的 enumerable 属性可以通过propertyIsEnumerable 方法来判断。

posted @ 2022-05-29 17:07  黄子涵  阅读(55)  评论(0编辑  收藏  举报