2020-12-24日:面向对象的程序设计(一);
- 属性类型 :数据属性和访问器属性
- (一):数据属性
- (1)四个行为特性:
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特 性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的 这个特性默认值为 true ;
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定 义的属性,它们的这个特性默认值为 true;
- [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的 这个特性默认值为 true;
- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候, 把新值保存在这个位置。这个特性的默认值为 undefined;
- (2)修改属性默认的特性: Object.defineProperty()方法 ;
- 三个参数:属性所在的对象、属性的名字和一个描述符对象;
- 描述符(descriptor)对象的属 性必须是:configurable、enumerable、writable 和 value;
-
var person = {}; Object.defineProperty(person, "name", { writable: false, value: "Nicholas", }); alert(person.name); //"Nicholas" person.name = "Greg"; // 值"Nicholas"是只读,属性的值是不可修改 的,尝试为它指定新值,则在非严格模式下,赋值操作将被忽略;在严格模式下,赋值操作将会导 致抛出错误; alert(person.name); //"Nicholas"
- configurable 设置为 false,表示不能从对象中删除属性:
-
var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas", }); alert(person.name); //"Nicholas" delete person.name; alert(person.name); //"Nicholas"
- 一旦把属性定义为不可配置的, 就不能再把它变回可配置;再调用 Object.defineProperty()方法修改除 writable 之外 的特性,都会导致错误 ;
-
var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas", }); //抛出错误 Object.defineProperty(person, "name", { configurable: true, value: "Nicholas", });
- ,可以多次调用 Object.defineProperty()方法修改同一个属性,但在把 configurable 特性设置为 false 之后就会有限制了
- (二)访问器属性:不包含数据值;
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特 性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为 true。 ;
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这 个特性的默认值为 true;
- [[Get]]:在读取属性时调用的函数。默认值为 undefined;
- [[Set]]:在写入属性时调用的函数。默认值为 undefined;
- 访问器属性不能直接定义,必须使用 Object.defineProperty()来定义;
-
var book = { _year: 2004, edition: 1 }; // _year 前面 的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性 ; Object.defineProperty(book, "year", { get: function () { return this._year; }, set: function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } }, }); book.year = 2005; alert(book.edition); //2
- 不一定非要同时指定 getter和 setter。只指定 getter意味着属性是不能写;
- 只指定 setter 函数的属性也 不能读;
- Object.defineProperty() 方法之前,要创建访问器属性,一般都使用两个非标准的方法: __defineGetter__()和__defineSetter__()
-
var book = { _year: 2004, edition: 1 }; //定义访问器的旧有方法 book.__defineGetter__("year", function () { return this._year; }); book.__defineSetter__("year", function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } }); book.year = 2005; alert(book.edition); //2
- 不支持 Object.defineProperty() 方法的浏览器中不能修改[[Configurable]] 和 [[Enumerable]];
- 定义多个属性 :Object.definePro- perties()方法 ;
- 接收两个对象参数:
- 要添加和修改其属性的对象;
- 第二个对象的属性与第一个对象中要添加或修改的属性一一对 应;
-
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; } }, }, });
- 以上代码在 book 对象上定义了两个数据属性(_year 和 edition)和一个访问器属性(year);
- 区别是这里的属性都是在同一时间创建;
- 读取属性的特性 :Object.getOwnPropertyDescriptor()方法 ;
- 接收两个参数;
- 属性所在的对象;
- 要读取其描述符的属性名;
- 返回值:
- 一个对象;
- 如果 是访问器属性,这个对象的属性有 configurable、enumerable、get 和 set;
- 如果是数据属性,这 个对象的属性有 configurable、enumerable、writable 和 value
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; } }, }, }); var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); alert(descriptor.value); //2004 alert(descriptor.configurable); //false alert(typeof descriptor.get); //"undefined" var descriptor = Object.getOwnPropertyDescriptor(book, "year"); alert(descriptor.value); //undefined alert(descriptor.enumerable); //false alert(typeof descriptor.get); //"function"
- 对于数据属性_year,value 等于初的值,configurable 是 false,而 get 等于 undefined。 对于访问器属性 year,value 等于 undefined,enumerable 是 false,而 get 是一个指向 getter 函数的指针;
- 创建对象
- 工厂模式 :
-
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
- 构造函数模式 :
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
- Person()中的代码 除了与 createPerson()中相同的部分外,还存在以下不同之处:
- 没有显式地创建对象;
- 直接将属性和方法赋给了 this 对象;
- 没有 return 语句;
- 创建 Person 的新实例,必须使用 new 操作符; 这种方式调用构造函数实际上会经历以下 4 个步骤;
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象;
- 在前面例子的后,person1 和 person2 分别保存着 Person 的一个不同的实例;
- 两个对象都 有一个 constructor(构造函数)属性;
- 该属性指向 Person;
- 创建的所有对象既是 Object 的实例,同时也是 Person 的实例 ;
- instanceof 操作符可以得到验证;
-
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true
- 任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数 ;
- 如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样;
- 将构造函数当作函数 :
// 当作构造函数使用 var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas"
// 作为普通函数调用 Person("Greg", 27, "Doctor"); // 添加到 window window.sayName(); //"Greg"
// 在另一个对象的作用域中调用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
- 不使用new操作符调用Person()会出现什么结果:属性和方法都被添加给window 对象了。有读者可能还记得,当在全局作用域中调用一个函数时,this 对象总是指向 Global 对象(在 浏览器中就是 window 对象)。因此,在调用完函数之后,可以通过 window 对象来调用 sayName()方 法,并且还返回了"Greg";
- 也可以使用 call()(或者 apply())在某个特殊对象的作用域中 调用Person()函数。这里是在对象o的作用域中调用的,因此调用后o就拥有了所有属性和sayName() 方法;
- 构造函数的问题 :每个方法都要在每个 实例上重新创建一遍 ;
- 创建两个完成同样任务的 Function 实例的确没有必要;况且有 this 对象在,根本不用在 执行代码前就把函数绑定到特定对象上面。因此,大可像下面这样,通过把函数定义转移到构造函数外 部来解决这个问题;
-
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
- 原型模式 :每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法
- prototype 就是通过调用构造函数而创建的那个对象实例的原型对象;
- 用原型对象的好处是可以 让所有对象实例共享它所包含的属性和方法; [ 不必在构造函数中定义对象实例的信息,而是 可以将这些信息直接添加到原型对象中 ];
-
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
- 将 sayName()方法和所有属性直接添加到了 Person 的 prototype 属性中;
- 构造函数 变成了空函数;
- 即使如此,也仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属 性和方法;
- 与构造函数模式不同的是:
- 新对象的这些属性和方法是由所有实例共享的;
- person1 和 person2 访问的都是同一组属性和同一个 sayName()函数;
1. 理解原型对象 :
- 只要创建了一个新函数;
- 就会根据一组特定的规则为该函数创建一个 prototype 属性;
- 属性指向函数的原型对象;
- 默认情况下 ,所有原型对象都获得一个 constructor (构造函数)属性 ;
- 属性包含一个指向 prototype 属性所在函数的指针;
- Person.prototype. constructor 指向 Person ;
- 通过这个构造函数,我们还可继续为原型对象 添加其他属性和方法;
- 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部 属性),指向构造函数的原型对象;
- 这个指针叫[[Prototype]];
- 脚本中 没有标准的方式访问[[Prototype]],但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性 __proto__ ;
- 这个连接存在于实例与构造函数的原型对象之间;
- 而不是存在于实例与构造函数之间;
- Person 构造函数、Person 的原型属性以及 Person 现有的两个实例之间的关系;
![]()
- ,Person.prototype指向了原型对象;
- Person.prototype.constructor又指回了Person;
- 原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性;
- Person 的每个实例—— person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;
- 换句话说,它们 【实例】与构造函数没有直接的关系;
- 通过 isPrototypeOf()方法来确定对象之 间是否存在这种关系;
- 如果实例的 [[Prototype]]指向调用 isPrototypeOf()方法的对象 (Person.prototype),那么这个方法就返回 true ;
- 判断 构造函数、与实例是否指向同一个原型对象;
-
alert(Person.prototype. isPrototypeOf (person1)); //true alert(Person.prototype.isPrototypeOf(person2)); //true
- ECMAScript 5增加了一个新方法: Object.getPrototypeOf() ;
- Object.getPrototypeOf() : 可以方便地取得一个对象的原型 ;
alert(Object.getPrototypeOf(person1) == Person.prototype); //true alert(Object.getPrototypeOf(person1).name); //"Nicholas"
- 第一行代码只是确定 Object.getPrototypeOf()返回的对象实际就是这个对象的原型;
- 第二行代码取得了原型对象中 name 属性的值,也就是"Nicholas;
- 代码读取某个对象的某个属性时:
- 搜索首先 从对象实例本身开始 ;
- 实例中找到了具有给定名字的属性,则返回该属性的;
- 没有找到, 则继续搜索指针指向的原型对;
- 实例中添加了一个属性,而该属性与实例原型中的一个属性同名;
- 实例中添加了一个属性;
- 该属性与实例原型中的一个属性同名;
- 该 属性将会屏蔽原型中的那个属性;
-
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg"——来自实例 alert(person2.name); //"Nicholas"——来自原型
- hasOwnProperty()方法 : 检测一个属性是存在于实例中,还是存在于原型中;
- 这个方法(不 要忘了它是从 Object 继承来的)只在给定属性存在于对象实例中时,才会返回 true ;
-
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "Greg"; alert(person1.name); //"Greg"——来自实例 alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas"——来自原型 alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //"Nicholas"——来自原型 alert(person1.hasOwnProperty("name")); //false
2 .原型与 in 操作符
- 两种方式使用 in 操作符:
- 单独使用: in 操作符会在通 过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中 ;
-
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true
- 同时使用 hasOwnProperty() 方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于 原型中;【 hasOwnProperty() 只在属性存在于 实例中时才返回 true , in 操作符只要通过对象能够访问到属性就返回 true 】
- Object.keys()方法 :
- 参数: 一个对象;
- 返回值: 一个包含所有可枚举属性的字符串数组 ;
-
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var keys = Object.keys(Person.prototype); // 原型对象调用; alert(keys); //"name,age,job,sayName" var p1 = new Person(); p1.name = "Rob"; p1.age = 31; var p1keys = Object.keys(p1); // 实例调用; alert(p1keys); //"name,age"
- 得到所有实例属性,无论是否可枚举;
- Object.getOwnPropertyNames() ;
-
var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor,name,age,job,sayName" // 包含了不可枚举的 constructor 属性 - Object.keys()和 Object.getOwnProperty- Names()方法都可以用来替代 for-in 循环;
- for-in ;
- for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,
- 既包括存在于实例中的属性,也包括存在于原型中的属性
- 更简单的原型语法 :
- 前面例子中每添加一个属性和方法就要敲一遍 Person.prototype;
- 为减少 不必要的输入;
- 更常见的做法是用一个包含所有属性和方法的 对象字面量来重写整个原型对象;
-
function Person() {} Person.prototype = { name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { alert(this.name); }, };
- 将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象 ;
- 例外:constructor 属性不再指向 Person 了.
-
alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true //constructor 属性则 等于 Object 而不等于 Person 了。
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true instanceof 操作符测试 Object 和 Person 仍然返回 true
- 如果 constructor 的值真的很重要,可以像下面这样特意将它设 置回适当的值;
-
function Person() {} Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { alert(this.name); }, };
- 特意包含了一个 constructor 属性;
- 并将它的值设置为 Person;
- 从而确保了通过该属 性能够访问到适当的值;
- 这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true;
-
//重设构造函数,只适用于 ECMAScript 5兼容的浏览器 Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person, });
- 原型的动态性 :
- 原型中查找值的过程是一次搜索;
- 我们对原型对象所做的任何修改都能够立即从实例上 反映出来;
- 即使是先创建了实例后修改原型也照样如此;
-
var friend = new Person(); Person.prototype.sayHi = function () { alert("hi"); }; friend.sayHi(); //"hi"(没有问题!)
即使 person 实例是在添加新方法之前创建的,但它仍然可 以访问这个新方法 ;
- 但如果是重 写整个原型对象,那么情况就不一样了;
- 调用构造函数时会为实例添加一个指向初原型的 [[Prototype]]指针 ;
- 把原型修改为另外一个对象就等于切断了构造函数与初原型之间的联;
- 实例中的指针仅指向原型,而不指向构造函数;
function Person() {} var friend = new Person(); Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { alert(this.name); }, }; friend.sayName(); //error 调用 friend.sayName()时发生了错误,因为 friend 指向的原型中不包含以该名字命名的属性。
- 调用 friend.sayName()时发生了错误,因为 friend 指向的原型中不包含以该名字命名的属性。
- 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;
- 它们引用的 仍然是最初的原型 ;
- 原型对象的问题 : 对于包含引用类型值的属性来说,问题就比较突出 ;
-
function Person() {} Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", friends: ["Shelby", "Court"], sayName: function () { alert(this.name); }, }; var person1 = new Person(); var person2 = new Person(); // 创建了 Person 的两个实例; person1.friends.push("Van"); // 修改了 person1.friends 引用的数组; alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" // 由于 friends 数组存在于 Person.prototype 而非 person1 中,所以刚刚提到的修改也会通过 person2.friends(与 person1.friends 指向同一个数组)反映出来; alert(person1.friends === person2.friends); //true
- 6.2.4 组合使用构造函数模式和原型模式 ;
- 构造函数模式用于定义实 例属性;
- 原型模式用于定义方法和共享的属性;
- 这种混成模式还支持向构造函数传递参 数;
-
function Person(name, age, job) { // 支持向构造函数传递参 数; this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; // 构造函数模式用于定义实 例属性; } Person.prototype = { constructor: Person, sayName: function () { alert(this.name); // 原型模式用于定义方法和共享的属性; }, }; var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); // "Shelby,Count,Van" alert(person2.friends); // "Shelby,Count" alert(person1.friends === person2.friends); // false alert(person1.sayName === person2.sayName); // true
- 6.2.5 动态原型模式 :
- 定义:检查某个应该存在的方法是否有效,来决定是否需要初始化原型 ;
- 使用动态原型模式时,不能使用对象字面量重写原型 ;
- ,如果 在已经创建了实例的情况下重写原型, 会切断现有实例与新原型之间的联系 ;
-
function Person(name, age, job) { //属性 this.name = name; this.age = age; this.job = job; //方法 if (typeof this.sayName != "function") { Person.prototype.sayName = function () { alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
- 6.2.6 寄生构造函数模式 ;
- 基本思想: 是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;
- 构造函数在不返回值的情况下,默认会返回新对象实例;
-
通过在构造函数的末尾添加 一个 return 语句,可以重写调用构造函数时返回的值;
-
function SpecialArray() { //创建数组 var values = new Array(); //添加值 values.push.apply(values, arguments); // values本身通过apply调用本身的push方法,将传入参数数组,按一个一个元素执行; //添加方法 values.toPipedString = function () { return this.join("|"); }; //返回数组 return values; } var colors = new SpecialArray("red", "blue", "green"); alert(colors.toPipedString()); //"red|blue|green"
- 关于寄生构造函数模式,有一点需要说明:
- 返回的对象与构造函数或者与构造函数的原型属 性之间没有关系 ;
- 也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同 ; 为此, 不能依赖 instanceof 操作符来确定对象类型
- 建议在可以使用其他模式的情 况下,不要使用这种模式。
- 6.2.7 稳妥构造函数模式 ;
- 稳妥对象 :
- 没有公共属性 ;
- 其方法也不引用 this 的对象;
- 稳妥构造函数遵循与寄生构造函数类似的模式;
- 两点不同:
- 新创建对象的 实例方法不引用 this ;
- 是不使用 new 操作符调用构造函数;
-
function Person(name, age, job) { //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function () { // 不引用 this alert(name); }; //返回对象 return o; }
- 这种模式创建的对象中,除了使用 sayName()方法之外,没有其他办法访问 name 的值 ;
- 像下面使用稳妥的 Person 构造函数
-
var friend = Person("Nicholas", 29, "Software Engineer"); // 不使用 new 操作符调用构造函数 friend.sayName(); //"Nicholas"
- 变量 friend 中保存的是一个稳妥对象;
- 除了调用 sayName()方法外,没有别的方式可 以访问其数据成员 ;
- 与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也 没有什么关系 ;
- 因此 instanceof 操作符对这种对象也没有意义;
Made by ---- Rise To The Heightest Then Qualitative Change


浙公网安备 33010602011771号