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. jQuery 常用的方法

    <!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...

  2. AI-响应式、分页器

    响应式 如果在setting配置下列,当只有BrowsableAPIRenderer时,只会返回浏览器页面:当配置成JSONRenderer,会返回json数据 REST_FRAMEWORK={ 'D ...

  3. Python杂写1

    一:编程及编程语言介绍 编程的目的:人把自己的思想流程表达出来,让计算机按照这种思想去做事,把人给解放出来. 编程语言:简单的说就是一种语言,是人和计算机沟通的语言. 编程:例如Python,利用Py ...

  4. gitblit删除版本库

    Git客户端不提供删除远程仓库的方法,gitblit服务器网页也不支持删除版本仓库.若要强制删除,Windows下可以: 先在任务管理器中停止gitblit进程,然后将gitblit版本库文件夹中将版 ...

  5. 安装 jpegtran-cffi 使用 from jpegtran import JPEGImage

    Requirements CPython >=2.6 or >=3.3 or PyPy cffi >= 1.0 libturbojpeg with headers Install 1 ...

  6. B - Alyona and towers CodeForces - 739C

    链接: https://vjudge.net/contest/202699#problem/B 题意: 给出一个序列,要支持区间加和操作 求其中最长的区间,该区间内的元素满足(ai<ai+1&l ...

  7. 基于AspNet Core2.0 开发框架,包含简单的个人博客Demo

    大家好,最近离职了,利用闲暇时间就捣鼓了一个基于AspNet Core开发框架,分享出来希望能给AspNet Core学者带来一些帮助,同时也能跟大家一起学习.当然了,由于我的个人技术及经验的有限,框 ...

  8. linux实现自动检测进程是否存活的脚本

    可以在性能测试过程中.定期检测startAgent和nmon的状态 #!/bin/sh while true do pnmon=`ps aux | grep nmon | grep -v grep`; ...

  9. mysql主从复制(半同步方式)

    mysql主从复制(半同步方式) 博客分类: MySQL mysqlreplication复制  一.半同步复制原理介绍 1. 优点 当事务返回客户端成功后,则日志一定在至少两台主机上存在. MySQ ...

  10. 黑色半透明镂空遮罩指引效果实现jQuery小插件

    /*! * by zhangxinxu(.com) 2017-05-18 * 新版上线时候的黑色半透明镂空遮罩指引效果实现jQuery小插件 * 兼容到IE8+ * MIT使用协议,使用时候保留版权 ...