zoukankan      html  css  js  c++  java
  • JavaScript—从数组的indexOf方法深入——Object的Property机制。

    在js中,可以说万物皆对象(object),一个数组也是一个对象(array)。

    很多对象都有很多很方便的方法 比如数组的push,concat,slice等等,但是如果一些对象,它没有实现这些方法,我们还是想使用这些功能。那该怎么办呢?

    1、很多方法都提供了非常高效的实现, 我们可以仿照它们的实现。

    比如IE8以下的浏览器不支持Array的indexOf方法,为了让数组支持indexOf,我们可以自己写一个方法,实现indexOf方法:

    (用IE浏览器调试 按F12,可以选择浏览器版本到IE5。)

     1             var arr = [1, 2, 3, 5];
     2             if (Array.prototype.indexOf) {
     3                 alert("你的浏览器支持indexOf方法。");
     4             } else {
     5                 alert("你的浏览器不支持indexOf方法。");
     6             }
     7             if (!Array.prototype.indexOf) {
     8                 Array.prototype.indexOf = function(item) {
     9                     for (var i = 0; i < this.length; i++) {
    10                         if(this[i]==item){
    11                             return i;
    12                         }
    13                     }
    14                     return -1;
    15                 }
    16             }
    17             alert(arr.indexOf(2));
    18             alert(arr.indexOf(8));

     当然这个方法是很垃圾的。在这里具体的实现 我就不献丑了,提供一个百度上copy的版本:

    有兴趣的话可以看看v8引擎是怎么实现的:https://github.com/v8/v8/blob/master/src/js/array.js

     1 if (!Array.prototype.indexOf)
     2 {
     3   Array.prototype.indexOf = function(elt /*, from*/)
     4   {
     5     var len = this.length >>> 0;
     6     var from = Number(arguments[1]) || 0;
     7     from = (from < 0)
     8          ? Math.ceil(from)
     9          : Math.floor(from);
    10     if (from < 0)
    11       from += len;
    12     for (; from < len; from++)
    13     {
    14       if (from in this &&
    15           this[from] === elt)
    16         return from;
    17     }
    18     return -1;
    19   };
    20 }

    2、继承——call和apply方法

    如果我们每有一个对象,那每个对象就要自己写一遍实现是不是很麻烦?

    在高级语言中,我们可以用继承来解决问题,比如下面的java代码:

     1 public class MyList<E> extends ArrayList<E>
     2 {
     3     
     4     public void myAdd(E e){
     5          super.add(e);
     6          System.out.println("Add:"+e);
     7 
     8     }
     9 
    10 }

    但是js中没有继承的概念啊,我们可以用call和apply来解决这样的问题。

    上面的代码就可以改写为:

     1             var myObject = function(){
     2                 
     3                 
     4             }
     5             myObject.prototype.add = function(){
     6                 Array.prototype.push.call(this,arguments);
     7                 //输出arguments
     8                 for(var i=0;i<arguments.length;i++){
     9                     console.log("Add:"+arguments[i]);
    10                 }
    11                 
    12             }
    13             var obj = new myObject();
    14             obj.add(1,2,3);

    这里可以看到:虽然用高级语言的继承方式实现了myAdd方法,但是现在myAdd方法只能传一个参数,如果要传多个参数,则需要再写一个public void myAdd(E[] e)方法,甚至是public void myAdd(List<E> e)方法。而JS用一个方法就可以搞定,用arguments对象表示输入的所有参数,这是高级语言难以做到的。

    (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 #<myobj> 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属性是只读的。

    运行这样的代码,结果却报错了:

     1             Object.defineProperty(myobj.prototype,'length',{
     2                                 
     3                 value:10,
     4                 writable:false,
     5                 
     6                
     7                 
     8             });
     9             var descriptor = Object.getOwnPropertyDescriptor(myobj.prototype, 
    10                 "length");
    11             
    12                descriptor.writable = true;
    13             Object.defineProperty(myobj.prototype,'length',descriptor);

    Uncaught TypeError: Cannot redefine property: length

    这是因为configurable的默认值是false,在调用了defineProperty之后,configurable就具有false属性,这样就不能逆转了。以后就不能改了。

    所以必须使用 configurable:true,这个对象属性才是可以修改的,完整的代码如下:

     1             Object.defineProperty(myobj.prototype,'length',{
     2                                 
     3                 value:10,
     4                 writable:false,
     5                 configurable:true
     6                
     7                 
     8             });
     9             var descriptor = Object.getOwnPropertyDescriptor(myobj.prototype, 
    10                 "length");
    11             
    12                descriptor.writable = true;
    13             Object.defineProperty(myobj.prototype,'length',descriptor);
    14             myobj.prototype.length = 100;
    15             var obj = new myobj();
    16             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对象转换为数组。

    参考文档:

    https://msdn.microsoft.com/zh-cn/library/dd548687

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

    http://www.tuicool.com/articles/ju26riE

  • 相关阅读:
    linux 学习笔记
    linux 子系统折腾记 (三)
    linux子系统折腾记 (二)
    windows linux 子系统折腾记
    会计学习笔记(非专业)
    linux 大冒险
    coreRT 和 Native 编译netcore AOT程序
    dotnet core如何编译exe
    win10的hyper-v共享文件夹
    packagereference 里面的资产是怎么回事?
  • 原文地址:https://www.cnblogs.com/xcr1234/p/5469076.html
Copyright © 2011-2022 走看看