笔记:《JavaScript面向对象精要》-第3、4章

三、理解对象
3.1 定义属性
当一个属性第一次被添加给对象时,JavaScript在对象上调用一个名为[[Put]]的内部方法。这个操作不仅指定了初始的值,也定义了属性的一些特征。
当一个已有的属性被赋予一个新值时,调用的是一个名为[[Set]]的方法。
//Object实例化
var person1 = new Object();
person1.name = "liang";   //调用[[Put]]的内部方法

//对象字面形式
var person2 = {
     name: "liang"
};

person2.name = "zhu";   //调用[[Set]]方法

3.2 属性探测
in 操作符在给定对象中查找一个给定名称的属性,如果找到则返回true。
in操作符会检测自有属性和原型属性,如果只要检测自有属性,使用 hasOwnProperty()方法。
var person1 = {
   name: "liang",
   age: 26
};

console.log("name" in person1);   //true
console.log(person1.hasOwnProperty("name"));   //true

//tostring是原型属性,所以用hasOwnProperty检测结果为false
console.log("toString" in person1);   //true
console.log(person1.hasOwnProperty("toString"));   //false

//可用这样一个函数来鉴别原型属性,结果为true则是原型属性
function check(object, name){
     return (name in object) && !object.hasOwnProperty(name);
}
console.log(check(person1,"age"));   //false
console.log(check(person1,"toString"));  //true

3.3 删除属性
delete 操作符针对单个对象属性调用名为[[Delete]]的内部方法。操作成功时,它返回true。
var person1 = {
   name: "liang",
   age: 26
};

delete person1.age;
"age" in person1;   //false

同名的自有属性会覆盖原型属性的值。delete 可以删除自有属性,不能删除原型属性。

3.4 属性枚举
[[Enumerable]]是属性的一个内部特征,指示属性是否可枚举,默认为true(可枚举的)。
for-in 循环会枚举一个对象所有的可枚举属性,并将属性赋给一个变量。
var person1 = {
   name: "liang",
   age: 26
};

var property;
for (property in person1){
    console.log("Name" + ": " + property);    //枚举属性
    console.log("Value" + ": " + person1[property]);    //枚举属性的值
}   
//Name: name   Value: liang   Name: age   Value: 26

Object.keys() 方法,获取可枚举属性的名字的数组。
for-in 循环会遍历原型属性,而Object.keys()只返回自有属性。
var person1 = {
   name: "liang",
   age: 26
};

//获取数组
var propertys = Object.keys(person1);
propertys;    //["name", "age"]

//输出属性和属性值
var i, len;
for(i=0, len=propertys.length; i<len; i++){
      console.log("Name" + ": " + propertys[i]);
      console.log("Value" + ": " + person1[propertys[i]]);
}     //Name: name   Value: liang   Name: age   Value: 26

3.5 属性类型
对象的属性有两种类型:数据属性和访问器属性。
数据属性只包含一个值。
访问器属性不包含值,而是定义了一个当属性被读取时调用的函数(称为getter),和一个当属性被写入时调用的函数(称为setter)。
访问器属性仅需要getter或setter两者中的任意一个,也可以两者都有。
特殊关键字get和set被用在访问器属性名字的前面,后面跟着小括号和函数体。getter被期望返回一个值,而setter则接受一个需要被赋给属性的值作为参数。
var person1 = {
     _name: "liang",    //命名规范:下划线表示该属性被认为是私有的

    get name(){
        console.log("the name is ");
        return this._name;
    },    //此处加逗号

    set name(value){
        console.log("set name to",value);
        this._name = value;
    }
};

//注意:使用的是person1.name,而不是person1._name
console.log(person1.name);   //the name is "liang"
console.log(person1._name);    //liang

person1.name = "zhu";    //set name to zhu "zhu"
console.log(person1.name);   //the name is "zhu"

console.log(Object.keys(person1));  //["_name", "name"]
通常情况下不使用访问器属性,但当你希望赋值操作会触发一些行为,或读取的值需要通过计算所需的返回值,可使用访问器属性。
如果只定义getter,该属性就变成只读。
如果只定义setter,该属性就变成只写。

3.6 属性特征
3.6.1 通用特征
有两个属性特征是数据和访问器属性都具有的。
[[Enumerable]],决定了是否可以遍历该属性。
[[Configurable]],决定了该属性是否可配置。delete可以删除可配置的属性。

