JS式面向对象

一、理解对象

一)属性类型

ECMA-262 第 5 版在定义只有内部才用的特性(attribute)时,描述了属性(property)的各种特征。

ECMA-262 定义这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。 
1.数据属性

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的
特性。

 [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特
性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的
这个特性默认值为 true。
 [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定
义的属性,它们的这个特性默认值为 true。
 [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的
这个特性默认值为 true。
 [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,
把新值保存在这个位置。这个特性的默认值为 undefined。

直接在对象上定义的属性,它们的[[Configurable]]、 [[Enumerable]]
和[[Writable]]特性都被设置为 true,而[[Value]]特性被设置为指定的值。

        var p = {
name: "Tom", //该属性的[[Value]]特性被设置为"Tom"
age: 1
}

要修改属性的默认特性,必须使用ECMAScript5的Object.defineProperty()方法

该方法的三个参数:属性所在对象、属性的名字、描述符对象,其中,描述符(descriptor)对象的属
性必须是: configurable、 enumerable、 writable 和 value。

        var person = {
name: "Tang",
gender: "man"
} Object.defineProperty(person,"name",{
writable: false,
value: "Salt Fish"
}); console.log(person.name); //Salt Fish
person.name = "King";
console.log(person.name); //Salt Fish
        var person = {name:null};

        Object.defineProperty(person,"name",{
configurable: false,
value: "King"
}); //以下语句抛出错误:can't redefine non-configurable property "name"
// 一旦把属性定义为不可配置的,
//就不能再把它变回可配置了。此时,再调用 Object.defineProperty()
//方法修改除 writable,value 之外的特性,都会导致错误
/*Object.defineProperty(person,"name",{
configurable: true,
value: "Fish"
});*/ //没问题
Object.defineProperty(person,"name",{
value: "Salt Fish"
}); console.log(person.name); //Salt Fish

此外,在调用 Object.defineProperty()方法时,如果不指定, configurable、 enumerable 和
writable 特性的默认值都是 false。

2.访问器属性

访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的)。
在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用
setter 函数并传入新值,这个函数负责决定如何处理数据。

var book = {
_year: 1990, //year前面的"_"记号表示只能通过对象方法访问的属性
edition: 1
}; Object.defineProperty(book,"year",{
get: function () {
return this._year; //有"_"
}, set: function (v) {
if (v > 2008) {
this._year = v;
this.edition += v - 2008;
}
}
}); book.year = 2012; //没有"_"
console.log("Year: "+book.year+"\n"+"Edition: "+book.edition);
/*Year: 2012
Edition: 5*/

注意:如果只指定getter意味着属性是不可写的,只指定setter表示属性不可读。

Object.definedProperties()方法:通过该方法可以一次定义多个属性。

    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;
}
}
}
});

Object.getOwnPropertyDescriptor()方法 :可以取得给定属性的描述
符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果
是访问器属性,这个对象的属性有 configurable、 enumerable、 get 和 set;如果是数据属性,这
个对象的属性有 configurable、 enumerable、 writable 和 value。

        var book = {
_year: 1990, //year前面的"_"记号表示只能通过对象方法访问的属性
edition: 1
}; Object.defineProperty(book,"year",{
get: function () {
return this._year; //有"_"
}, set: function (v) {
if (v > 2008) {
this._year = v;
this.edition += v - 2008;
}
}
}); book.year = 2012; //没有"_" var desc = Object.getOwnPropertyDescriptor(book,"year");
console.log(desc.value); //undefined
console.log(desc.configurable); //false var desc2 = Object.getOwnPropertyDescriptor(book,"_year");
console.log(desc2.value); //
console.log(desc2.configurable); //true

二、创建对象

关于new

    function Person() {}

    Person.prototype.name = "Tom";
var p1 = Person();
var p2 = new Person(); /*console.log(p1.name);*/ //undefined
console.log(p2.name); //Tom
    /*
* 使用new操作符将函数作为构造器进行调用的时候,其上下文被定义为新对象实例。
* */
function Man() {
this.age = 22;
this.position = "teacher";
this.say = function () {
console.log("Big sb!");
}
}
Man.prototype.age = 0;
var m1 = new Man();
//注意优先级
console.log(m1.age); //
var m = new Man();
//每个对象实例都有一个constructor属性,该属性引用的是创建该对象的构造器
//而prototype则是构造器的一个属性。所以每个实例都可以找到自己的原型。
console.log(m.constructor.prototype.age); //
console.log(Man.prototype.age);//
console.log(Man.constructor.prototype.age); //undefined
console.log(m.constructor.prototype.constructor.prototype.age); //
console.log(m.constructor === Man); //true
var m2 = new m.constructor();
console.log(m === m2); //false

一)工厂模式

        function createPerson(name, gender, job) {
var instance = new Object();
instance.name = name;
instance.gender = gender;
instance.job = job; instance.introduce = function () {
console.log(this.name);
}
return instance;
} var p = createPerson("ashin","man","killer");
p.introduce();

工厂模式虽然解决了创建多个相似对象的问题,但并没有解决对象识别问题。

