您的位置:首页 > Web前端 > JavaScript

JS对象详解

2016-04-17 12:51 525 查看

对象

   提起面向对象的程序设计语言,立刻让人想起的是 C++、 Java 等这类静态强类型语言,

以及 Python、 Ruby 等脚本语言,它们共有的特点是基于类的面向对象。而说到 JavaScript,

很少能让人想到它面向对象的特性,甚至有人说它不是面向对象的语言,因为它没有类。没

错, JavaScript 真的没有类,但 JavaScript 是面向对象的语言。 JavaScript 只有对象,对象就

是对象,不是类的实例。

  因为绝大多数面向对象语言中的对象都是基于类的,所以经常有人混淆类的实例与对象

的概念。对象就是类的实例,这在大多数语言中都没错,但在 JavaScript 中却不适用。

JavaScript 中的对象是基于原型的,因此很多人在初学 JavaScript 对象时感到无比困惑。通过

这一节,我们将重新认识 JavaScript 中对象,充分理解基于原型的面向对象的实质。

创建和访问

  JavaScript 中的对象实际上就是一个由属性组成的关联数组,属性由名称和值组成,值

的类型可以是任何数据类型,或者函数和其他对象。注意 JavaScript 具有函数式编程的特性,

所以函数也是一种变量,大多数时候不用与一般的数据类型区分。

在 JavaScript 中,你可以用以下方法创建一个简单的对象:

var foo = {};
foo.prop_1 = 'bar';
foo.prop_2 = false;
foo.prop_3 = function() {
return 'hello world';
}
console.log(foo.prop_3());


  以上代码中,我们通过 var foo = {}; 创建了一个对象,并将其引用赋值给 foo,

通过 foo.prop1 来获取它的成员并赋值,其中 {} 是对象字面量的表示方法,也可以用 var

foo = new Object() 来显式地创建一个对象。

1. 使用关联数组访问对象成员

我们还可以用关联数组的模式来创建对象,以上代码修改为:

var foo = {};
foo['prop1'] = 'bar';
foo['prop2'] = false;
foo['prop3'] = function() {
return 'hello world';
}


在 JavaScript 中,使用句点运算符和关联数组引用是等价的,也就是说任何对象(包括

this 指针)都可以使用这两种模式。使用关联数组的好处是,在我们不知道对象的属性名

称的时候,可以用变量来作为关联数组的索引。例如:

var some_prop = 'prop2';
foo[some_prop] = false;


2. 使用对象初始化器创建对象

上述的方法只是让你对JavaScript对象的定义有个了解,真正在使用的时候,我们会采用

下面这种更加紧凑明了的方法:

var foo = {
'prop1': 'bar',
prop2: 'false',
prop3: function (){
return 'hello world';
}
};


这种定义的方法称为对象的初始化器。注意,使用初始化器时,对象属性名称是否加引

号是可选的,除非属性名称中有空格或者其他可能造成歧义的字符,否则没有必要使用引号.

上下文对象

var someuser = {
name: 'byvoid',
display: function() {
console.log(this.name);
}
};
someuser.display(); // 输出 byvoid
var foo = {
bar: someuser.display,
name: 'foobar'
};
foo.bar(); // 输出 foobar


  JavaScript 的函数式编程特性使得函数可以像一般的变量一样赋值、传递和计算,我们

看到在上面代码中, foo 对象的 bar 属性是 someuser.display 函数,使用 foo.bar()

调用时, bar 和 foo 对象的函数看起来没有区别,其中的 this 指针不属于某个函数,而

是函数调用时所属的对象。

  在 JavaScript 中,本质上,函数类型的变量是指向这个函数实体的一个引用,在引用之

间赋值不会对对象产生复制行为。我们可以通过函数的任何一个引用调用这个函数,不同之

处仅仅在于上下文。下面例子可以帮助我们理解

var someuser = {
name: 'byvoid',
func: function() {
console.log(this.name);
}
};
var foo = {
name: 'foobar'
};
someuser.func(); // 输出 byvoid
foo.func = someuser.func;
foo.func(); // 输出 foobar
name = 'global';
func = someuser.func;
func(); // 输出 global


  仔细观察上面的例子,使用不同的引用来调用同一个函数时, this 指针永远是这个引

用所属的对象。在前面的章节中我们提到了 JavaScript 的函数作用域是静态的,也就是说一

个函数的可见范围是在预编译的语法分析中就可以确定的,而上下文对象则可以看作是静态

作用域的补充。

call和apply

call 和 apply 的功能是一致的,两者细微的差别在于 call 以参数表来接受被调用函

数的参数,而 apply 以数组来接受被调用函数的参数。 call 和 apply 的语法分别是:

func.call(thisArg[, arg1[, arg2[, …]]])