Object.definedProperty() 方法,可用来改变属性特征。有3个参数:拥有该属性的对象、属性名、包含需要设置特征的属性描述对象。
var person1 = {
    name: "liang"
};

Object.defineProperty(person1,"name",{
    enumerable: false
});

console.log("name" in person1);   //true
console.log(Object.keys(person1));   // []

3.6.2 数据属性特征
数据属性额外拥有两个特征:
[[Value]] 包含属性的值。
[[Writable]] 指示该属性是否可以写入。
var person1 = {};

Object.defineProperty(person1,"name",{
    value: "liang",
    writable: true,
    enumerable: true,
    configurable: true
});
Object.defineProperty() 方法调用时,会首先检查该属性是否存在,如果不存在,将根据属性描述对象指定的特征创建。调用这个方法,如果不指定特征的布尔值,它会默认设置为false。

3.6.2 访问器属性特征
访问器属性不需要存储值,因此没有[[Value]]和[[Writable]]。
有另外两个额外特征:[[Get]]和[[Set]]
之前的例子:

var person1 = {
     _name: "liang",    //命名规范:下划线表示该属性被认为是私有的,实际上还是公有的

    get name(){
        console.log("the name is ");
        return this._name;
    },     //此处加逗号

    set name(value){
        console.log("set name to",value);
        this._name = value;
    }
};
改写成如下形式:
var person1 = {
     _name: "liang"
};

Object.defineProperty(person1,"name",{   //注意这里的name,不是_name,也可以用别的字符串来定义访问器属性
    get:function(){
        console.log("Reading name");
        return this._name;
    },
    set:function(value){
        console.log("Setting name to ",value);
        this._name=value;
    },
    enumerable:true,
    configurable:true
});
 
person1.name = "zhang";  //setting name to zhang   "zhang" 

3.6.4 定义多重属性
Object.defineProperties() 可以为一个对象同时定义多个属性。
接受两个参数:需要改变的对象、一个包含所有属性信息的对象。
var person1 = {};

Object.defineProperties(person1,{
     _name: {
        value: "liang",
        enumerable: true,
        configurable: true,
        writable: true
    },

    name: {
        get:function(){
            console.log("reading name");
            return this._name;
        },
       set: function(value){
            console.log("setting name to %s",value);
            this._name=value;
        },
       enumerable: true,
       configurable: true
    }
});

person1.name;   //reading name  "liang"
person1._name;   //"liang"

3.6.5 获取属性特征
Object.getOwnPropertyDescriptor() 方法获取属性的特征。
这个方法只适用于自有属性。接受两个参数:对象、属性名。
var person1 = {
   name: "liang"
};

var descriptor = Object.getOwnPropertyDescriptor(person,"name");
descriptor;   //{value: "liang", writable: true, enumerable: true, configurable: true}

3.7 禁止修改对象
[[Extensible]] 指明该对象是否可以被修改。有3种方法来锁定对象属性。
Object.preventExtensible() 禁止扩展,对象不能继续添加新的属性。 Object.isExtensible() 判断是否为可扩展的。
Object.seal() 封印对象,对象不能继续添加新的属性,且不能删除和改变属性类型。 Object.isSealed() 判断是否被封印的。
Object.freeze() 冻结对象,对象不 能继续添加新的属性,且不能删除和改变属性类型,还不能写入任何数据属性。 Object.isFrozen() 判断是否被冻结。
var person1 = {
    name: "liang"
}

Object.seal(person1);
delete person1.name;    //将无法删除
console.log(Object.isExtensible(person1));   //false
console.log(Object.isSealed(person1));   //true

四、构造函数和原型对象
4.1 构造函数
构造函数名的首字母要大写。
function Person(){
   //statement
}

构造函数接受一个命名参数name并将其赋给this对象的name属性。
function Person(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    };
}

var person1 = new Person("liang");

person1.sayName();  //"liang"

构造函数中还能用Object.defineProperty() 方法来帮助我们初始化。
function Person(name){
    Object.defineProperty(this,"name",{
        get:function(){
            return name;
        },
        set:function(newName){
            name = newName;
       },
        enumerable:true,
        configurable:true
    });
}

4.2 原型对象
几乎所有的函数(除了一些内建函数)都有一个名为prototype的属性,该属性是一个原型对象,用来创建新的对象实例。
所有创建的对象实例共享该原型对象,且这些对象实例可以访问原型对象的属性。
当你试图访问一个对象的某个属性时,JavaScript首先在自有属性里查找该名字,如果在自有属性里没有找到则查找原型属性。
4.2.1 [[Prototype]] 属性
对象实例通过内部属性[[Prototype]]跟踪其原型对象。该属性是对象实例指向原型对象的指针。
Object.getPrototypeOf() 方法读取[[Prototype]]属性的值。
var obj = {};

