《JavaScript高级程序设计第三版》——细碎知识痛点整理(第六章)

面向对象的程序设计 

对象是一组没有特定顺序的值
6.1.1 属性类型
ECMAScript中有两种属性:数据属性和访问器属性。
1. 数据属性
Configurable 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
Enumerable 表示能否通过for-in循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
Writable 表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
Value 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined。
对于像前面例子中那样直接在对象上定义的属性,它们的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被设置为true,而[[Value]]特性被设置为指定的值。例如:

var person = { 
name: "Nicholas" 
}; 

这里创建了一个名为name的属性,为它指定的值是"Nicholas"。也就是说,[[Value]]特性将被设置为"Nicholas",而对这个值的任何修改都将反映在这个位置。
要修改属性默认的特性
Object.defineProperty()方法不能重复定义

2. 访问器属性
Configurable
Enumerable
Get 在读取属性时调用的函数。默认值为undefined。
Set 在写入属性时调用的函数。默认值为undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()来定义

var book = {
  _year: 2004,
  edition: 1
};

Object.defineProperty(book,"year",{
  get:function(){
    return this._year;
  },
  set:function(newValue){
    if(newValue > 2004){
      this._year = newValue;
      this.edition += newValue - 2004;
    }
} }); book.year
= 2010; console.log(book.edition)//7

以上代码创建了一个book对象,并给它定义两个默认的属性:_year和edition。_year前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。而访问器属性year则包含一个getter函数和一个setter函数。
使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化。

6.1.2  定义多个属性

var book = {};
Object.defineProperties(book,{
  _year: {
    value: 2004
  },
  edition: {
    value: 1
  },
  year: {
    get: function(){
      return this._year;
    },
    set: function(newValue){
      if(newValue >2004){
        this._year = newValue;
        this.edition += newValue - 2004;
      }
    }
  }
}) console.log(book)

6.2 创建对象
虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。
6.2.1 工厂模式
在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节

function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var person1 = createPerson('lxf',"23",'fw');
var person2 = createPerson("lxx","21");
console.log(person1);//{name: "lxf", age: "23", job: "fw", sayName: ƒ}
console.log(person2);//{name: "lxx", age: "21", job: undefined, sayName: ƒ}

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

6.2.2 构造函数模式
ECMAScript中的构造函数可用来创建特定类型的对象。

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("console.log(this.name);");//新实例化了一个对象
// this.sayName = function(){console.log(this.name)}
}
var person1 = new Person('lxf',"23",'fw');
var person2 = new Person('lxx',"23");

console.log(person1);
console.log(person2 instanceof Person);//我们在这个例子中创建的所有对象既是Object的实例,同时也是Person的实例
console.log(person2 instanceof Object);
console.log(person1.sayName == person2.sayName);//以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的

然而,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此,大可像下面这样,通过把函数定义转移到构造函数外部来解决这个问题

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
// this.sayName = new Function("console.log(this.name);");//新实例化了一个对象
// this.sayName = function(){console.log(this.name)}
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}

与工厂模式的区别
没有显式地创建对象;
直接将属性和方法赋给了this对象;
没有return语句。
函数名Person使用的是大写字母P,为了区别于ECMAScript中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3) 执行构造函数中的代码(为这个新对象添加属性);
(4) 返回新对象。

1. 将构造函数当作函数
以这种方式定义的构造函数是定义在Global对象(在浏览器中是window对象)中的。
2. 构造函数的问题
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。
可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。
6.2.3 原型模式
ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如按照字面意思来理解,那么 prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。Person 的每个实例——person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们与构造函数没有直接的关系。此外,要格外注意的是,虽然这两个实例都不包含属性和方法.

function Person(){};
  Person.prototype.name = "lxf";
  Person.prototype.age = "age";
  Person.prototype.sayName = function(){
  console.log(this.name);
}
var person1 = new Person();
var person2 = new Person();
person2.name = 'fff';

console.log(person1.sayName == person2.sayName);//true
console.log(Person.prototype.isPrototypeOf(person1));//true
console.log(Object.getPrototypeOf(person1));//{name: "lxf", age: "age", sayName: ƒ, constructor: ƒ}
console.log(person1.hasOwnProperty("name"));
console.log(person2.hasOwnProperty("name"));//检查属性是否在实例上

使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

delete person1.name; 

猜你喜欢

转载自www.cnblogs.com/laomi233/p/9108258.html