二)构造函数模式

创建自定义构造函数,从而定义自定义对象类型的属性和方法。

        //自定义一个函数
function Person(name, gender, job) {
this.name = name;
this.gender = gender;
this.job = job; this.introduce = function () {
console.log("My name is "+this.name);
}
} //用new操作符执行该函数(作为构造函数执行)。
var p = new Person("Jerry","boy","actor");
p.introduce(); //My name is Jerry
console.log(p.constructor == Person); //true
console.log(p instanceof Person); //true
console.log(p instanceof Object); //true //当做普通函数使用
var p2 = Person("Jax","man","Gui");
window.introduce(); //My name is Jax
// console.log(p2.constructor == Person); //报错
console.log(p2 instanceof Person); //false 这里p2是执行结果,当然是false
console.log(p2 instanceof Object); //false 同上

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

var name = "Window";
function Person(name) {
this.name = name;
} var tom = new Person("Tom")
console.log(this.name); //Window
console.log(tom.name); //Tom Person("Jack");
console.log(this.name); //Jack

使用构造函数模式解决了对象识别问题,此外这种方法定义的构造函数是定义在Global对象中的。

使用构造函数主要问题:对象内部的每个方法都要在每个实例上重新创造一遍。创建两个完成同样任务的 Function 实例的确没有必要

解决方法一,把函数定义转移到构造函数外部。

       function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job; this.sayName = sayName;
} function sayName() {
console.log("Name:" +this.name);
}

新问题:破坏自定义引用类型的封装性。

三)原型模式

理论依据:

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype
属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor
(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。创建了自定义的构造函数之后,、

其原型对象默认只会取得 constructor 属性;至于其他方法,则

都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部
属性),指向构造函数的原型对象

        function Person(name, age, job) {
} Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
console.log(this.name);
} var person1 = new Person();
var person2 = new Person(); person1.sayName(); //Nicholas
person2.sayName(); //Nicholas
console.log(person1.sayName === person2.sayName); //true

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先

从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,
则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这
个属性,则返回该属性的值。也就是说,在我们调用 person1.sayName()的时候,会先后执行两次搜
索。首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。”然后,它继续搜索,再
问:“person1 的原型有 sayName 属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函
数。当我们调用 person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。而这正是多个
对象实例共享原型所保存的属性和方法的基本原理。

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们
在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该
属性将会屏蔽原型中的那个属性

但通过delete操作符可以删除属性,取消屏蔽

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"—— 来自原型
delete person1.name;
alert(person1.name); //"Nicholas"—— 来自原型

in 操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中 
同时使用 hasOwnProperty()方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于
原型中

要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys()方法。这个方法
接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组

更简单的原型语法:

function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};

我们将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。
最终结果相同,但有一个例外: constructor 属性不再指向 Person 了。前面曾经介绍过,每创建一
个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在
这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新
对象的 constructor 属性(指向 Object 构造函数),不再指向 Person 函数。此时,尽管 instanceof
操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了

如果 constructor 的值真的很重要,可以像下面这样特意将它设
置回适当的值(当然并没有多少卵用)。

    function Person(){
}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};

注意,以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。

原生对象的原型:

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式
创建的。所有原生引用类型(Object、 Array、 String,等等)都在其构造函数的原型上定义了方法。

原型对象的问题:

   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();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

四)组合使用构造函数模式和原型模式

创建自定义类型最常用的方式就是,组合使用构造函数模式与原型模式。

    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

五)动态原型模式

        function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
} //这就是所谓的动态创建......
if (typeof this.sayName != "function") {
Person.prototype.sayName = function () {
console.log(this.name);
}
}

六)稳妥构造函数模式

        //没用公共属性,方法也不引用this
function Person(name, age, job) {
var obj = new Object();
obj.sayName = function () {
console.log(name);
}
return obj;
} var p = Person("Tom");
p.sayName(); //Tom

三、继承

ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。 
原型链:

        function SuperType() {
this.property = true;
} SuperType.prototype.getSuperValue = function () {
return this.property;
} function SubType() {
this.subproperty = false;
} //继承
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
} var instance = new SubType();
console.log(instance.getSuperValue()); //true
console.log(instance.getSubValue()); //false

组合继承:

组合继承是最常用的继承方法

        function SuperType(name) {
this.name = name;
this.colors = ["blue","red","yellow"];
} SuperType.prototype.sayName = function () {
console.log("My name is "+this.name);
} function SubType(name, age) {
SuperType.call(this,name);
this.age = age;
} //继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log("I'm "+this.age+" years old.");
} var instance = new SubType("Kotlin",3);
instance.colors.push("black");
console.log(instance.colors); //["blue","red","yellow","black"]
instance.sayName(); //My name is Kotlin
instance.sayAge(); //I'm 3 years old. var p = new SubType("Java",20);
console.log(p.colors); //["blue","red","yellow"]
p.sayName(); //My name is Java
p.sayAge(); //I'm 20 years old.

