JavaScript的 基本数据类型---对象

第一:Javascript对象是    

第二:Javascript中    

第三:Javascript的对象是数据;

第四:JavaScript 中的对象可以简单理解成"名称:值"对(name:value)。名称(name):"名称"部分是一个 JavaScript 字符串

      参考----------https://www.cnblogs.com/foodoir/p/5971686.html

对象是JavaScript的一个基本数据类型,是一种复合值,它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。即属性的无序集合。

二、对象的创建(多种方法)

 

  1、对象直接量 / 字面量

 

       var obj = {
           name: 'lyl',
           age: 18
       }
       console.log(obj.name); // lyl

 

  2、构造函数:

 

    (1)、系统自带的的, eg: new Object(), Array(), Number(),Boolean(), Date()...  

 

 var obj = new Object();
      obj.name = 'lyl';
      console.log(obj.name); //lyl

 

     (2)、自定义的:为了和普通函数区分,首字母大写,采用大驼峰式写法(普通函数采用小驼峰式写法)

 

function Obj (name) {
          this.name = name;
          this.age = 18;
      }
      var obj = new Obj('lyl');
      console.log(obj.name); //lyl
      console.log(obj.age); //18

    自定义构造函数的基本构造原理:

 

      首先,文字理论解释一番,其实一切的关键全在与new这个操作符上,用new和不用new返回的结果大相径庭。不用new,则Obj('lyl')根本就是一个函数的正常执行,没有返回值,则默认返回undefined,而是用new操作符后js引擎就会将该函数看作构造函数看待,经过内部的一系列隐士操作,返回值就是一个对象了。

 

      看下如下demo:

 

  demo1:用new和不用new的区别演示:

function Obj () {
          this.age = 18;
      }
    //   不用new
      console.log(Obj()); // undefined
    //  用new
      console.log(new Obj()); //Obj {age: 18}
复制代码
复制代码

 

  demo2 用new返回值为对象的基本原理:

 

  不用new,函数内的this指向的是window,所以this.xxx定义的变量都是window上的属性,但为什么使用new后其中的this就不是window对象了呢?那是因为

 

  用new后,js引擎会在函数被进行两步隐士操作(假设构造函数名为Person):第一步, var this = Object.create(Peson.prototype);   (也是创建对象的一种方法,下边会讲到)  隐士的改变函数内this的含义,现在函数内的this是一个原型为Person.prototype, 构造函数为Person的对象(其实此过程就将想要的对象基本创造成功了,只是差些属性而已,从此可是看出构造函数创建对象的最根本原理是借用Object.create()方法来实现的,只不过被封装功能化了); 第二步, 在创建的对象设置完所需要的属性后,隐士的将创建的对象this通过return返回  return this; 

 

  

 

  通过代码的展现:

 

复制代码
复制代码
 //  构造函数的原型
     Person.prototype = {
       say: function () {
         console.log('I am saying');
       }
     }

    //  构造函数
     function Person () {
      //  隐士操作
      // var this = Object.create(Person.prototype);
      
      //返回对象属性的设置
        this.name = 'lyl';
        this.age = 18;

      //  隐士操作
        // return this;
     }

     var person1 = new Person();
     console.log(person1.name); // lyl
     person1.say(); //I am saying
复制代码
复制代码

 

 

 

  上述两步理论的验证:

 

  第一步:现在函数内的this是一个原型为Person.prototype, 构造函数为Person的对象

 

复制代码
复制代码
 //  构造函数的原型
     Person.prototype = {
       say: function () {
         console.log('I am saying');
       }
     }

    //  构造函数
     function Person () {

        this.name = 'lyl';
        this.age = 18;
        // 打印this对象的原型
        console.log(this.__proto__); // 
        // 验证this是否是Person构造函数的实例
        console.log(this instanceof Person); //true
     }
     new Person();//打印结果如下
    //  Object say: ()__proto__: Object
    // true

     Person();//打印结果如下
    //  Window
    // false
复制代码
复制代码

 

 

 

   第二步:隐士的将创建的对象this通过return返回

 

由以上一些代码,我们已经可以看出返回的是满足条件的对象,现在我们创建对象时不用new,并显示的模拟这两步隐士操作来验证(我们不用new则两步隐士操作就不会产生)

 

复制代码
复制代码
 //  构造函数的原型
     Person.prototype = {
       say: function () {
         console.log('I am saying');
       }
     }

    //  构造函数
     function Person () {
       var that = Object.create(Person.prototype);

        that.name = 'lyl';
        that.age = 18;
        
        return that;
    //提前返回that导致return this无法执行而失效
     }

     var person = new Person();
//此处不用new也是可以成功返回一个满足条件的对象,因为显示的返回了that console.log(person.name); //lyl person.say(); //I am saying
复制代码
复制代码

 

p.s. 关于显示返回that的问题,当我们用new生成对象,若我们显示return的是一个对象 / 引用值,则会导致return this失效,若返回的是原始值,则return this不会失效

 

 

 

  3、Object.create(原型); 创建一个继承该原型的实例对象

 

  关于此方法的一些注意事项:

 

  (1)、若传参为Object.prototype,则创建的原型为Object.prototype,和 new Object()创建的对象是一样的   Object.create(Object.prototype) <==>new Object(); 

 

  (2)、若传参为空 或者 null,则创建的对象是没有原型的, 导致该对象是无法用document.write()打印会报错,因为document.write()打印的原理是调用Object.prototype.toString()方法,该对象没有原型,也就没有该方法,所以document.write()无法打印

 

  由此延伸的知识点: 引用值都也是算作是对象,所以都可以用document.write()打印;原始值numebr, boolean, string都有自己对象的包装类,借助此机制也是可以用document.write()打印出的;

 

但undefined 和 null既不是引用值,也没有对应的包装类,所以应该无法打印的,但大家会发现这两个值也是可是用document.write()打印的,因为这两个值被设定为特殊值,document.write()打印其是不用调用任何方法的,而是之直接打印其值

 

 

 

