javascript对象详解以及call、apply和bind的使用

来源:互联网 发布:php怎么连接数据库 编辑:程序博客网 时间:2024/04/29 20:21

因为项目需要用到javascript,boss说我以前是写Java的,写javascript上手快...只怪当时too young,于是误打误撞走进了javascript的世界。殊不知一入js深似海,更不知js比之Java就像雷峰塔比之雷锋、印度尼西亚比之印度、老婆饼比之老婆...


提起面向对象编程语言,往往想起的是C++、Java等强类型静态语言,以及Python等脚本语言。其中尤以Java为甚,Java is pure object oriented ,相较于C++,Java的main函数甚至都在一个类里面。这些语言都有一个共同点——他们都是基于的面向对象。而提到javascript,很多人会怀疑它的面向对象特性,甚至认为javascript不是一门面向对象的语言,因为javascript没有类。事实上,javascript确实没有类,但javascript有对象,甚至只有对象,javascript的对象不是类的实例。大部分程序设计语言的面向对象都是基于类的,以至于人们形成一种惯性思维:对象都是类的实例。在javascript中这样的经验却是不适用的,所以我们更愿意称javascript是一门基于对象的语言。

I 创建

javascript对象实际上是一个由属性和值组成的关联数组。什么是关联数组呢?相较于平常意义上用数字作为索引的数组,关联数组可以用字符串作为索引。类似于键值对,对象的属性就是“键”,对象的值就是“值”咯。对象的值可以是任何数据类型,或者函数和其他对象。为什么对象的值可以是函数呢?说来话长,50多年前诞生了一种叫做函数式编程的东东(区别于命令式编程,与面向对象式编程更是风马牛不相及),函数式编程有很坚固的数学基础,跟什么lambda演算有关啦,一般人都不会懂的啦。我们javascript具有函数式编程的特点,所以函数是"第一等公民" !所谓"第一等公民",指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值,这些特性在C++和纯正的Java(之所以说纯正,是为了区别于scala等)里面是不可以想象的吧。好像扯远了,总之就是表达一个意思:函数可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

我们可以用以下代码创建一个简单的对象:

var obj = {};//创建一个空对象obj.prop1 = 'hey';//给第一个属性赋值obj.prop2 = true;obj.prop3 = function (str) { //函数也可以赋值给一个变量    return str;};console.log(obj.prop3('hello world'));//输出hello world

以上代码中,我们通过var obj = { };创建出一个对象并将其引用赋值给obj,随后通过obj.prop1 来获取其成员并赋值。除了通过var obj = { };创建一个对象,还可以用显式地用var obj=new Object();来创建一个对象。

既然上面已经提到javascript对象实质上就是一个关联数组,我们就来看看怎么用关联数组的方式创建对象,修改以上代码:

var obj = {};//创建一个空对象obj['prop1'] = 'hey';//使用关联数组引用赋值obj['prop2'] = true;obj['prop3'] = function (str) { //函数也可以赋值给一个变量    return str;};console.log(obj.prop3('hello world'));//输出hello world
在javascript中,使用句点运算符和关联数组引用是等价的,使用关联数组的的一个好处是,在我们不知道对象的属性名称时,可以用变量作为关联数组的索引。例如:
var someProp = 'prop4';obj[prop4] = 100;
通过以上代码你应该已经了解了如何去创建一个对象,但是在实际使用时,难免不会觉得繁琐冗杂。更加紧凑明了的方式如下:

var obj = {    prop1: 'hey',    prop2: true,    prop3: function (str) {        return str;    }};


II 构造函数

前一节介绍的创建对象的方法可能让写惯了C++、Java的你大开眼界,对象竟然还能不靠构造函数来生成。仔细想想,我们可以察觉出前面方法的弱点:如果我们想批量地创建对象,能初始化若干固定好的属性、方法呢?不用担心,javascript为我们提供了构造函数,让我们看看如何用构造函数创建一个对象出来:

function Person(name, age) {    this.name = name;    this.age = age;    this.introduceMyself = function () {        console.log(this.name);    };}

以上就是一个简单的构造函数,我们就可以用new语句来创建对象了:

var me = new Person("chendotjs", 21);
接着就可以通过me访问该对象的方法和属性了。

III this指针

首先需要更正的是:在javascript里面并没有像C++一样的指针概念,这里所谓的this指针只是沿用一种传统的称法。javascript里的this“指针”和Java里面的this“指针”是很类似的。javascript里面任何函数的运行一定是被某个对象(包括全局变量)调用,而this指针就指向该对象,或者准确点说,是指向该对象的一个引用。来看一个例子:

var user1 = {    name: 'user1',    display: function () {        console.log('I\'m ' + this.name);    }};var user2 = {    name: 'user2',    func: user1.display};user1.display();//输出I'm user1user2.func();//输出I'm user2name = 'global';func = user1.display;func();//输出I'm global
javascript的函数式编程特性使得函数可以像一般的变量一样赋值、传递。在上面的代码中,user2的func属性和global的func属性都是user1.display。当调用user1.display();、user2.func();和func();时,虽然调用的是同一个函数,但是this指针不属于任何一个函数,而取决于函数被调用时所属的对象。

