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 是没有效果的。
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 最后一次说说闭包
- Ajax
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子
- JavaScript 各种遍历方式详解
- call/apply/bind 的理解与实例分享