欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

初学JavaScript之对象的练习笔记

程序员文章站 2022-03-17 13:05:56
...
/**
 *  这里将description()方法改进一下,改成不可枚举的类型
 */
Object.defineProperty(Object.prototype,"description",{
    writable:true,
    enumerable:false,
    configurable:true,
    value:function(){
        console.log(this);
    }

});



/**
 *  对象属性特性
 *      (1)可写    (是否可以设置该属性的值)
 *      (2)可枚举  (是否可以通过for/in循环返回该属性)
 *      (3)可配置  (是否可以删除或者修改该属性)
 *
 *  对象特性
 *       (1)对象的原型(prototype) 指向另外一个对象,本对象的属性继承自它的原型对象
 *       (2)对象的类  (class) 是一个标识对象类型的字符串
 *       (3)对象的扩展标记  指明是否可以向该对象添加新属性
 *
 *
 * 三类对象
 *       (1)内置对象  如:数组、函数、日期、正则表达式
 *       (2)宿主对象 
 *       (3)自定义对象 由运行中的JavaScript代码创建的对象
 *
 * 两类属性 
 *       (1)*属性  是直接在对象中定义的属性
 *       (2)继承属性  是在对象的原型对象中定义的属性
 */


"===============创建对象================".description();
"---------对象直接量".description();
var empty = {};
var point = {x:0,y:0};
var point2 = {x:point.x,y:point.y};
var book = {
    "main title" : "JavaScript",  // 属性名字中有空格,必须用字符串表示
    'sub-title' : "JS",           // 属性名字中有连字符,必须用字符串表示
    "for" : "all audiences",      // "for"是保留字,必须用引号
    author :{
        firstname : "张",
        surname : "三丰"
    }
};


"---------通过new创建对象".description();
/**
 * 关键字new后跟随一个函数调用。这里的函数称作构造函数,构造函数用以初始化一个新创建的对象。
 */
var o = new Object();
var a = new Array();
var b = new Date();
var r = new RegExp("js");


"---------原型".description();
/**
 * 每个对象都有原型继承属性
 *  (1) 所有通过对象直接量创建的对象那个都具有同一个原型对象
 *  (2) 通过new和构造函数调用创建的对象的原型就是构造函数的prototype属性值
 */


"---------Object.create()".description();
/**
 * ES5定义了一个名为Object.create()的方法,它创建一个新对象。
 * 其中第一个参数就是一个对象的原型。
 * 第二个参数可选,用以对对象的属性进一步描述
 * Object.create()是一个静态函数
 */
var o1 = Object.create({ x : 1, y : 2});
o1.x.description();  //[Number 1]

var o2 = Object.create(null);    // 不继承任何属性和方法
var o3 = Object.create(Object.prototype); // 和 new Object()一样

//我们可以通过一个方法来兼容ES5和ES3
function inherit(p){
    if(p == null) throw TypeError();  // p是一个对象,不能是null
    if(Object.create) return Object.create(p);
    var type = typeof p;
    if(type !== "object" && type !== "function") throw TypeError();
    function fun(){};
    fun.prototype = p;
    return new fun();
}

"---------属性的查询和设置".description();
var author = book.author;
author.description();     // { firstname: '张', surname: '三丰' }
var name = author.surname;
name.description();       // [String: '三丰']
var title = book["main title"];
title.description();      // [String: 'JavaScript']


"---------作为关联数组的对象".description();
// 自行体会 关联数组对象的灵活之处
var customer = {address1 : "addr1",address2 : "addr2",address3 : "addr3",address4 : "addr4"};
var addr = ""
for (var i = 0; i < 4; ) {
    addr += customer["address" + ++i] + " ";
}
addr.description();


"---------继承".description();
/**
 * JS对象具有”自有属性“,也有一个属性是从原型对象继承而来。
 * 如果要查询对象obj的属性x,如果obj中不存在x,那将会继续在obj的原型对象中查询属性。
 * 如果原型对象也没有该属性,如果这个原型对象有原型,就继续往下查询,知道找到或者原型为null的对象为止。
 *
 * 对象原型属性构成一个”链“,通过这个”链“可以实现属性的继承。
 */
/**
 *  如果给对象obj的属性x赋值,如果obj中已经有属性x(不是继承而来的),那么这个复制操作只改变这个已有属性x的值。
 *  如果obj中不存在属性x,那么赋值操作给obj添加一个新属性x,
 *  如果之前obj继承自属性x,那么这个继承的属性就被新创建的同名属性所覆盖
 */