三、对象的增、删、改、查

 

  1、增: 所谓增添一个对象的属性,就是直接对该属性进行赋值操作即可,这就相当于为该对象添加了一个新属性,而打印未添加的属性,浏览器不会报错,而是会打印出undefined

 

  

 

 var obj = {};
    console.log(obj.name); //undefined (不会报错)
    obj.name = 'lyl';
    console.log(obj.name); // lyl

 

  

 

  2、删:我们通过delete操作符来删除一个对象的属性

 

  

 

复制代码
 var obj = {
       name : 'lyl'
     };
     console.log(obj.name); //lyl
     delete obj.name; 
     console.log(obj.name); //undefined
复制代码

 

  3、改: 修改一个对象的属性是最简单的了,直接通过赋值操作赋予其其他的值即可

 

  

 

复制代码
var obj = {
      name: 'lyl'
    };
    console.log(obj.name); // lyl
    obj.name = 'obj';
    console.log(obj.name); // obj
复制代码

 

  

 

  4、查:查询一个对象的属性值有两种方法

 

  

 

复制代码
复制代码
var obj = {
      name: 'lyl'
    };
    // 第一种方法
   console.log(obj['name']); //lyl
  //  第二种方法
    console.log(obj.name); // lyl
  //p.s.最本质的是第一种方法,因为在使用第二种方法时,后台自动将其转换为第一种字符串的形式来查询 
复制代码
复制代码

 

    

 

p.s.以上的增、删、改三种操作都只是针对当前对象的属性进行操作,而不会影响到当前对象的原型的属性。

 

  而查询是先看看当前对象本身是否设置了该属性,如果当前对象未设置该属性,则再看该对象的原型中是否设置了该属性,若两者都没有,则返回undefined

 

 

 

四、包装类:

 

  1、五个原始值:number, string , boolean, undefined, null

 

其中number, string, boolean是分别拥有自己的包装类,而undefined和null是没有自己的包装类的

 

  2.原始值不是对象,无法拥有自己的属性,但因为的包装类的存在,原始值就好似可以拥有自己的属性了,但其拥有的属性又有点特殊之处,如下用string来举例:

 

先看一段code

 

//  str是string类型的,非对象,不能拥有属性,为什么能打印出str.length?
     var str = 'abcd';
     console.log(str.length); //4

 

 

 

 

 

上边code中问题的解释:

 

复制代码
    // 因为每次执行完一条完整js语句后该类型对象的包装类就会将该语句包装,所以也就不会导致报错了,这些都是后台自己写的
     var str = 'abcd';
    //  var str1 = new String('abcd');
     console.log(str.length); //4
    //  var str1 = new String('abcd');
    // console.log(str1.length);
复制代码

 

注意:每次包装类包装完一次完整语句后就会被销毁。(即解释一条语句,用包装类包装一次,然后销毁),这回导致其和正常对象的一些不同之处,如下例子

 

  

 

    var str = 'abcd';
    str.len = 4;
    console.log(str.len); // undefiend
  //???

 

 关键在于‘销毁’一词上,解释如下:

 

  

 

复制代码
复制代码
    var str = 'abcd';
    // var str1 = new String('abcd');
    // 销毁
    str.len = 4;
    // var str1 = new String('abcd');
    // str1.len = 4;
    // 销毁
    console.log(str.len); // undefiend
    // var str1 = new String('abcd');
    // console.log(str.len);   str1为刚创建的对象,其len属性自然为undefiend
    // 销毁
复制代码
复制代码

 

   一个易错的坑:

 

// 总之记得'原始值包装类''销毁'这两句话就行
   var str = 'abcd';
   str.lenth = 2;
   console.log(str);  // ab or abcd ? answer is abcd
   console.log(str.length); // 2 or 4 ? answer is 4

 

 

 

  

 

五、原型:

 

  1、原型的定义: 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过改构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。

 

  2、利用原型特点和概念,可以提取共有属性。将一类对象的共有属性提取出来,放到该类对象的原型中,从而不需要每次用new操作符时都重新定义一遍该共有属性。

 

  如下,定义一个Person构造函数,而属于Person多构造对象共有的属性方法,则定义到Person的原型中

 

  

 

复制代码
复制代码
Person.prototype = {
        eat: function (food) {
           console.log('I have eated ' + food);
        },
        sleep: function () {
          console.log("I am sleeping");
        }
      }
      // 人的构造函数
      function Person (name, age) {
        this.name = name;
        this.age = age;
      }
      var person1 = new Person('lyl', 18);
      console.log(person1.name); //lyl
      person1.eat('apple'); //I have eated apple
复制代码
复制代码

 

  3、如何查看原型:

 

   之前是不允许我们查看构造函数的原型的,但后来提供了一个可查看构造函数原型的接口:隐士属性__proto__(其实我们能够访问原型的属性,或者说继承原型,靠的就是__proto__属性连接着构造函数和原型,可以说没有__proto__属性的存在,就无法实现原型的继承)

 

    (1)、首先我们先说明一下__proto__这个接口是存放到哪里的

 

      看过以上对象创建过程的都应该知道在用new创建一个对象时,内部会隐士自动创建一个this的对象,进过一系列处理后再隐士将this对象返回。而__proto__就对于隐士创建的this对象中,如下代码:

 

  

 

复制代码
复制代码
 // 原型
    Person.prototype = {
      say: function () {
        console.log("I am saying ");
      },
      play: function () {
        console.log("I am playing");
      }
    }
    // 构造函数
    function Person (name) {
      
      // var this = Object.create(Person.prototype);
      // p.s.在隐士创建的this对象中存在一个属性,即__proto__,该属性存储了Person.prototype

      this.name = name;

      // return this;
    }
    // 对象的创建
    var person1 = new Person('lyl');
    // 打印原型
    console.log(person1.__proto__);
复制代码
复制代码

 

 

 

    (2)、如何查看原型:直接通过new操作符创建的对象访问__proto__属性即可,如上代码演示

 

  4、如何查看对象的构造函数,我们通过属性constructor来查看:

 

    contructor属性位于构造函数的原型中,其中存储的是构造函数信息,所以在不知道原型的情况下,由原型继承原理,我们可以用实例对象来直接访问constructor,即获取创建该实例的构造函数

 

复制代码
 function Person () {
           this.name = 'myName';
           this.age = 18;
       }
       var person = new Person();
       console.log(person.constructor); // function Perso(){...}
复制代码

 

六、原型链:

 

  1、定义:顾名思义,原型链就是将一个个原型串连起来,形成一条原型继承的链子。

 

  2、原型链的构成:

 

      如下代码例子, Child继承Parent, Parent继承GrandParent, 而GrandParent没有自定义原型,所以默认为原型链的最顶端new Object();

 

       (为什么Object为最顶端,因为Object.prototype为null,为null是没有原型的)

 

复制代码
复制代码
 //    原型链: Child -> new Parent() -> new GrandParent() -> new Object();
       function GrandParent() {
           this.name = 'GrandParent';
           this.a = 3;
       }
       Parent.prototype = new GrandParent();
       function Parent() {
           this.name = 'parent';
           this.b = 2;
       }
       Child.prototype = new Parent();
       function Child() {
           this.name = 'child';
           this.c = 1;
       }

       var child = new Child();
       console.log(child); // Child {name: "child", c: 1}

       console.log(child.a); // 3
       console.log(child.b); //2
       console.log(child.c); //1
复制代码
复制代码

 

  3、原型链的增删改查:

 

    使用如上的原型链说明, Child -> new Parent() -> new GrandParent() -> new Object(), 实例对象为child

 

    (1)、增:

 

      为child实例对象添加属性,总是添加为其自己本身的属性,为对原型和原型链是没有影响的。(再具体说即对和其相同构造函数构造的实例对象无法造成影响,以下说法同此)

 

    (2)、删:

 

      使用delete操作符只能删除child实例对象自己本身的属性,而无法删除由原型继承而来的属性

 

    (3)、改:

 

      分两种情况:

 

        若修改的属性为继承自原型的,且值类型为原始值,则仅仅修改的是该实例对象的属性,对原型无法造成影响。

 

        若修改的属性为继承自原型的,属性值类型为引用值,则对引用值的修改又分两种情况:

 

            第一种是直接对该修改的属性赋值 => 此情况仅仅修改的是实例对象的该属性,无法对原型造成影响。

 

            第二种是对该修改的属性添加内容或去除内容,而不是对其重新赋值 => 此情况会对原型造成影响。如下例子:

 

            

 

复制代码
复制代码
 Person.prototype = {
            has: [1, 2, 3]
        }
        function Person () {
            this.name = 'lyl';
        }
        var person1 = new Person();
        var person2 = new Person();
        person1.has.push(4);
        // person1 和 person2都改变了,因为person1的修改影响到了原型,进而影响到了另一个实例对象
        console.log(person1.has); //[1, 2, 3, 4]
        console.log(person2.has); // [1, 2, 3, 4]

 

     (4)、查:

 

  查询过程如下,首先看构造函数中是否有要查询的属性,若有,则直接返回,若没有,则看其原型有没有要查询的属性,若没有,则再看原型的原型上是否有要查询的属性,以此顺序在原型链上查询,若一直到原型链顶端后仍没有要查询的属性,则返回undefined

 

  p.s.理解了在原型链上的增删改查后,自然就能理解在原型上的增删改查了,只要把在原型上的增删改查当成只有一个原型的很短的原型链即可。

 

 

 

   4、绝大多数对象最终都会继承自Object.prototype

 

    为什么事绝大多数呢?因为null,和undefined是没有原型的,上文有详细提到

 

 

 

七、对象继承史

 

  由于之前已经总结过这些,所以此处直接就粘链接了: http://www.cnblogs.com/Walker-lyl/p/5592048.html

 

八、命名空间:

 

  我们可以利用对象创建命名空间来管理变量,防止污染全局,适用于模块开发,如下简单的小demo:

 

复制代码
复制代码
 var workSpace = {
           person1: {
               name: 'one',
               age: 18
           },
           person2: {
               name: 'two',
               age: 20
           }
       }
    // 这样两个人虽然有同名变量,但不会相互影响,因为位于不同命名空间
     //    访问第一个人的姓名
    console.log(workSpace.person1.name); // one
    console.log(workSpace.person2.name); //two

九、实现类似jquery中的链式调用: return this;

 

    如下小demo,其中的this不懂的没关系,上面会说,你只要把次demo中的this当成person对象就行

 

  

 

var person = {
          foodCount: 10,
          eat: function () {
             this.foodCount--;
              return this;
          },
          buy: function () {
              this.foodCount++;
              return this;
          },
          print: function () {
              console.log(this.foodCount);
          }
      }
    //   foodCount初始值为10, 在连续吃了三次后,变为7
      person.eat().eat().eat();
      person.print(); //7

 

 十、对象的枚举:

 

  1.obj.hasOwnProperty('prop');

 

      该方法的作用是来判断对象obj的自身属性中是否含有属性prop,

 

    自身属性是在构造函数中生成的或者实例对象后来自己添加的,而继承属性则是从原型上继承的属性,

 

    所以该方法就是判断该属性是从原型继承来的还是自身的。

 

      作用: 遍历一个对象的所有自身属性,因为es5中的对象遍历是默认打印包括继承自原型的属性的,demo如下

 

  

 

Person.prototype.age = 18;
     function Person () {
         this.name = 'lyl';
     }
     var person = new Person();
    //  未用hasOwnProperty
    // print:
    //  lyl
     for(var prop in person) {
         console.log(person[prop]);
     } 
    //  使用hasOwnProperty
    // print: 
    // 18 lyl
     for(var prop in person) {
         if(person.hasOwnProperty(prop)) {
             console.log(person[prop]);
         }
     }

 

 

 

   2、prop in obj; 

 

  in操作符用来判断该对象obj上是否有该属性prop,prop既可以是自身属性,也可以是继承属性,如下demo

 

Person.prototype.age = 18;
     function Person () {
         this.name = 'lyl';
     }
     var person = new Person();
    console.log('age' in person); // true
    console.log('name' in person); //true
    delete person.name;
    console.log('name' in person); //false

  3、object instanceof Object;

 

  instanceof操作符用来判断object实例对象是否为Object构造函数创建的,如下demo

 

Person.prototype.age = 18;
     function Person () {
         this.name = 'lyl';
     }
     var person = new Person();
     console.log(person instanceof Person); // true
     console.log(new Object() instanceof Person); //false

 

 十一、this基本介绍:

 

  1、函数预编译过程 this —> window
  2、全局作用域里 this —> window

 

  3、obj.func();   func()里面的this指向obj), 可以这样理解,谁调用func,则this就指向谁

 

  4、call/apply 可以改变函数运行时this指向,

 

    (1)、call用法:

 

      func.call(要改变后的this, arg1, arg2, ... );

 

    (2)、apply用法:

 

      func.apply(要改变后的this, [arg1, arg2, arg2]);

 

    (3)、apply和call共同点:都是改变this指向

 

        apply和call不同点:传参形式不同,call是将参数一个个传进来,而apply是将所有参数存进一个数组中,然后将该数组传

 

 

 

如下demo:

 

  

 

// demo1
    function demo1() {
        console.log(this);
    }
    // demo1() <==> this.demo1(); <==> window.demo1()
    demo1(); // window
// demo2
    var demo2 = {
        retThis: function () {
            console.log(this);
        }
    }
    demo2.retThis(); // demo2 = {...}
// call / apply改变this
    demo1.call(demo2);  // demo2 = {}
    demo2.retThis.call(window); // window

 

十二、对象的克隆:

 

  你可能回想,直接用等号赋值不久完成克隆了吗?这还用说,但你忽略了对象是一个引用值,赋值操作赋的是该对象的引用,然后会产生很坏的影响。

 

  举例来说,将obj1赋值给obj2,然后我们向obj2中添加了一个属性,然后我们会惊喜的发现obj1中也有了此刚刚向obj2中添加的属性,但我obj1并不需要该属性啊,则造成obj1不开心了,为了让obj1开心起来,我们向下看,嘿嘿:

 

  对象克隆,分为浅克隆和深克隆,而上边的直接赋值的克隆操作为浅克隆,为什么称为浅克隆呢?因为,克隆的和被克隆的对象在克隆操作完成后,指向同一个地址引用,改变其中一个(注意:此处的改变为增加或删除对象的属性,而不是为该对象重新赋值一个对象),另一个也会改变,而深克隆则不会产生此现象。

 

  深克隆/深拷贝code如下,这是我从网上搜到的一个目前我见到的最完整最简短的code,直接贴上:

 

 

 

// 对象的深度克隆(Array / obj /...)
        function  deepClone(obj) {
            var str, retObj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
            if(typeof obj !== 'object') {
                return;
            }else if(window.JSON) {
                str = JSON.stringify(obj);
                retObj = JSON.parse(str);
            }
            else {
                for(var prop in obj) {
                    retObj[prop] = typeof obj[prop] === 'object' ? deepClone(obj[prop]) : obj[prop];
                }
            }
            return retObj;
        }


   1、通过对象字面量的方式创建对象(对象字面量是一个表达式,这个表达式的每次运算都创建并初始化一个新对象。每次计算对象字面量的时候,也都会计算他的每个属性的值。也就是说,如果在一个重复调用的函数中的循环体内使用了对象直接量,它将创建很多新对象,并且每次创建的对象的属性值也有可能不同。)

        语法:var obj = {};
        实例:
        首先,我们创建一个空的对象,里面没有任何属性,我们来获取它的类型   

var obj1 = {};//没有任何属性的对象
console.log(typeof obj);        //object

 

        它返回的结果是object,说明我们创建的是一个object对象,当然,我们还可以用下面的方法来创建

复制代码
                var obj2={x:1,y:2,z:3};
                var obj3={
                'x':1,
                "y":2,
                username:'king',
                'for':'javascript关键字必须放到引号中间',    //for是javascript关键字,必须放到引号中间
                'first-name':'foodoir',                //-是特殊字符,也需要放在引号中间
                married:true,
                test:null,
                test1:undefined,
                salary:12.3,
                person:{                            //在对象中"值"部分可以是任何 JavaScript的数据类型——包括对象
                    username:'king',
                    age:21,
                    addr:'北京',
                },                                    //最后一个对象属性的后面的逗号可以写可以不写,在ECMAScript 5中自动被忽略,在ECMAScript 3的大部分实现中也可以忽略这个逗号,但在IE中报错
            }
复制代码

    2、通过new object创建对象
        语法:var obj = new Object();
        实例:

            var obj4 = new Object();//创建一个空对象
            var arr = new Array();//创建一个空数组对象
            var date = new Date();//创建一个空时间对象   

    3、通过构造函数的形式创建对象
        语法:function Person(){};或者var Person=function(){};
        实例:

复制代码
            var obj5=new Test1();
            function Test1(num1,num2){
                this.n1=num1;
                this.n2=num2;
            }
            var obj6=new Test1(5,6);
            console.log(typeof obj6);                //object
            console.log(obj6 instanceof Object);    //true
复制代码

        在使用通过构造函数的形式创建对象时应注意:

            a.使用的时候通过new操作符得到对象var person1=new Person()
            b.用构造器创建对象的时候可以接收参数
            c.构造器函数的首字母最好大写,区别其他的一般函数

        注意:构造器属性(constructor property),当我们创建对象的时候,实际上同时也赋予了该对象一种特殊的属性,就是构造器属性,这个构造器属性实际上是一个指向用于创建该对象的构造器函数的引用

补充:
typeof 和 instanceof 常用来判断一个变量是否为空,或者是什么类型的。但它们之间还是有区别的:
    typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。
        它返回值是一个字符串,该字符串说明运算数的类型。typeof 一般只能返回这几个结果:number,boolean,string,function,object,undefined。
        我们可以使用 typeof 来获取一个变量是否存在,如 if(typeof a!="undefined"){alert("ok")},而不要去使用 if(a) 因为如果 a 不存在(未声明)则会出错,对于 Array,Null 等特殊对象使用 typeof 一律返回 object,这正是 typeof 的局限性。
    instanceof 用于判断一个变量是否某个对象的实例
        在上面的例子中,obj6 instanceof Object判断的为true,则说明创建的obj6也是Object类型            
        
    4、通过Object.create()创建对象   

复制代码
        var obj7 = Object.create({x:1});
        //创建一个普通的空对象
        var obj8 = Object.create(null);
        //创建一个对象的原型属性
        var obj9 = Object.create(Object.prototype);    //prototype对象的原型属性
        console.log(typeof obj7);                //object
        console.log(typeof obj8);                //object
        console.log(typeof obj9);                //object
        //通过instanceof 操作符检测对象是否由某个指定的构造器函数创建的
        console.log(obj7 instanceof Object);    //true
        console.log(obj8 instanceof Object);    //false,注意:空对象用instanceof判断时,结果为false
        console.log(obj9 instanceof Object);    //true
复制代码

如何对对象进行操作?

    学过数据库的我们都知道,数据库最基本的四个操作分别是“增、删、查、改”,那么在对对象的操作中是否也存在类似的操做呢?是的,在对对象的操作中也有查询,添加,修改,删除操作,都是基于对象的属性的操作。下面我们来介绍对象的几个基本操作。

    首先,我们先要通过查询方法,来获取对象中的属性,访问属性有三种方法:对象名.属性名;对象名[属性名];当处于某个对象方法内部的时候,可以通过this来访问同一对象的属性

复制代码
var person = {
    username: "foodoir",
    age: 21,
    sex: "",
    addr: "湖南",
    salary: 123456,
};
console.log("姓名:" + person.username + "\n" + "性别:" + person.sex); //姓名:foodoir    性别:男
console.log("年龄:" + person['age'] + "\n" + "薪水:" + person["salary"]); //年龄:21    薪水:123456
//如果属性不确定,需要使用[]
var key = 'username';
console.log(person.key); //undefined   此种方法不能得到key的值,通过[]方法可以得到
console.log(person[key]); //foodoir     当属性不确定时,用该方法
console.log(person['key']); //undefined
复制代码

            看到这里,你肯定会有有疑问,为什么第9行代码中person['age']中的age和person["salary"]中的salary都加了引号,且都可以显示出结果,而在第13行代码中person['key']中的key加了引号,反而返回值是undefined 原因究竟是什么呢?

            age为person对象中的属性名,而该对象只对person进行了定义,没对person里面的属性进行定义,故在调用的时候,在[]中间应该加上引号,也就是说,person['age']和person.age是等价的

            而key是在对象外定义的(var key = 'username';),在对象里面它是不存在key这个属性的(即属性不确定),此时直接用person[key]方法就好了,person.key和person['key']返回的必须是person对象里面确定的属性,key没在person属性里面,故这两种方法返回的值都是undefined

   学会了如何获取对象的属性,然后我们该继续学习对象中的属性进行添加,修改和查询操作

复制代码
function personInfo(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
var person1 = new personInfo('foodoir', 20, '');
console.log(person1.name + person1.sex); //foodoir男
复制代码

    属性的添加有两种方式:对象名.属性名=值;对象名[属性名]=值       

复制代码
//添加属性
var obj = {};
obj.username = 'foodoir';
obj.age = 21;
obj.sex = '';
obj.addr = '湖南';
obj['test'] = 'hello world';
console.log(obj.username + " " + obj.age + " " + obj.sex + " " + obj.addr + " " + obj['test']); //foodoir 21 男 湖南 hello world
复制代码

    属性的修改也有两种方式:对象名.属性名=值;对象名[属性名]=值       

//修改指定属性
obj.username = 'chenwu';
console.log("修改之后的名字:" + obj.username); //修改之后的名字:chenwu
obj['test'] = 'hello javascript';
console.log("修改之后的test:" + obj['test']); //修改之后的test:hello javascript

    属性的删除也有两种方式:delete 对象名.属性名;delete 对象名[属性名]    。在delete删除指定的属性时应该注意:(后面讲对象的结构的时候会详细介绍)
        delete只能删除自身属性,不能删除继承属性
        要删除继承属性,只能从定义它属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象
        delete只是断开属性和宿主对象的联系,而不会去操作属性的属性
        delete不能删除哪些可配制性为false的属性  

//通过delete删除指定的属性
delete obj.sex;
console.log(obj.sex); //undefined
delete obj['test'];
console.log(obj['test']); //undefined

         除了“增、删、改、查”我们还要学会对象中的遍历,对象中的遍历有两种,一种是for/in遍历,一种是通过Object.keys(obj)函数进行遍历

复制代码
var obj = {
    x: 1,
    y: 2,
    test: "helloworld",
    edu: "javascript",
};
//通过for/in遍历属性
for(var i in obj) {
    console.log(i); //x y test edu(换行输出)
    console.log(obj[i]); //输出的是属性名对应的属性值
}
//通过Object.keys(obj)遍历属性
console.log(Object.keys(obj)); //["x", "y", "test", "edu"]
复制代码

    虽然两种方法都可以对对象的属性进行遍历,但是二者之间还是有区别的。Object.keys() 方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。

    学了前面的觉得难度不是很大,我们试着做一个对象中有方法的例子

复制代码
var obj = {
    username: 'foodoir',
    age: 21,
    sex: '',
    sayHi: function() {
        return 'say hi';
    },
    info: function() {
        return obj.username + obj['sex'];
    }
}
console.log(obj.sayHi()); //say hi
console.log(obj.info); //在info的后面要加(),不然结果是:function(){ return obj.username+obj['sex']; }
console.log(obj.info()); //foodoir男
复制代码

    注意:在方法的后面要加(),不然,返回的将是function(){ return obj.username+obj['sex']; }

复制代码
function Person(username, age, sex) {
    this.username = username;
    this.age = age;
    this.sex = sex;
    this.info = function() {
        return this.name + this.age;
    }
}
var person1 = new Person('foodoir', 21, '');
console.log(person1.info()); //NaN    String+Number两种类型相加的结果为NaN        
console.log(Person.person1); //undefined person1不是Person里面的属性,故返回undefined
console.log(person1.username); //foodoir    
//向对象中添加新属性        
person1.test = 'this is a test'; //this is a test
console.log(person1.test);
//向对象中添加新的方法
person1.fun = function() {
    return 'hello world';
}
console.log(person1.fun()); //hello world
复制代码

 

到此刻,你可能有新的疑问,究竟对象的结构是怎样?下面图片是我自己对于对象的理解画的一幅图

 

与对象相关的特性有哪些?

与对象相关的特性有三个:对象的原型(prototype)、对象的类(class)、对象的扩展标记(extensible flag),具体如下:

对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象

        通过对象字面量创建的对象使用Object.prototype作为它们的原型
        通过new创建的对象使用构造函数的prototype属性作为他们的原型
        通过Object.create()创建的对象使用第一个参数(也可以是null)作为它们的原型

  关于对象的原型,前面已经介绍过了,这里不再另外介绍。

对象的类(class)是一个标识对象类型的字符串

        ECMAScript3和ECMAScript5都未提供这个属性的方法,可以通过对象的toString()方法间接查询。下面来看例子:

复制代码
var obj = {};
console.log(obj.toString());//【object Object】
var arr = new Array();
console.log(arr.toString());//这里的结果还是【object Object】,这里有个问题需要注意,内置对象重写了toString方法
//我们用回调的方法
console.log(Object.prototype.toString.call(arr));//[object Array]
var d = new Date();
console.log(Object.prototype.toString.call(d));//[object Date]
//测试函数
function classof(obj) {
    if(obj === null) {
        return 'Null';
    }
    if(obj === undefined) {
        return 'Undefined';
    }
    return Object.prototype.toString.call(obj).slice(8, -1);
}
var x = null;//Null
x = undefined;//Undefined
x = 123;//Number
x = 12.3;//Number
x = 'foodoir';//String
x = true;//Boolean
x = [];//Array
x = window;//glable
x = function() {};//Function

function f() {};
x = new f();//Object
console.log(classof(x));
复制代码

对象的扩展标记(extensible flag)指明了(在ECMAScript5中)是否可以向该对象添加新属性

        所有内置对象和自定义对象都是显示可扩展的,宿主对象的可扩展性由JavaScript引擎定义的。
        可以通过Object.preventExtensions()将对象设置为不可扩展的,而且不能再转换成可扩展的了,可以通过Object.isExtensible()检测对象是否是可扩展的。示例代码如下:

        preventExtensions()只影响到对象本身的可扩展性,如果给一个不可扩展的对象的原型添加属性,这个不可扩展的对象同样会继承这些新属性
        可扩展性的目的是将对象锁定,防止外接干扰,通常和对象的属性的可配置行与可写性配合使用

复制代码
var obj = {};
//检测对象是否可扩展
console.log(Object.isExtensible(obj));//true
var d = new Date();
console.log(Object.isExtensible(d));//true
obj.x = 1;
console.log(obj.x);//1
//通过preventExtensions()将对象变为不可扩展的
obj1 = Object.preventExtensions(obj);
console.log(obj === obj1);//true
console.log(Object.isExtensible(obj1));//false
obj1.y = 2;
console.log(obj1.y);//undefined

//通过下面的这种方法会报错
Object.defineProperty(obj1, 'z', {
    value: 1
});
复制代码

 问题:如何把对象变为不可扩展的,且保持对象自身的属性变为不可修改的?

       Object.seal()和Object.preventExtensions()类似,除了能够将对象设置为不可扩展的,还可以将对象的所有自身属性都设置为不可配置的。也就是说不能给这个对象添加新属性,而且它已有的属性也不能删除或配置,不过它已有的可写属性依然可以设置。可以通过Object.isSealed()检测对象是否封闭

复制代码
var obj = {
    x: 1,
    y: 2,
    username: 'foodoir'
};
obj.age = 12;
delete obj.x;
var o = Object.seal(obj);
console.log(obj === o);//true
console.log(Object.isExtensible(o));//false,不可扩展的
obj.y = 55;//尝试修改
console.log(obj.y);//此时仍然可以修改

/*访问器属性是不可以修改的,运行下面代码会报错
    Object.defineProperty(obj,'username',{
        get :function(){
            return 'this is a test';
        }
    });
*/

o.z = 77;
console.log(o.z);
console.log(o.username);//foodoir
delete o.username;
console.log(o.username);//foodoir
//    Object.defineProperties(obj,'username',{value:'hello'});//同样会报错
console.log(Object.isSealed(o));//true,被封闭了
console.log(o.username);//foodoir
console.log(Object.getOwnPropertyDescriptor(obj, 'username'));
//Object configurable: falseenumerable: true value: "king" writable: true __proto__: Object
复制代码

        Object.freeze()将更严格地锁定对象--冻结(frozen).除了对象设置为不可扩展的和将其属性设置为不可配置的之外,还可以将它自身的所有数据属性设置为只读(如果对象的存储器属性具有setter方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)。可以使用Object.isFroze()来检测对象是否冻结。

复制代码
 var obj = {
    prop: function() {},
    foo: 'foodoir'
};
obj.test = 'this is a test';
delete obj.prop;
var o = Object.freeze(obj);
console.log(obj === o);//true
console.log(Object.isFrozen(o));//true,已经被冻结
//冻结后所有的操作都会失败
o.x = 1;
console.log(o.x);//undefined
console.log(o.foo);//foodoir
o.foo = 'hello';
console.log(o.foo);//foodoir,在严格模式下会报出异常

//浅冻结
var obj1 = {
    internal: {}
};
//冻结obj1
Object.freeze(obj1);
//在属性中添加值1
obj1.internal.x = 1;
console.log(obj1.internal.x);//1

//递归的冻结(深度冻结)
function deepFreeze(obj) {
    var prop, propKey;
    Object.freeze(obj);
    for(propKey in obj) {
        //如果还是一个对象的话,将obj[propKey]赋值给prop
        prop = obj[propKey];
        //如果没有自己的属性或者不是一个Object或者冻结这个函数
        if(!obj.hasOwnProperty(propKey) || !(typeof prop === 'object') || Object.isFrozen(prop)) {
            continue;
        }
        deepFreeze(prop);
    }
}

var obj2 = {
    internal: {}
};
deepFreeze(obj2);
obj2.internal.x = 1;
console.log(obj2.internal.x);//undefined
复制代码

下面是关于可扩展性的一些小结,直接看代码

复制代码
//默认对象是可扩展的,也就是非冻结的
    console.log(Object.isFrozen({}));//false
    
//一个不可扩展的对象同时也是一个冻结的对象
    var obj=Object.preventExtensions({});
    console.log(Object.isFrozen(obj));//true
    
//一个非空对象默认也是非冻结
var obj1={x:1};
console.log(Object.isFrozen(obj1));//false
Object.preventExtensions(obj1);//将对象变成不可扩展的
console.log(Object.isFrozen(obj1));//false
delete obj1.x;//删掉属性之后,变成可冻结的了
console.log(Object.isFrozen(obj1));//true

//一个不可扩展的对象,但是拥有一个可写但不可配置的属性,仍然是非冻结的
var obj2={x:1};
Object.preventExtensions(obj2);
Object.defineProperty(obj2,'x',{writable:false});
console.log(Object.isFrozen(obj2));//false,非冻结
//将x变为不可配置
Object.defineProperty(obj2,'x',{configurable:false});
console.log(Object.isFrozen(obj2));//true,冻结了

//如果一个不可扩展的对象,拥有一个不可配置但可写的属性,是非冻结的
var obj3={x:1};
Object.preventExtensions(obj3);
Object.defineProperty(obj3,'x',{configurable:false});
console.log(Object.isFrozen(obj3));//false,非冻结
//将x变为不可写
Object.defineProperty(obj3,'x',{writable:false});
console.log(Object.isFrozen(obj3));//true,冻结了

//如果一个不可扩展的对象拥有一个访问器属性,它也是非冻结的
var obj4={
    get test(){
        return 1;
    }
};
Object.preventExtensions(obj4);
console.log(Object.isFrozen(obj4));//false,非冻结的
Object.defineProperty(obj4,'test',{configurable:false});
console.log(Object.isFrozen(obj4));//true,冻结的

//直接冻结一个对象
var obj5={x:1};
Object.freeze(obj5);
console.log(Object.isFrozen(obj5));//true,被冻结了
console.log(Object.isSealed(obj5));//true,密封的
console.log(Object.isExtensible(obj5));//true,不可扩展的
复制代码

 

到这里,我们再来小结一下Object.prototype和Object对象有哪些属性和方法。

Object对象的属性

Object.prototype: 可以为所有Object类型的对象添加属性

Object对象的方法

Object.create(): 指定原型对象和属性创建一个对象。
语法
Object.create(proto, [propertiesObject])
参数
proto: 一个对象, 作为新创建对象的原型
propertiesObject: 一个对象值, 可以包含若干个属性, 属性名称为新建对象的属性名, 属性值为那个属性的属性描述对象。

Object.defineProperty(): 给对象添加 / 修改一个属性并指定该属性的配置
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj: 需要定义的对象
prop: 需要定义或修改的属性名
descriptor: 属性定义或修改的属性的描述
描述
该方法允许精确添加或修改对象的属性。 正常的属性添加通过赋值来创建并显示在属性枚举中(
for...in 循环 或 Object.keys 方法), 这种方式添加的属性值可能被改变, 也可能会被 删除。 该方法允许改变这些额外细节的默认设置。
对象里目前存在的属性描述符有两种主要形式: 数据描述符和存取描述符。 数据描述符是一个拥有可写或不可写值的属性。 存取描述符是由一对 getter - setter 函数功能来描述的属性。 描述符必须是两种形式之一;
数据描述符和存取描述符均具有以下可选键值
configureable: 当且仅当这个属性描述符值为 true 时, 该属性可能会改变, 也可能会被从相应的对象删除。 默认为 false。
enumerable: true 当且仅当该属性出现在相应的对象枚举属性中。 默认为 false。
value: 与属性有关的值。 可以是任何有效的Javascript值。 默认为undefined
writable: true当且仅当可能用赋值运算符改变与属性相关的值。 默认为false
存取描述同时具有以下可选键值
get: 一个给属性提供getter的方法, 如果没有getter则为undefined。 方法将返回作用属性的值, 默认为undefined
set: 一个给属性提供setter的方法, 如果没有setter则为undefined。 该方法将受到作为唯一参数的新值分配给属性。 默认为undefined
注意
这些选项不一定是自身属性, 如果是继承来的也要考虑。 为了确认保留这些默认值, 你可能要在这之前冻结 Object.prototype, 明确指定所有的选项, 或者将 __proto__ 属性指向空。

Object.defineProperties(): 在一个对象上添加或修改一个或者多个自有属性, 并返回该对象。
语法
Object.defineProperities(obj, props)
参数
obj: 将要被添加属性或修改属性的对象
props: 该对象的一个或多个键值对定义了将要为对象添加或修改的属性的具体配置

Object.keys(): 方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组, 数组中属性名的排列顺序和使用for - in循环遍历该对象时返回的顺序一致(两者的主要区别是for - in还会遍历除一个对象从其原型链上继承到得可枚举的属性)
语法
Object.keys(obj)
参数
返回该对象的所有可枚举自身属性的属性名
描述
Object.keys 返回一个所有元素为字符串的数组, 其元素来自于从给定的对象上面可直接枚举的属性。 这些属性的顺序与手动遍历该对象属性时的一致。
如果你想获取一个对象的所有属性, ,甚至包括不可枚举的, 可以通过Object.getOwnPropertyNames() 实现

Object.getOwnPropertyNames(): 返回一个由指定对象的所有自身属性的属性名( 包括不可枚举属性) 组成的数组
语法
Object.getOwnPropertyNames(obj)
参数
obj: 要查看的对象
描述
Object.getOwnPropertyNames 返回一个数组, 该数组对元素是 obj 自身拥有的枚举或不可枚举属性名称字符串
数组中枚举属性的顺序与通过
for...in loop( 或 Object.keys)) 迭代该对象属性时一致。 数组中不可枚举属性的顺序未定义。

Object.getOwnPropertyDescriptor(): 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性, 不需要从原型链上进行查找的属性))
语法
Object.getOwnPropertyDescriptor(obj, prop)
参数
obj: 在该对象上查看属性
prop: 一个属性名称, 该属性的属性描述被返回
返回值
如果指定的属性存在于对象上, 则返回其属性描述符( property descriptor), 否则返回 undefined
描述
该方法允许对一个属性的描述进行检索。 在 Javascript 中, 属性 由一个字符串类型的“ 名字”( name) 和一个“ 属性描述符”( property descriptor) 对象构成一个属性描述符是一个记录

Object.getPrototypeOf(): 返回指定对象的原型(也就是该对象内部属性[[Prototype]] 的值)
语法
Object.getPrototypeOf(obj)
参数
要返回的对象
描述
如果参数不是一个对象类型, 将跑出TypeError异常
Object.freeze(): 冻结一个对象。 冻结对象是指那些不能添加新的属性, 不能修改已有属性的值, 不能删除已有属性, 以及不能修改已有属性的可枚举性、 可配置性、 可写性的对象。 也就是说这个对象永远不能改变的。
语法
Object.freeze(obj)
参数
obj: 要被冻结的对象
描述
冻结对象的所有自身属性都不可能以任何方式被修改。 任何尝试修改该对象的操作都会失败, 可能是静默失败, 也可能会抛出异常( 严格模式中)
数据属性的值不可更改, 访问器属性( 有getter和setter) 也同样( 但由于是函数调用, 给人的错觉是还是可以修改这个属性)。 如果一个属性的值是个对象, 则这个对象中的属性是可以修改的, 除非它也是个冻结对象。

Object.isFrozen(): 判断对象是否已经被冻结
语法
Object.isFrozen(obj)
参数
obj: 被检测的对象
描述
一个对象是冻结的( frozen) 是指它不可扩展, 所有属性都是不可配置的( non - configurable), 且所有数据属性( data properties) 都是不可写的( non - writable)数据属性是值那些没有取值器( getter) 或赋值器( setter) 的属性。

Object.preventExtensions(): 阻止对象扩展
语法
Object.preventExtensions(obj)
参数
obj: 将要变得不可扩展的对象
描述
如果一个对象可以添加新的属性, 则这个对象是可扩展的。 preventExtensions 可以让这个对象变的不可扩展, 也就是不能再有新的属性。
需要注意的是不可扩展的对象的属性通常仍然可以被删除
尝试给一个不可扩展对象添加新属性的操作将会失败, 不过可能是静默失败, 也可能会抛出 TypeError 异常( 严格模式)。

Object.isExtensible(): 检测一个对象是否可扩展(是否可以在它上面添加新的属性)
语法
Object.isExtensible(obj)
参数
obj: 需要检测的对象
描述
默认情况下, 对象是可扩展的: 即可以为他们添加新的属性。 以及它们的 __proto__ 属性可以被更改。
Object.preventExtensions, Object.seal 或 Object.freeze 方法都可以标记一个对象为不可扩展( non - extensible)。

Object.seal(): 可以让一个对象密封, 并返回被密封之后的对象。 密封对象是指那些不能添加新的属性、 不能删除已有属性, 以及不能修改已有属性的可枚举性、 可配置性、 可写性, 但可能可以修改已有属性的值的对象
语法
Object.seal(obj)
参数
obj: 要被密封的对象
描述
通常情况下, 一个对象是可扩展的( 可以添加新的属性)
密封一个对象会让这个对象变的不能添加新属性, 且所有已有属性会变的不可配置。
属性不可配置的效果就是属性变的不可删除, 以及一个数据属性不能被重新定义成为访问器属性, 或者反之。
但属性的值仍然可以修改。 尝试删除一个密封对象的属性或者将某个密封对象的属性从数据属性转换成访问器属性, 结果会静默失败或抛出TypeError 异常( 严格模式)。
不会影响从原型链上继承的属性。 但 __proto__() 属性的值也会不能修改。

Object.isSealed(): 检测一个对象是否被密封sealed
语法
Object.isSealed(obj)
参数
obj: 要被检测的对象
描述
如果这个对象是密封的, 则返回 true, 否则返回 false。
密封对象是指那些不可 扩展 的, 且所有自身属性都不可配置的( non - configurable) 对象。

Object.prototype的属性

Object.prototype.constructor: 返回一个指向创建了该对象原型的函数引用
  注意:该属性的值是那个函数本身, 而不是一个包含函数名称的字符串。 对于原始值( 如1, true 或 "test"),该属性为只读。所有对象都会从它的原型上继承一个 constructor 属性

Object.prototype的方法

Object.prototype.hasOwnProperty(): 检测某个对象是否含有指定的自身属性
语法
obj.hasOwnProperty(prop)
参数
要检测的属性名称
描述
所有继承了 Object.prototype 的对象都会从原型链上继承到 hasOwnProperty 方法
这个方法可以用来检测一个对象是否含有特定的自身属性, 和 in 运算符不同, 该方法会忽略掉那些从原型链上继承到的属性

Object.prototype.isPrototypeOf(): 检测一个对象是否存在于另一个对象的原型链上
语法
prototype.isPrototypeOf(object)
参数
prototype: 检测该对象是否在参数object的原型链上
object: 在该对象的原型链上搜寻
描述
isPrototypeOf方法允许你检测一个对象是否存在于另一个对象的原型链上

Object.prototype.propertyIsEnumerable(): 检测指定的属性名是否是当前对象可枚举的自身属性
语法
obj.propertyIsEnumerable(prop)
参数
prop: 需要检测的属性名
描述
每个对象都有 propertyIsEnumerable 方法。 该方法可以判断出指定的属性是否是自身的可枚举属性, 也就是说该属性是否可以通过for...in 循环等遍历到
有些属性虽然可以通过for...in 循环遍历到, 但因为它们不是自身属性, 而是从原型链上继承的属性, 所以该方法也会返回false。 如果对象没有指定的属性, 该方法返回 false。

Object.prototype.toString(): 返回一个代表该对象的字符串
语法
object.toString()
描述
当对象需要转换为字符串时, 会调用它的toString() 方法.
默认情况下, 每个对象都会从Object上继承到toString() 方法, 如果这个方法没有被这个对象自身或者更接近的上层原型上的同名方法覆盖(遮蔽), 则调用该对象的toString() 方法时会返回 "[object type]",
这里的字符串type表示了一个对象类型

Object.prototype.valueOf(): 返回的是this值, 即对象本身
语法
object.valueOf()
返回值
在其他类型的对象中, valueOf有可能返回一个不同的值

 

posted @ 2018-12-22 14:30  konglingbin  阅读(1142)  评论(0编辑  收藏  举报