事实上,在以上代码中,user1.display、user2.func和func是指向同一个函数实体的3个引用,引用之间的赋值不会复制出新的对象。这涉及到了“深拷贝和浅拷贝”的内容,将放在下一篇详细讨论。

关于this指针还有3个有趣的函数:call、apply以及bind,下面就介绍一下这3个函数:

1.call和apply

call和apply的功能较为相似,简而言之,就是允许一个对象去调用另一个对象的成员函数。call和apply的用法分别如下:
fun.call(thisArg[, arg1[, arg2[, ...]]])fun.apply(thisArg[,argsArray])
其中,fun是函数的引用,thisArg是fun调用时的this指针指向的对象,arg1、arg2是argsArray是传给fun的参数。我们来看一个例子:
var user1 = {    name: 'user1',    display: function (food) {        console.log(this.name + ' eats ' + food);    }};var user2 = {    name: 'user2'};user1.display('apples');//输出user1 eats applesuser1.display.call(user2, 'pears');//输出user2 eats pearsuser1.display.apply(user2, ['pears']);//输出user2 eats pears

2.bind

如果每次都用call和apply会显得不方便,因为必须传递固定的参数。针对这种情况,可以使用bind永久绑定this指针指向的对象。bind的语法如下:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
其中,fun是待绑定的函数,thisArg是fun调用时的this指针指向的对象,arg1、arg2是argsArray是传给fun的参数。bind方法的返回值是绑定了thisArg的fun,这点和call、apply是不同的。看一个例子:
var user1 = {    name: 'user1',    display: function () {        console.log('I\'m ' + this.name);    }};var user2 = {    name: 'user2'};user2.func1 = user1.display;user2.func1();//输出I'm user2user2.func2 = user1.display.bind(user1);user2.func2();//输出I'm user1name = 'global';func = user1.display.bind(user2);func();//输出I'm user2
在直接将user1.display赋值给user2.func1后,调用user2.func1()时,this指针指向user2,所以输出结果为“I'm user2”。user2.func2 使用了bind方法,将user1作为this指针绑定到user2.func2 ,调用user2.func2 ()时,this指针指向user1,所以输出为“I'm user1”。全局函数func也是同样的道理,这里省略具体分析。


bind还有一个重要的功能是绑定参数列表,如下例所示:
var user1 = {    name: 'user1',    display: function (act, sth) {        console.log(this.name + ' ' + act + ' ' + sth);    }};var user2 = {    name: 'user2'};user1.display('eats', 'food');//输出user1 eats foodvar func = user1.display.bind(user2, 'eats');func('apples');//输出user2 eats apples
以上代码中,func函数将this指针绑定到user2,并将第一个参数绑定为‘eats’,之后在调用func时,只需要传入第三个参数即可。仔细想想,这个特性有很大的用处,通过这个特性可以在多次调用函数时,省略重复输入相同的参数。

3.bind的原理

我们可以尝试运行以下代码:
var user1 = {    name: 'user1',    display: function () {        console.log('I\'m ' + this.name);    }};var user2 = {    name: 'user2'};func1 = user1.display.bind(user2);func1();func2 = func1.bind(user1);func2();

运行出来的结果:
I'm user2
I'm user2

全局函数func1将this指针绑定到user2,调用func1()输出I'm user2是理所当然的事。当我们试图将func2赋值为“绑定this指针为user1的func1”,再调用func2,却发现输出仍然是I'm user2,即this指针仍然指向user2,这是个什么原因呢?
要想明白其中奥妙,还得看bind是怎么实现的。下面是我写的一个简化版的bind方法(不支持参数绑定):
fun.bind=function(obj){    var method=this;    return function(){        method.call(obj);    }}
需要注意的是,函数体中的this指针指向的是fun,因为切记:函数也是对象。以这个为基础,我们再来修改上一段代码:
var user1 = {    name: 'user1',    display: function () {        console.log('I\'m ' + this.name);    }};var user2 = {    name: 'user2'};bind_byme = function (obj) {    var method = this;    return function () {        method.call(obj);    }};user1.display.bind_byme = bind_byme;//给user1.display加上自己写的bindfunc1 = user1.display.bind_byme(user2);func1();func1.bind_byme = bind_byme;//给func1加上自己写的bindfunc2 = func1.bind_byme(user1);func2();
将自己作为编译器去解释以上代码大笑,得到以下结果:
func1=function(){    user1.display.call(user2);};func2=function(){    func1.call(user1);};
以上结果就很明了了,因为func1根本没有使用this指针,所以func2的bind函数根本不起作用,所以两次绑定是没有意义的。



0 0
原创粉丝点击