js中,可以说万物皆对象(object),一个数组也是一个对象(array)。
很多对象都有很多很方便的方法 比如数组的push,concat,slice等等,但是如果一些对象,它没有实现这些方法,我们还是想使用这些功能。那该怎么办呢?
1、很多方法都提供了非常高效的实现, 我们可以仿照它们的实现。
比如ie8以下的浏览器不支持array的indexof方法,为了让数组支持indexof,我们可以自己写一个方法,实现indexof方法:
(用ie浏览器调试 按f12,可以选择浏览器版本到ie5。)
var arr = [, , , ];if (array.prototype.indexof) {alert(你的浏览器支持indexof方法。);} else {alert(你的浏览器不支持indexof方法。);}if (!array.prototype.indexof) {array.prototype.indexof = function(item) {for (var i = ; i >> ;var from = number(arguments[]) || ;from = (from < )? math.ceil(from): math.floor(from);if (from < )from += len;for (; from < len; from++){if (from in this &&this[from] === elt)return from;}return -;};}
2、继承——call和apply方法
如果我们每有一个对象,那每个对象就要自己写一遍实现是不是很麻烦?
在高级语言中,我们可以用继承来解决问题,比如下面的java代码:
public class mylist extends arraylist{public void myadd(e e){super.add(e);system.out.println(add:+e);}}
但是js中没有继承的概念啊,我们可以用call和apply来解决这样的问题。
上面的代码就可以改写为:
var myobject = function(){}myobject.prototype.add = function(){array.prototype.push.call(this,arguments);//输出argumentsfor(var i=;i
(ps,其实在java中可以写public void myadd(e... e),这个是不定参数,用法上public void myadd(e[] e)是一样的)
call和apply方法用于改变函数内this指针的指向,call只有两个参数,而apply通常是知道参数个数之后才使用的,下面以例子说明:
var obj = function(name){this.name = name;}obj.prototype.getname = function(){return this.name;}var obj1 =new obj(zou);var obj2 = {name:'andy'};var name = obj1.getname.call(obj2);alert(name);
参考是:
apply(object,arg1,arg2,....)
call(object,[arg1,arg2,....])
call后面只能跟一个“数组”,包括了所有的参数。而apply则是一颗语法糖,如果知道参数的个数,用apply将很方便。
上面的object也可以是null或者undefined,这样,这个object就是global object(window),例如,还是接着上例:
var name = 'goo';alert(obj1.getname.call(null)); (在严格模式下,由于全局对象是null,故会抛出异常:uncaught typeerror: cannot read property 'name' of null)
3、object.defineproperty
(注意:不要在ie8以下使用该类特性)
微软:将属性添加到对象,或修改现有属性的特性。
getter、setter,
其实js中对于对象的属性也有getter和setter函数,不过个人觉得js中的getter和setter更像c#一些。
例如下面的代码就定义了一个getter/setter:
function myobj(){}object.defineproperty(myobj.prototype,'length',{get:function(){return this.length_; //这里不能是length。},set:function(value){return this.length_=value;}});
注释的地方不能是length,否则会无限递归。
也可以去掉set,让length变量只读。
object.defineproperty(myobj.prototype,'length',{get:function(){return this.length_; //这里不能是length。}, /*set:function(value){return this.length_=value;}*/});myobj.length = 3;
这个代码会抛出异常:uncaught typeerror: cannot set property length of # which has only a getter。
要让对象的属性只读,还可以用writable:false,
object.defineproperty(myobj.prototype,'length',{writable:false});
writable:false不能与get set共存,否则会抛出type error。
configurable:是否能用delete语句删除,但是configurable属性好像在严格模式下才有效,这样的代码在非严格模式下仍然能执行:(严格模式报错)
object.defineproperty(myobj.prototype,'length',{configurable:false});var obj = new myobj();delete obj.length;
value:指定该对象的固定值。value:10,表示这个对象初始值为10.
在非严格模式下,这样的代码不会报错,严格模式下会报错:
object.defineproperty(myobj.prototype,'length',{writable:false,value:'10'});var obj = new myobj();obj.length = 100;
可以用getownpropertydescriptor来获取并修改这些值,比如说,现在我的length属性是只读的。
运行这样的代码,结果却报错了:
object.defineproperty(myobj.prototype,'length',{value:,writable:false,});var descriptor = object.getownpropertydescriptor(myobj.prototype, length);descriptor.writable = true;object.defineproperty(myobj.prototype,'length',descriptor); uncaught typeerror: cannot redefine property: length
这是因为configurable的默认值是false,在调用了defineproperty之后,configurable就具有false属性,这样就不能逆转了。以后就不能改了。
所以必须使用 configurable:true,这个对象属性才是可以修改的,完整的代码如下:
object.defineproperty(myobj.prototype,'length',{value:,writable:false,configurable:true});var descriptor = object.getownpropertydescriptor(myobj.prototype, length);descriptor.writable = true;object.defineproperty(myobj.prototype,'length',descriptor);myobj.prototype.length = ;var obj = new myobj();alert(obj.length);
可以加上一句descriptor.configurable = false;
表示这个属性我修改了,以后你们都不能再修改了
这个特性在很多时候也有用,数组array的push pop等方法,如果使用call、apply,要求对象的length可变。如果对象的length属性只读,那么调用call、apply时,会抛出异常。
就比如domtokenlist对象,它的length就是不可以变的。我拿到了一个dom对象domtokenlist,
但是它的configurable是true,我们可以修改让它的length属性可以变啊:
看见没,这个configurable是true,而setter是undefined,我们给它写一个set方法,不就可以了吗?
var descriptor = object.getownpropertydescriptor(domtokenlist.prototype,'length');descriptor.set = function(value){this.length = value;}object.defineproperty(domtokenlist.prototype,'length',descriptor);
然后运行,
又抛出了一个异常,uncaught rangeerror: maximum call stack size exceeded(…)
这是因为,我们在set this.length时,它会在我们写的那个set方法中无限递归。
因此,我们需要使用delete消除length属性的影响,也就是:
var descriptor = object.getownpropertydescriptor(domtokenlist.prototype,'length');descriptor.set = function(value){delete domtokenlist.prototype.length;this.length = value;}object.defineproperty(domtokenlist.prototype,'length',descriptor);
这样,domtokenlist也就支持了push,pop等等操作了。
array.prototype.push.call(document.body.classlist,'abc')
然后再行封装
domtokenlist.prototype.push = function(){array.prototype.push.call(document.body.classlist,array.prototype.slice.call(arguments));}
array.prototype.slice.call(arguments)方法用于把arguments对象转换为数组。