JavaScript基础笔记(四) JS式面向对象的更多相关文章

  1. JavaScript基础笔记集合(转)

    JavaScript基础笔记集合   JavaScript基础笔记集合   js简介 js是脚本语言.浏览器是逐行的读取代码,而传统编程会在执行前进行编译   js存放的位置 html脚本必须放在&l ...

  2. JavaScript基础笔记一

    一.真假判断 真的:true.非零数字.非空字符串.非空对象 假的:false.数字零.空字符串.空对象.undefined 例: if(0){ alert(1) }else{ alert(2) } ...

  3. JavaScript基础笔记二

    一.函数返回值1.什么是函数返回值    函数的执行结果2. 可以没有return // 没有return或者return后面为空则会返回undefined3.一个函数应该只返回一种类型的值 二.可变 ...

  4. javascript学习笔记(四) Number 数字类型

    数字格式化方法toFixed().toExponential().toPrecision(),三个方法都四舍五入 toFixed() 方法指定小数位个数  toExponential() 方法 用科学 ...

  5. javascript基础入门之js中的结构分支与循环语句

    javascript基础入门之js中的结构分支与循环语句 程序的结构①顺序结构:自上而下:②选择(分支)结构:多条路径,根据不同的条件,只执行其中一个:③循环结构:重复某些代码④配合特定的语句实现选择 ...

  6. javascript基础入门之js中的数据类型与数据转换01

    javascript基础入门之js中的数据结构与数据转换01 js的组成(ECMAScript.BOM.DOM)        js中的打印语句:        数据类型        变量      ...

  7. JavaScript(第十四天)【面向对象和原型】

    学习要点: 1.学习条件 2.创建对象 3.原型 4.继承 ECMAScript有两种开发模式:1.函数式(过程化),2.面向对象(OOP).面向对象的语言有一个标志,那就是类的概念,而通过类可以创建 ...

  8. Javascript学习笔记四——操作表单

    Javascript学习笔记 大多网页比如腾讯,百度云之类的需要登陆,用户输入账号密码就可以登陆,那么浏览器是如何获取用户的输入的呢?今天就记录一下操作表单. 操作表单与操作DOM是差不多的,表单本身 ...

  9. JavaScript基础笔记(十四)最佳实践

    最佳实践 一)松散耦合 1.解耦HTML/JavaScript: 1)避免html种使用js 2)避免js种创建html 2.解耦CSS/JS 操作类 3.解耦应用逻辑和事件处理 以下是要牢记的应用和 ...

随机推荐

  1. Nginx详解二十:Nginx深度学习篇之HTTPS的原理和作用、配置及优化

    一.HTTPS原理和作用: 1.为什么需要HTTPS?原因:HTTP不安全1.传输数据被中间人盗用.信息泄露2.数据内容劫持.篡改 2.HTTPS协议的实现对传输内容进行加密以及身份验证 对称加密:加 ...

  2. yslow V2 准则详细讲解

    主要有12条:   1. Make fewer HTTP requests 尽可能少的http请求..我们有141个请求(其中15个JS请求,3个CSS请求,47个CSS background ima ...

  3. fio 测试磁盘性能

    在磁盘测试中最关心的几个指标分别为: iops(每秒执行的IO次数).bw(带宽,每秒的吞吐量).lat(每次IO操作的延迟). 当每次IO操作的block较小时,如512bytes/4k/8k等,测 ...

  4. gitlab报错502及处理

    报错截图: 解决: 1.端口问题 如上面写的815端口,那配置文件的8080端口都改成815端口 之后重新载入配置文件,并开启 gitlab-ctl reconfigure gitlab-ctl st ...

  5. js里添加的标签

    js里添加的标签.网页加载此标签绑定的js函数时,由于没有标签,故无法执行函数. 例如: js中添加了一个button: html1 += "<td><button typ ...

  6. mysql 5.7 安装

    linux(CentOS6.7) 环境Mysql 5.7.17安装教程分享给大家,供大家参考,具体内容如下: 1系统约定 安装文件下载目录:/data/software Mysql目录安装位置:/us ...

  7. C#线性表

    线性表是线性结构的抽象 线性结构的特点是结构中的数据元素之间存在一对一的线性关系 一对一的关系指的是数据元素之间的位置关系 (1)除第一个位置的数据元素外,其它数据元素位置的前面都只有一个数据元素 ( ...

  8. Give root password for maintenance(or type control -D to continue)

    2017-09-30 18:12:08 1:错误如图,本来开机准备用一下虚拟机,就出现一个这,为啥记录一下呢,因为网上好多不是很靠谱. 原因可能是之前关闭虚拟机的时候不小心出现异常了: 2:解决办法: ...

  9. 【译】异步JavaScript的演变史:从回调到Promises再到Async/Await

    我最喜欢的网站之一是BerkshireHathaway.com--它简单,有效,并且自1997年推出以来一直正常运行.更值得注意的是,在过去的20年中,这个网站很有可能从未出现过错误.为什么?因为它都 ...

  10. Codeforces 387E George and Cards

    George and Cards 我们找到每个要被删的数字左边和右边第一个比它小的没被删的数字的位置.然后从小到大枚举要被删的数, 求答案. #include<bits/stdc++.h> ...