var objp1 = {};
objp1.x = 1;
var objp2 = inherit(objp1);
objp2.y = 2;
var objp3 = inherit(objp2);
objp3.z = 3;         
objp3.description();                 // { z: 3 }
objp3.toString().description();      // [String: '[object Object]']
(objp3.x + objp3.y).description();   // [Number: 3]

objp3.x = 666;          // 覆盖了继承来的属性
objp1.description();    // { x: 1 }

"---------属性的错误访问".description();
// 抛出一个类型错误异常,undefined没有length属性
// var len = book.subtitle.length;
// 一种简练的常用方法
var len = book && book.subtitle && book.subtitle.length;  // ==> undefined

// 内置构造函数的原型是只读的,赋值失败 但是不会报错  
Object.prototype = 0;    // Object.prototype值没有改变


"---------删除属性".description();
/**
 * delete运算符可以删除对象的属性。delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性
 */

// 由于已经删除的属性的引用依然存在,在JS某些实现中,可能因为这种不严谨的代码而造成内存泄漏。
// 所以在销毁对象的时候,要遍历属相中的属性,依次删除
var obja = {p:{x : 123}};
var objb = obja.p;
delete obja.p;
obja.description();   // {}
objb.description();   // { x: 123 }


"---------检查属性".description();
// 我们可以通过 in运算符、hasOwnPreperty() 和 propertyIsEnumerable() 方法来完成这个工作
// 甚至仅通过属性查询也可以做到这一点
var obj = { x : 1};
("x" in obj).description();          // [Boolean: true]
("y" in obj).description();          // [Boolean: false]
("toString" in obj).description();   // [Boolean: true] obj继承toString属性

// hasOwnProperty()方法用来检测给定的名字是否是对象的*属性。继承属性将返回false
obj.hasOwnProperty("x").description();        // [Boolean: true]     
obj.hasOwnProperty("y").description();        // [Boolean: false]  继承属性
obj.hasOwnProperty("toString").description(); // [Boolean: false]  继承属性

// propertyIsEnumerable()是hasOwnProperty()的增强版
// 只检测到时自有属性且这个属性的可枚举性为true时才返回true.
var obj = inherit({ y : 123});
obj.x = 333;
obj.propertyIsEnumerable("x").description();      // true
obj.propertyIsEnumerable("y").description();      // false y继承而来的
Object.prototype.propertyIsEnumerable("toString");  // fasle  不可枚举

// "!==" 判断一个属性是否是undefined  来判断有没有该属性
(obj.x !== undefined).description();         //[Boolean: true]
(obj.z !== undefined).description();         //[Boolean: false]  
(obj.toString !== undefined).description();  //[Boolean: true]

var obj = {x : undefined};
(obj.x !== undefined).description(); // [Boolean: false], 属性是存在的,值为undefined
("x" in obj).description();          // [Boolean: true]         


"---------枚举属性".description();
/**
 * for/in 循环 可以在循环中遍历对象中所有可枚举的属性(包括*属性 和 继承的属性)
 */
var obj = inherit({x:123});
obj.y = 444;
o.propertyIsEnumerable("x").description(); // [Boolean: false] 因为不是自有属性
for(p in obj)
{
    console.log(p);  // y 、 x  
}

/**
 *  用来枚举属性的对象工具
 *
 *  把p中的可枚举属性复制到o中,并返回o
 *  如果o和p中含有同名属性,则覆盖o中的属性
 *  这个函数并不处理getter和 setter以及复制属性
 */
function extend(o,p)
{
    for(prop in p){
        o[prop] = p[prop];
    }
    return o;
}

/**
 * 将p中的可枚举属性赋值到o中,并返回o
 * 如果o和p中有同名的属性,不受影响
 */
function merge(o,p)
{
    for(prop in p){
        if (o.hasOwnProperty(prop)) continue;
        o[prop] = p[prop];
    }
    return o;
}

/**
 *  如果 o中的属性在p中没有同名属性,则从o中删除这个属性
 */
function restrict(o,p)
{
    for(prop in o){
        if(!(prop in p)) delete o[prop];
    }
    return o;
}

/**
 *  如果o中的属性在p中存在同名属性,则从o中删除这个属性
 */
function subtract(o,p)
{
    for(prop in p)
    {
        delete o[prop];
    }
    return o;
}

