JavaScript基础笔记(四) JS式面向对象
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式面向对象的更多相关文章
- JavaScript基础笔记集合(转)
JavaScript基础笔记集合 JavaScript基础笔记集合 js简介 js是脚本语言.浏览器是逐行的读取代码,而传统编程会在执行前进行编译 js存放的位置 html脚本必须放在&l ...
- JavaScript基础笔记一
一.真假判断 真的:true.非零数字.非空字符串.非空对象 假的:false.数字零.空字符串.空对象.undefined 例: if(0){ alert(1) }else{ alert(2) } ...
- JavaScript基础笔记二
一.函数返回值1.什么是函数返回值 函数的执行结果2. 可以没有return // 没有return或者return后面为空则会返回undefined3.一个函数应该只返回一种类型的值 二.可变 ...
- javascript学习笔记(四) Number 数字类型
数字格式化方法toFixed().toExponential().toPrecision(),三个方法都四舍五入 toFixed() 方法指定小数位个数 toExponential() 方法 用科学 ...
- javascript基础入门之js中的结构分支与循环语句
javascript基础入门之js中的结构分支与循环语句 程序的结构①顺序结构:自上而下:②选择(分支)结构:多条路径,根据不同的条件,只执行其中一个:③循环结构:重复某些代码④配合特定的语句实现选择 ...
- javascript基础入门之js中的数据类型与数据转换01
javascript基础入门之js中的数据结构与数据转换01 js的组成(ECMAScript.BOM.DOM) js中的打印语句: 数据类型 变量 ...
- JavaScript(第十四天)【面向对象和原型】
学习要点: 1.学习条件 2.创建对象 3.原型 4.继承 ECMAScript有两种开发模式:1.函数式(过程化),2.面向对象(OOP).面向对象的语言有一个标志,那就是类的概念,而通过类可以创建 ...
- Javascript学习笔记四——操作表单
Javascript学习笔记 大多网页比如腾讯,百度云之类的需要登陆,用户输入账号密码就可以登陆,那么浏览器是如何获取用户的输入的呢?今天就记录一下操作表单. 操作表单与操作DOM是差不多的,表单本身 ...
- JavaScript基础笔记(十四)最佳实践
最佳实践 一)松散耦合 1.解耦HTML/JavaScript: 1)避免html种使用js 2)避免js种创建html 2.解耦CSS/JS 操作类 3.解耦应用逻辑和事件处理 以下是要牢记的应用和 ...
随机推荐
- N阶楼梯上楼问题
N阶楼梯上楼问题 时间限制: 1 Sec 内存限制: 32 MB 题目描述 样例输出 13 #include <stdio.h> int main() { int i, n; long ...
- 卸载列表信息——Uninstall注册表
今天用InstallShield打包了一个安装程序,安装顺利完成了,但是当我去控制面板准备卸载时,发现我的程序没有详细的信息,正常的软件信息如下图: 而我的程序没有发布者,大小和版本,也没有图标,于是 ...
- WPF多屏最大化
如果计算机存在多个显示器,这时设置wpf窗口为最大化,窗口只能在主显示器中实现最大化,如果想要实现窗口拉伸至多屏,需要获取所有显示器分辨率之和.这时用到了System.Windows.SystemPa ...
- 计算1至n中数字X出现的次数【math】
转自: nailperry 一.1的数目 编程之美上给出的规律: 1. 如果第i位(自右至左,从1开始标号)上的数字为0,则第i位可能出现1的次数由更高位决定(若没有高位,视高位为0),等于更高位数字 ...
- (2).NET CORE微服务 Micro-Service ---- .NetCore启动配置 和 .NetCoreWebApi
什么是.Net Core?.Net Core是微软开发的另外一个可以跨Linux.Windows.mac等平台的.Net.Net Core相关知识看文章地步dotnet dllname.dll 运行P ...
- centos的基本操作
1.ssh连接阿里云一段时间不操作自动断开打开/etc/ssh/sshd_config添加或修改: ClientAliveInterval 120ClientAliveCountMax 0 2.挂载数 ...
- Python学习(五) —— 文件操作
一.文件操作 1.文件操作:数据持久化的一种 步骤:找到文件,打开文件,操作:读.写.追写,关闭文件 打开文件:f = open(文件路径,操作模式,编码方式),f:文件句柄.文件 ...
- Python 携程
一.协程 1.又称微线程,纤程.英文名Coroutine.一句话说明什么是协程:协程是一种用户态的轻量级线程(相当于操作系统不知道它的存在,是用户控制的). 2.协程拥有自己的寄存器上下文和栈(代码的 ...
- 51Nod1601 完全图的最小生成树计数 Trie Prufer编码
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1601.html 题目传送门 - 51Nod1601 题意 题解 首先我们考虑如何求答案. 我们将所有 ...
- scrapy下载图片报[scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt:错误
本文转自:http://blog.csdn.net/zzk1995/article/details/51628205 先说结论,关闭scrapy自带的ROBOTSTXT_OBEY功能,在setting ...