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

因为项目需要用到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 user1
user2.func();//输出I'm user2

name = 'global';
func = user1.display;
func();//输出I'm global
j avascript的函数式编程特性使得函数可以像一般的变量一样赋值、传递。在上面的代码中,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 apples
user1.display.call(user2, 'pears');//输出user2 eats pears
user1.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 user2

user2.func2 = user1.display.bind(user1);
user2.func2();//输出I'm user1

name = '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 food

var 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加上自己写的bind

func1 = user1.display.bind_byme(user2);
func1();

func1.bind_byme = bind_byme;//给func1加上自己写的bind

func2 = func1.bind_byme(user1);
func2();
将自己作为编译器去解释以上代码 大笑,得到以下结果:
func1=function(){
    user1.display.call(user2);
};
func2=function(){
    func1.call(user1);
};
以上结果就很明了了,因为func1根本没有使用this指针,所以func2的bind函数根本不起作用,所以两次绑定是没有意义的。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值