console.log(Object.getPrototypeOf(obj) === Object.prototype);   // true
//任何一个泛用对象,其[[Prototype]]属性始终指向Object.prototype

isPrototypeOf() 方法检测某个对象(Object)是否是另一个对象(obj)的原型对象。
var obj = {};

console.log(Object.prototype.isPrototypeOf(obj));   //true
或者说isPrototypeOf() 是检测一个对象(person1)是否是另一个对象(Person)的对象实例。
function Person(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    };
}

var person1 = new Person("liang");

Person.prototype.isPrototypeOf(person1);   //true

Object.getPrototypeOf(person1) === Person.prototype;   //true

4.2.2 在构造函数中使用原型对象
function Person(name){
    this.name = name;
}

console.log(Person.prototype);   //{constructor: ƒ}

Person.prototype.sayName = function(){
  return this.name;
};

console.log(Person.prototype);   //{sayName: ƒ, constructor: ƒ}

var person1 = new Person("liang1");

person1.sayName();    //"liang1"
上例中,sayName() 现在是一个原型属性而不是自有属性。
如下的构造函数,sayName是一个自有属性,区分这两种创建方式。
function Person(name){
    this.name = name;
    this.sayName = function(){
    return this.name;
    }
}

console.log(Person.prototype);   //{constructor: ƒ}

原型对象上存储其他类型的数据,存储引用值时要注意,这些引用值会被多个实例共享。
function Person(name){
    this.name = name;
}

Person.prototype.favorites= [];

var person1 = new Person("liang");
var person2 = new Person("zhu");

person1.favorites.push("pizza");
person2.favorites.push("quinoa");

person1.favorites;   //["pizza", "quinoa"]

通过一个对象字面形式替换原型对象,来设置多个原型属性。
function Person(name){
    this.name = name;
}

Person.prototype={
    sayName: function(){
        return this.name;
    },
    toString: function(){
        return "[Person " + this.name + "]";
    }
};

var person1 = new Person("liang");
console.log(person1.sayName());    //"liang"
console.log(person1.toString());    //"[Person liang]"
使用对象字面形式改写原型对象改变了构造函数的属性,因此它现在指向Object而不是Person。
console.log(person1.constructor === Person);    //false
console.log(person1.constructor === Object);    //true
这是因为原型对象具有一个constructor属性,这是对象实例所没有的。当一个函数被创建时,它的prototype属性也被创建,且该原型对象的constructor属性指向该函数。当使用对象字面形式改写原型对象Person.prototype时,其contructor属性将被置为泛用对象Object。
为避免这一点,手动重置constructor属性
function Person(name){
    this.name = name;
}

Person.prototype={
    constructor:Person,
    sayName: function(){
        return this.name;
    },
    toString: function(){
        return "[Person " + this.name + "]";
    }
};

构造函数、原型对象、对象实例,这三者的关系:
对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。
所有一切对象的原型顶端,都是Object.prototype。

4.2.3 改变原型对象
给定类型的所有对象实例共享一个原型对象。[[Prototype]]属性只是一个指向原型对象的指针,任何对原型对象的改变都立即反应到所有引用它的对象实例上。
使用Object.seal()或Object.freeze()方法,将无法添加自有属性或改变冻结对象的自有属性,但可以通过在原型对象上添加属性来扩展这些对象实例。
var person1 = new Person("liang");
var person2 = new Person("zhu");

Object.freeze(person1);

Person.prototype.sayHi = function(){
    console.log("Hi");
};

person1.sayHi();
person2.sayHi();

4.2.4 内建对象的原型对象
所有内建对象都有构造函数,因此也都有原型对象可改变。
Array.prototype.sum = function(){
    return this.reduce(function(previous,current){    //reduce()是数组方法
        return previous + current;
    });
};

var numbers = [1,2,3,4,5];
numbers.sum();   //15
在sum() 内部,this指向数组的对象实例numbers,于是该方法也可以自由使用数组的其他方法,比如reduce()。
内建对象的原型对象虽然可以改变,但不建议在生产环境中使用。可以用来做实验和验证新功能。


猜你喜欢

转载自blog.csdn.net/kjhz_liang/article/details/80439083