func.apply(thisArg[, argsArray])

其中, func 是函数的引用, thisArg 是 func 被调用时的上下文对象, arg1、 arg2 或

argsArray 是传入 func 的参数。我们以下面一段代码为例介绍 call 的工作机制:

var someuser = {
name: 'byvoid',
display: function(words) {
console.log(this.name + ' says ' + words);
}
};
var foo = {
name: 'foobar'
};
someuser.display.call(foo, 'hello'); // 输出 foobar says hello


用 Node.js 运行这段代码,我们可以看到控制台输出了 foobar。someuser.display 是

被调用的函数,它通过 call 将上下文改变为 foo 对象,因此在函数体内访问 this.name

时,实际上访问的是 foo.name, 因而输出了foobar.

bind

 如何改变被调用函数的上下文呢?前面说过,可以用 call 或 apply 方法,但如果重复

使用会不方便,因为每次都要把上下文对象作为参数传递,而且还会使代码变得不直观。针

对这种情况,我们可以使用 bind 方法来永久地绑定函数的上下文,使其无论被谁调用,上

下文都是固定的。 bind 语法如下:

func.bind(thisArg[, arg1[, arg2[, …]]])

  其中 func 是待绑定函数, thisArg 是改变的上下文对象, arg1、 arg2 是绑定的参

数表。 bind 方法返回值是上下文为 thisArg 的 func。通过下面例子可以帮你理解 bind

的使用方法:

var someuser = {
name: 'byvoid',
func: function() {
console.log(this.name);
}
};
var foo = {
name: 'foobar'
};
foo.func = someuser.func;
foo.func(); // 输出 foobar
foo.func1 = someuser.func.bind(someuser);
foo.func1(); // 输出 byvoid
func = someuser.func.bind(foo);
func(); // 输出 foobar
func2 = func;
func2(); // 输出 foobar


  上面代码直接将 foo.func 赋值为 someuser.func,调用 foo.func() 时, this指

针为 foo,所以输出结果是 foobar。 foo.func1 使用了 bind 方法,将 someuser 作

为this指针绑定到 someuser.func,调用 foo.func1() 时, this指针为 someuser,

所以输出结果是 byvoid。全局函数 func 同样使用了 bind 方法,将 foo 作为 this 指

针绑定到 someuser.func,调用 func() 时, this 指针为 foo,所以输出结果是 foobar。

而 func2 直接将绑定过的 func 赋值过来,与 func 行为完全相同。

使用 bind 绑定参数表

bind 方法还有一个重要的功能:绑定参数表,如下例所示。

var person = {
name: 'byvoid',
says: function(act, obj) {
console.log(this.name + ' ' + act + ' ' + obj);
}
};
person.says('loves', 'diovyb'); // 输出 byvoid loves diovyb
byvoidLoves = person.says.bind(person, 'loves');
byvoidLoves('you'); // 输出 byvoid loves you


可以看到, byvoidLoves 将 this 指针绑定到了 person,并将第一个参数绑定到

loves,之后在调用 byvoidLoves 的时候,只需传入第三个参数。这个特性可以用于创建

一个函数的“捷径”,之后我们可以通过这个“捷径”调用,以便在代码多处调用时省略重

复输入相同的参数。

理解 bind

尽管 bind 很优美,还是有一些令人迷惑的地方,例如下面的代码:

var someuser = {
name: 'byvoid',
func: function () {
console.log(this.name);
}
};
var foo = {
name: 'foobar'
};
func = someuser.func.bind(foo);
func(); // 输出 foobar
func2 = func.bind(someuser);
func2(); // 输出 foobar


全局函数 func 通过someuser.func.bind将this指针绑定到了foo,调用func()输

出了foobar。我们试图将func2赋值为已绑定的func重新通过bind将this指针绑定到

someuser的结果,而调用func2时却发现输出值仍为foobar,即 this 指针还是停留在 foo

对象上,这是为什么呢?要想解释这个现象,我们必须了解 bind 方法的原理。

让我们看一个 bind 方法的简化版本(不支持绑定参数表):

someuser.func.bind = function(self) {
return this.call(self);
};


假设上面函数是 someuser.func 的 bind 方法的实现,函数体内 this 指向的是

someuser.func,因为函数也是对象,所以 this.call(self) 的作用就是以 self 作为

this指针调用 someuser.func。

//将func = someuser.func.bind(foo)展开:
func = function() {
return someuser.func.call(foo);
};
//再将func2 = func.bind(someuser)展开:
func2 = function() {
return func.call(someuser);
};
从上面展


开过程我们可以看出, func2 实际上是以 someuser 作为 func 的this指

针调用了 func,而 func 根本没有使用 this 指针,所以两次 bind 是没有效果的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript