js对象

作者: 李华炎 | 来源:发表于2018-04-11 12:35 被阅读0次

    对象的概述

    什么是对象?

    对象是一个具体的事物,包含一系列的属性,这些属性是无序的,每一属性都有一个字符串key和对应的value

    在js语言中,除了基本数据类型剩下的就是对象了

    对象原型上的属性

    Object.prototype上的属性
    1. obj.constructor ; 指向创建该对象的构造函数
    2. obj.hasOwnProperty(propertyName); 返回一个boolean类型的值
    3. obj.isPrototypeOf(obj2); 判断obj是不是obj2的原型
    4. obj.propertyIsEnumerable(propertyName)
    5. obj.toLocaleString();
    6. obj.toString();
    7. obj.valueOf();
    属性操作

    读写对象属性 : 读取对象obj.z; 写对象obj.z=3; 循环读写对象属性obj[string]

    属性异常

    删除属性 : delete obj.z

    检测属性 : hasOwnProperty 或 in

    枚举属性 : obj.propertyIsEnumerable(propertyName), 一个boolean值

    设置枚举属性 : Object.defineProperty(obj, "price", {enumerable : false, value : 1000});为obj设置一个不可枚举的属性price

    • 判断对象是否拥有某个属性obj.hasOwnProperty(propertyName) 该方法不判断原型链上的属性,只判断自身拥有的属性,自身拥有即为true,否则为false
    • 判断对象或对象的原型链上是否拥有某一属性可以使用in,如console.log("name" in obj) ,如果在原型链上能找到name属性即返回true,反之为false

    • 判断是否可以使用delete关键字删除对象的属性,可以查看对象的自身属性描述中的configurable是否为true,当描述对象中的configurable值为true时才可以删除对象的属性。

      delete Object.prototype; //false
      var descriptor = Object.getOwnPropertyDescriptor(Object,"prototype");
      console.log(descriptor);
      /*
      configurable: false
      enumerable: false
      value: {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
      writable: false
      __proto__: Object
      */
      descriptor.configurable;  //false,代表不允许删除或删除失败
      
      //定义的全局变量也不能通过delet删除
      var globalVal = 1;
      delete globalVal; //false
      
      // Object.defineProperty(obj, propertyName, {});
      // Object.defineProperty(为某个要添加属性的对象, 要添加的属性名, 对将被修改或定义的属性的描述);
      var obj = {}
      Object.defineProperty(obj, 'li', {value: '厉害了...'});
      

    对象的描述符

    通过Object.defineProperty(obj, propertyName, descriptor);

    如下的属性都是描述符对象(descriptor)的key

    1. writable 可写的
    2. enumerable
    3. configurable
    4. value
    5. get
    6. set
    对象属性实例:
    var obj = {x:1};
    console.log(obj.y); //undefined
    var yz = obj.y.z;   //TypeError:Cannot read property "z" of undefined
    obj.y.z = 2;        //TypeError:Cannot set property "z" of undefined
    
    var yz = obj && obj.y && obj.y.z; //当obj、obj.y、obj.y.z都存在时,给yz赋值
    
    //---------------------------------------------------------------------------------
    var cat = new Object();
    cat.legs = 4;
    cat.name = "kitty";
    
    console.log("legs" in cat); //true
    console.log("abc" in cat);  //false
    console.log("toString" in cat);//true
    
    console.log(cat.hasOwnProperty("legs"));        //true
    console.log(cat.hasOwnProperty("toString"));    //false
    
    console.log(cat.propertyIsEnumerable("legs"));      //true
    console.log(cat.propertyIsEnumerable("toString"));  //false
    
    Object.defineProperty(cat,"price",{enumerable:false, value:1000});
    console.log("price是否可枚举: "+cat.propertyIsEnumerable("price"));  //false
    
    //---------------------------------------------------------------------------------
    //过滤原型链上的属性
    var o = {x:1,y:2,z:3};
    var obj = Object.create(o);
    obj.str = "我是自身属性";
    for(var key in obj) {
      //console.log(key);   // 会打印原型链o上的属性
    }
    
    for(var key1 in obj) {
      if (obj.hasOwnProperty(key1)){  // 过滤o的属性
        console.log(key1);
      }
    }
    

    创建对象的方式(js中)

    1.字面量的方式创建对象

    2.使用new关键字创建对象(new构造器)

    3.通过原型继承创建对象(Object.create(obj.prototype))

    1. 字面量的方式创建对象

      var obj = {
           name : "zhangsan",
           age : 18,
          sayHi : function () {
               console.log("你好啊!我是:" + this.name)
          }
      }
      

    2. 使用new关键字创建对象

      var obj1 = new Object();
      obj1.name = "123";           // 动态的给对象添加属性,js是一种动态的语言
      console.log(obj1.name);      // 123
      

      使用构造函数创建对象

      function Cat (name, color) {
           this.name = name;
           this.color = color;
           this.run = function() {
               console.log("跑得快");
       }
      }
      var cat = new Cat("coco","white");
      console.log(cat.name);       // coco
      console.log(cat.color);      // white
      
    1. 通过原型继承创建对象

      /**
       * 使用对象原型创建新的实例
       * @param obj 是被创建出来的对象的原型对象
       * @returns 返回对象
       */
      function createObj(obj){
          if (obj == null) throw TypeError;
          if (Object.create) {
              return Object.create(obj);
          }
          if (typeof obj !== "object" && typeof obj !== "function") throw TypeError;
          function F(){};
          F.prototype = obj;
          return new F();
      }
      
      // 创建一个空对象
      var nullObj = createObje(Object.prototype);  // 和直接量{}一样
      

    对象的方法

    1.公共方法

    在原型上定义公共方法要比在构造函数中定义更节省硬件的损耗

    // 构造函数
    function User(name,age) {
        this.name = name;
        this.age = age;
        this.study = function(course){
            console.log('正在学习'+course);
        }
    }
    
    // 给构造函数的原型对象添加方法,
    // 这些方法在每个由User构造器实例化出来的对象都可以访问
    // 称为公共方法
    User.prototype.getName = function () {
        return this.name;
    }
    User.prototype.getAge = function () {
        return this.age;
    }
    
    var u = new User('张三',20);
    console.log(u.getName());
    console.log(u.getAge());
    

    2.私有方法

    对象的私有方法只能内部调用

    function Classroom(students, teacher) {
         this.students = students;
         this.teacher = teacher;
    
         // 私有方法,返回学生的数量
         _studentsCount = function () {
             console.log('学生总数:' + students.length);
         }
         // 对象内部调用
         _studentsCount();
    }
    
    var classOne = new Classroom(['小张','小米'],'Mr.Li');
    console.log(classOne.teacher);
    // classOne._studentsCount();   // 错误,因为不是对象的公共属性
    

    3.特权方法

    特权方法可以通过自身的公有方法访问到自身的私有成员。

    // 特权方法:获取用户的出生年份
    function User(name, age) {
        // 私有变量
        var year = (new Date()).getFullYear() - this.age;
      
        this.name = name;
        this.age = age;
        // 创建一个特权方法,能够访问私有变量year,并保证自身是公有可访问的
        this.getBornYear = function () {
            console.log(year);
        }
    }
    
    var user = new User('大熊', 1989);
    user.getBornYear();
    

    4.静态方法

    静态方法通过构造函数名就可以直接调用

    /**
     * 动态生成方法(get/set)
     * @param properties 是一个对象
     * @constructor
     */
    function User(properties) {
        for (var item in properties) {
            (function (currentObj) {
                var p = item;
                // get 获取某个属性
                currentObj['get'+p] = function () {
                    return properties[p];
                }
    
                // set 设置某个属性
                currentObj['set'+p] = function (val) {
                    properties[p] = val;
                }
            })(this); // 将当前对象传递到闭包中
        }
    }
    
    // 创建一个新的用户实例,并把有两个属性的对象作为参数传递进入
    // user对象的属性是私有的,只能通过get/set获取
    var user = new User({
        name: '张三',
        age: 18,
    });
    
    // console.log(user.name);  // 错误的undefined , 会给对象添加属性
    console.log(user.getname());
    
    // 给User对象添加静态方法,可以做任意的事情
    User.staticMethod = function () {
        console.log('我是静态方法')
    };
    // 调用静态方法
    User.staticMethod();
    

    对象的访问方式

    1. 对象的属性访问,可以通过点语法访问符合标识符命名规则的属性。如:object.property
    2. 对象的key 访问,可以访问不符合标识符的命名规则的属性。如:object['prefix-name']

    标识符命名规则:只能包含字母、美元符合、数字和下划线,并且不能以数字开头

    Object.defineProperty(obj, "x", { value : 1 }); 此时,enumerable默认为false

    obj.x = 1;如果使用直接赋值的方式创建对象的属性,则这个属性的enumerable为true

    使用构造函数创建对象的升级

    1. 构造函数里面只定义属性
    2. 方法放在构造函数的原型上定义。这样做的目的可以减少每次创建对象时需要的内存空间,只需开辟用于存放属性的空间就可以了,至于方法可以通过对象的内部原型 .__proto__ 到构造函数的原型上调用需要的方法。
    // 未升级的写法
    function Cat (name, color) {
        this.name = name;
        this.color = color;
        this.run = function() {
            console.log("跑得快");
        }
    }
    
    // 第一次升级的写法
    function Cat (name, color) {
        this.name = name;
        this.color = color;
    }
    
    Cat.prototype = {
        // 在构造函数的原型上定义run方法,这样所有通过构造函数Cat创建出来的对象都可以使用run方法
        run = function() {
            console.log("跑得快");
        }
    }
    
    
    // 第二次升级
    function Cat (options) {
        this.name = name;
        this.color = color;
    }
    
    Cat.prototype = {
        run = function() {
            console.log("跑得快");
        }
    }
    
    var cat = new Cat({
        name: "susu",
        color: "black"
    });
    
    // 第三次升级
    function Cat (options) {
        this._init(options);
    }
    
    Cat.prototype = {
        run = function() {
            console.log("跑得快");
        },
        _init : function (options) {
            this.name = options.name || "";
            this.color = options.color || "";
        }
    }
    

    使用原型的注意事项

    1.使用对象访问属性的时候,如果在本身内找不到就会去原型中查找;但是使用点语法进行属性赋值的时候,并不会去原型中进行查找;使用点语法赋值的时候,如果对象中不存在该属性,就会给该对象新增该属性,而不会去修改原型中的属性(实例只使用原型不修改原型,但原型是可以被修改的)

    2.如果在原型中的属性是引用类型的属性,那么所有的对象共享该属性,并且一个对象修改了该引用类型属性中的成员,其他对象也都会受影响 (原型中的引用类型属性被修改,其他引用该属性的对象的值也跟着被改变,因为引用类型的赋值是一个地址)

    3.一般情况下不会将属性放到原型对象中,一般情况下原型中只会放需要共享的方法或常量

    // 构造函数
    function Person(){
      
    }
    
    // 字面量对象
    var x = {
        brand:"laosilaisi",
        type:"huanying"
    };
    
    // 为Person构造函数的原型添加属性,该属性是一个引用类型的
    Person.prototype.car = x;
    
    var p = new Person();
    console.log(p.car.brand);   // laosilaisi
    
    // 替换Person原型上的属性car的引用(此时的car指向另一个对象,该对象已经不是x了)
    Person.prototype.car = {
        brand:"BYD"
    };
    
    var p1 =new Person();
    console.log(p1.car.brand);  // BYD
    console.log(p.car.brand);   // BYD  
    
    p.car = {
        // 为p对象添加一个car属性,此时的p只会使用自身的car属性,不会再去原型对象上找car属性了
    };
    console.log(p.car.brand);   //  在自身找到car属性  undefied
    
    // 结论:
    // 1.通过实例是不能修改原型上的成员的,但是可以访问原型上的成员。
    // 2.通过实例的构造函数的prototype属性可以修改原型。
    // 3.原型上的属性是引用类型时,只要被修改,引用该属性的其他变量的值也发生变化。
    

    对象与原型及原型链之间的关系

    构造器:Foo (每一个构造器都有prototype属性)

    构造器原型:Foo.prototype (构造器原型也是对象,所以也有proto属性)

    对象:obj (所有的对象都有proto属性)

    当定义一个函数function Foo(){} 时,系统会默认的为该函数Foo 添加一个属性prototype ,当通过函数Foo使用new关键字创建对象时obj 时,obj可以通过对象自身的属性proto 指向该对象的构造器的原型对象Foo.prototype

    function Foo () {
        // 系统会默认为该函数添加prototype属性
    }
    
    Foo.prototype.z = 3;    // 原型上的属性
    var obj = new Foo();    // 创建实例obj
    
    //obj可以通过自身的proto属性指向构造器(Foo)的原型对象(Foo.prototype),从而可以访问到在原型链中的z
    console.log(obj.z);     //3   
    
    obj.z = 5;
    console.log(obj.z);     //5
    
    delete obj.z;           //true
    console.log(obj.z);     //3
    
    delete Foo.prototype.z; //true
    console.log(obj.z);     //undefined
    

    实例截图

    1

    object.png

    2


    creat.png

    深拷贝/浅拷贝

    浅拷贝:只拷贝当前对象中的所有属性(即只复制一层对象的属性)

    深拷贝:而深复制则递归复制了对象所有层级的属性,包括属性中的引用类型

    /**
     * 浅拷贝
     * @param src
     * @returns {{}}
     */
    function shallowCopy(obj) {
        var newObj = {};
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                newObj[prop] = obj[prop];
            }
        }
        return newObj;
    }
    
    /**
     * 深拷贝
     * @param obj
     */
    var deepCopy = function(obj) {
        var str; // 通过序列化与反序列化也可以创建对象,所有定义了该变量
        var newObj = obj.constructor === Object ? {} : [];
        if (typeof obj !== 'object'){
            return;
        }else if(window.JSON){
            str = JSON.stringify(obj);  // 序列化对象
            newObj = JSON.parse(str);   // 还原
        }else{
            for (var key in obj) {
                newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
            }
        }
        return newObj;
    }
    
    
    
    var obj = { a:1, arr: [2,3] };
    
    // 浅复制会导致 obj.arr 和 shallowObj.arr 指向同一块内存地址,
    // 任何一方被修改,另一方也被改变
    var shallowObj = shallowCopy(obj);
    shallowObj.arr.push('浅拷贝对象会改变原对象的引用类型属性的值');
    console.log(obj);
    console.log(shallowObj);
    
    var deepObj = deepCopy(obj);
    deepObj.arr.push('深拷贝');
    console.log(obj);
    console.log(deepObj);
    

    相关文章

      网友评论

        本文标题:js对象

        本文链接:https://www.haomeiwen.com/subject/cfvahftx.html