/**
 * 返回一个新对象,这个对象同事拥有o和p的属性
 * 如果o和p中有重名属性,使用p中的属性值
 */
function union(o,p){
    return extend(extend({},o),p);
}


/**
 * 返回一个新对象,这个对象拥有同时在o和p中出现的属性
 * 很像求o和p的交集,单p中属性的值被忽略
 */
function intersection(o,p){
    return restrict(extend({},o),p);
}

/**
 * 返回一个数组,这个数组包含的是o中可枚举的自有属性的名字
 */
function keys(o){
    if(typeof o !== "object") throw TypeError();
    var result = [];
    for(var prop in o){
        if(o.hasOwnProperty(prop)) result.push(prop);
    }
    return result;
}


var obj1 = {"x":11,"y":22,"w":5555};
var obj2 = {"x":66,"y":88,"z":99};
// extend(obj1,obj2).description();        // { x: 66, y: 88, w: 5555, z: 99 }
// merge(obj1,obj2).description();         // { x: 11, y: 22, w: 5555, z: 99 }
// restrict(obj1,obj2).description();      // { x: 11, y: 22 }
// subtract(obj1,obj2).description();      // { w: 5555 }
// union(obj1,obj2).description();         // { x: 66, y: 88, w: 5555, z: 99 }
// intersection(obj1,obj2).description();  // { x: 11, y: 22 }
// keys(obj1).description();               // [ 'x', 'y', 'w' ]

/**
 *  Object.keys() 返回可枚举的自有属性
 *  Object.getOwnPropertyNames() 返回对象的所有自有属性的名称。
 */
Object.keys(obj1).description();               // [ 'x', 'y', 'w' ]
Object.getOwnPropertyNames(obj1).description();// [ 'x', 'y', 'w' ]


"---------属性getter 和 setter".description();
/**
 * 对象属性就是由名字、值和一组特性组成。
 * 属性值可以用一个或两个方法替代,这两个发方法是getter和setter。
 * 由getter和setter定义的属性称做“存取器属性”,它不同于“数据属性”,数据属性只是一个简单的值
 */

// 定义存取器属性最简单的方法是使用对象直接量语法的一种扩展写法
/**********
var o = {
    // 普通的数据属性
    data_prop : value,
    // 存取器属性都是成对定义的函数
    get accessor_prop(){函数体},
    set accessor_prop(value){函数体}
};
**********/

var point = {
    // 普通的可读写的数据属性
    x : 1.0,
    y : 1.0,

    // 可读写的储存器属性,它是getter和setter.
    // 函数体结束后不要忘记 逗号
    get r() { return Math.sqrt(this.x * this.x + this.y * this.y);},
    set r(value) {
        var oldvalue = Math.sqrt(this.x * this.x + this.y * this.y);
        var ratio = value / oldvalue;
        this.x *= ratio;
        this.y *= ratio;

    },

    // theta是只读存取器属性,它只是getter方法
    get theta(){return Math.atan2(this.y,this.x);}
}
point.description();     // { x: 1, y: 1, r: [Getter/Setter], theta: [Getter] }

var p = inherit(point);
p.x = 1;
p.y = 1;
p.r.description();       // [Number: 1.4142135623730951]
p.theta.description();   // [Number: 0.7853981633974483]



var serialnum = {
    // 这个数据属性包含下一个***
    // $ 符号按时这个属性是一个私有属性
    $n:0,
    // 返回当前值,然后自增
    get next(){ return this.$n++;},
    // 给n设置新的值,但只有当它比当前值打时才设置成功
    set next(value){
        if(value >= this.$n) this.$n = value;
        else throw "***的值不能比当前值小";
    }
}

serialnum.description();
serialnum.next.description();
serialnum.description();
serialnum.next = 6;
serialnum.description();


/**
 * 这个对象有一个可以返回随机数的储存器属性
 */
var random = {  
    get octet() {return Math.floor(Math.random()*256);},
    get uint16() {return Math.floor(Math.random()*65536);},
    get int16() {return Math.floor(Math.random()*65536) - 32768;}
}


"---------属性的特性".description();
/**
 * 属性还包括一些标识 可写、可枚举、可配置。
 * 数据属性 4个特性:值、可写性、可枚举性(enumerable)、可配置性(configurable)。
 * 存取器属性4个特性: 读取 (get) 写入(set) 可枚举、可配置性。
 *
 * "属性描述符" 中属性有:value、writable、enumerable 和 configurable。
 * 存取器属性 用 get、set 属性代替 value 和 writable.
 */

