我爱编程

js对象

2018-04-11  本文已影响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

对象的描述符

通过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);
上一篇 下一篇

猜你喜欢

热点阅读