//{ value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor({x:1},"x").description(); 
//{ get: [Function: get octet],set: undefined,enumerable: true,configurable: true }
Object.getOwnPropertyDescriptor(random,"octet").description();

console.log(Object.getOwnPropertyDescriptor({},"x"));        // undefined,没有这个属性
console.log(Object.getOwnPropertyDescriptor({},"toString")); // undefined,继承属性
/**
 *  注意:从上述函数可以看出Object.getOwnPropertyDescriptor()只能得到自有属性的描述符
 *       该方法只能修改或者创建*属性,不能修改继承属性
 *       要想获得继承属性的特性,需要遍历原型链。
 */

// 想要新建属性具有某种特性,需要调用Object.defineProperty()
var obj = {z:7};
Object.defineProperty(obj,"x",{
    value:100,
    writable:true,
    enumerable:false,
    configurable:true
}).description();         // { z: 7 }
obj.x.description();      // [Number: 100]
obj.propertyIsEnumerable("x").description();  // [Boolean: false]

// 现在对属性x做修改,让它变为只读
Object.defineProperty(obj,"x",{writable:false});
obj.x = 666;            // 操作失败但是不报错
obj.x.description();    // [Number: 100]

// 由于属性依然是可配置的,因此可以通过这种方式对它进行修改
Object.defineProperty(obj,"x",{value : 888});
obj.x.description();    // [Number: 888]

/**
 * 提示:对于新创建的属性来说,默认属性是false或者undefined。
 *      对于修改已有属性来说,默认的特性值没有做任何改变。
 */

/**
 * 如果要同时修改或创建多个属性 用 Object.defineProperties()。
 * 第一个参数是对象,第二个参数是一个映射表。
 *
 * defineProperty 和 defineProperties() 返回的都是被修改后的对象。
 */
var p = Object.defineProperties({},{
    x:{value : 1,writable: true, enumerable:true, configurable:true},
    y:{value : 1,writable: true, enumerable:true, configurable:true},
    r:{get : function(){ return Math.sqrt(this.x * this.x + this.y * this.y);},
     enumerable:true, configurable:true}
});
p.description();     //  { x: 1, y: 1, r: [Getter] }

var p2 = Object.defineProperty({},"x",{
    value:100,
    writable:true,
    enumerable:true,
    configurable:true
});
p2.description();   //  { x: 100 }


/**
 * 注意:(1)如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性
 *      (2)如果对象是不可配置的,则不能修改它的可配置性和枚举性。
 *      (3)如果读取器属性是不可配置的,则不能修改其getter和setter方法,也不能转成数据属性
 *      (4)如果数据属性是不可配置的,则不能将它转化成读取器属性。
 *      (5)如果数据属性是不可配置的,则不能将它的可写性从false修改成true,但是可以从true修改成false
 *      (6)如果数据属性是不可配置且不可写的,则不能修改它的值。
 *      (7)如果数据属性可配置但不可写的值是可以修改的。(实际是先将其标记成可写,然后修改,最后转化成不可写)
 */


/**
 * 给Object.prototype 添加一个不可枚举的extend()方法
 */
Object.defineProperty(Object.prototype,"extend",{
    writable:true,
    enumerable:false,
    configurable:true,
    value:function(o){
        // 得到所有的自有属性,包括不可枚举属性
        var names = Object.getOwnPropertyNames(o);
        // 遍历它们
        for(var i = 0; i < names.length; i++)
        {
            //如果属性已经存在,则跳过
            if(name[i] in this) continue;
            // 获得o中的属性的描述符
            var desc = Object.getOwnPropertyDescriptor(o,name[i]);
            // 用它给this创建一个属性
            Object.defineProperty(this,name[i],desc);
        }
    }
});


"---------对象的三个属性".description();
/**
 * 原型(prototype)、 类(class)、可扩展性(extensible attribute)
 */
/**
 ** 原型属性
 *  对象的原型属性是用来继承属性的。
 *  原型属性是在实例对象之处就设置好的。
 *  1)使用对象直接量创建的对象使用Object.prototype作为原型
 *  2)通过new创建的对象使用构造函数的prototype属性作为原型
 *  3) 通过Object.create()创建的对象使用第一个参数作为原型
 */ 
//  通过Object.getPrototypeOf()可以查询它的原型。
//  检查一个对象是否是另一个对象的原型,使用isPrototypeOf()方法。

var p = {x:1};
var o = Object.create(p);
p.isPrototypeOf(o).description();                // [Boolean: true]  o继承自p
Object.prototype.isPrototypeOf(o).description(); // [Boolean: true]  p继承自Object.prototype

/**
 * 类属性
 * 对象的类属性是一个字符串,用以表示对象的类型信息。
 * 间接查询方法就是 toString()(继承自Object.prototype) 返回类型 [object class]
 */
// 写一个classof()函数
// 这个函数对自定义构造函数创建的对象是没办法区分的
function classof(o){
    if (o === null) return "Null";
    if (0 === undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8,-1);
}

/**
 * 可扩展性
 * 对象的可扩展性用以表示是否可以给对象添加新属性。(所有内置对象和自定义对下你给都是显式可扩展的)
 * Object.esExtensible() 用来判断该对象是否可扩展的。
 * Object.preventExtensions() 将对象转化成不可扩展的。(一旦转化成不可扩展的,就无法在将其转换回可扩展的了)
 * preventExtensions只影响到对象本身的可扩展性。(如果给不可扩展的对象原型添加属性,不可扩展对象同样继承该属性)
 *
 * Object.seal() 和 Object.preventExtensions()类似,
 * 除了能够将对象设置为不可扩展的,还可以将对象的所有自有属性都设置为不可配置的。
 * 也就是说,不能给该对象添加新属性,而且它一有的属性也不能删除或者配置,不过已有的科协属性依然可设置。
 * 可以使用Object.isSealed()来检测对象是否封闭
 *
 * Object.freeze()将更严格的锁定对象(冻结 freeze)。
 * 除了将对象设置为不可扩展和将其属性设置为不可配置的之外,还可以将它自有的所有数据属性设置为只读。
 * (如果对象的存取器属性有setter方法,存取器属性不受影响,仍可以通过setter赋值)
 * Object.isFrozen()来检测对象是否冻结
 *
 * preventExtensions()、seal()、freeze()都返回传入的对象
 * 可以通过函数嵌套的方式调用它们
 */
// 创建一个封闭对象,包括一个冻结的原型和一个不可枚举的属性
var obj = Object.seal(Object.create(Object.freeze({x:1}),{y:{value:2,writable:true}}));


"---------序列化对象".description();
/**
 * 对象序列化是指对象的状态转换为字符串,也可以将字符串还原成对象。
 */
var obj = {x:1,y:{z:[false,null,""]}};
var ostr = JSON.stringify(obj);
ostr.description()        // [String: '{"x":1,"y":{"z":[false,null,""]}}']
var p = JSON.parse(ostr); // p 是 obj的深拷贝
p.description();          // { x: 1, y: { z: [ false, null, '' ] } }

/**
 * JSON 的语法是js语法的子集,它并不能表示js里的所有值。
 * 支持 对象、数组、字符串、无穷大数字、true、false 和 null,并且它们可以序列化和还原。
 * NaN、Infinity和 - Infinity序列化结果是null.
 */


"---------对象方法".description();
/**
 * toString(),将返回一个表示调用这个方法的对象值的字符串。
 * 在需要转化成字符串的时候,JS都会调用这个方法。比如使用“+”。
 *
 * toLocaleString()这个方法返回一个表示这个对象的本地化字符串。
 */

({x:1,y:2}.toString()).description();         // [String: '[object Object]']
[1,2,3,4,5,6,].toString().description();      // [String: '1,2,3,4,5,6']
(function(){console.log("Hello world!")}).toString().description();
// [String: 'function (){console.log("Hello world!")}']
/**
 * toJSON()方法
 * Object.prototype实际上没有定义toJS0N()方法,但对于需要执行序列化的对象来说,
 * JSON.stringifyO方法会调用tJOSON()方法.如果在待序列化的对象中存在这个方法,则调用它,
 * 返回值即是序列化的结果,而不是原始的对象,
 */


/**
 * valueOf()方法
 * valueOfO方法和toString()方法非常类似
 * JavaScript需要将对象转换为某种原始值而非字符串的时候才会调用它,尤其是转换为数字的时候,
 * 如果在甭要使用原始值的上下文中使用了对象,JavaScript就会自动调用这个方法,
 * 默认的value0f()方法不足为奇,伹有些内S类自定义了valueOf()方法(比如Date.valueOf()),
 */