Object-Oriented 面向对象
理解对象
- 对象属性分为 【数据属性】 和 【访问器属性】
- 对象属性中的【数据属性】
- configurable 描述该属性能否通过delete删除或被重新定义(为false时defineProperty也无法使用了)
- enumerable 能否被for-in循环返回属性
- writable 能否修改属性的值
- value 这个属性的数据值
-
利用defineProperty来定义数据属性
因为writable属性被设置为false,我尝试修改xusheng的值,但是无效 - 对象属性中的的 【访问器属性】
- configurable 描述该属性能否通过delete删除或被重新定义(为false时defineProperty也无法使用了)
- enumerable 能否被for-in循环返回属性
- get 在读取属性时调用的函数。
- set 在写入属性时调用的函数。
_ name 前面 _ 的书写代表只能通过对象方法访问的属性。
-
定义多个属性:defineProperties可以创建多个对象属性
例子创建了_year和edition两个数据属性和year访问器属性 -
读取属性的特性
通过getOwnPropertyDescriptor获取到的上面例子的属性描述符
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
- 创建对象方法:
- new Object()
- 对象字面量{ }
缺点:使用同一个接口创建很多对象,会产生大量的重复代码。
其他创建对象的方法
1. 工厂模式
这种模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。
缺点:工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
2. 构造函数模式
构造函数可以用来创建特定类型的对象
-
构造函数开头字母需要大写(Person)
-
在new 构造函数时,程序会执行以下步骤
- 创建一个新对象
- 将构造函数的作用域赋值给新对象
- 执行构造函数中的代码
- 返回新对象
-
对象的 constructor 是用来标识对象类型的
-
通过构造函数创建对象有一个constructor属性,指向原构造函数
-
创建得对象是构造函数的实例
实例和构造函数关系 -
以上构造函数带来的问题,相同作用的函数被反复复制,改进:
- 新的问题:全局下又不可能有多个类似于sayName函数的东西,会很乱
3. 原型模式
关系图 实例的sayName均来自构造函数的原型 构造函数同样实现效果回到问题本质,与构造函数的区别在哪里?
新对象的这些属性和方法是由所有实例共享(不会再创建相同作用的函数)的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName()函数
- 理解原型对象
-
__proto__ 是两个下划线!!
-
确定实例和原型对象关系可以用:
- isPrototypeOf()判断对象是否和原型对象有关
- Object.getPrototypeOf()获得对象的原型对象
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf(person1) == Object.getPrototypeOf(person1) //获得person1对象指向的原型对象
-
当调用person1.sayname时。解析器会现在实例对象上查找sayname属性,如果找不到,再在原型对象上查找sayname属性。所以实例上的属性会屏蔽掉原型上同名属性
-
通过delate操作符可以删除实例上的属性,从而恢复原型的访问
-
利用hasOwnProperty(“属性名”)可以判断这个属性是来自于原型对象还是自己本身
- 与hasOwnProperty不同的是,使用in操作符只要能访问到该属性都会返回true(无论是原型对象上还是实例对象上)
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true name属性在实例对象属性中
- hasPrototypeProperty(person1,"name") 实例对象要能访问到原型上的属性才能返回true,即使原型对象上有属性name,但是实例对象上的name将原型对象上的name屏蔽,所以依然返回false
var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true
person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false 实例对象要能访问到原型上的属性才能返回true
-
for in 枚举属性
原则:所有开发人员定义的属性都是可以枚举的属性,所以对象实例属性和原型对象属性都是可以被枚举的。
图中红色框中属性都可被枚举,黄色框中不可被枚举,因为他们来自原型链中的Object原型属性 -
取得对象上所有可枚举的实例属性:Object.keys()
-
使用Object.key()取得对象上可枚举的属性组成的字符串数组
var person = new Person()
Object.key(person) //"say" person实例对象上的属性,原型对象上的不会被
console.log(Object.keys(person.__proto__))// "age,job,name,sayName" 原型上的属性
- Object.getOwnPropertyNames(object) 可以列出对象上所有的属性,即使是不可枚举的属性,你可以试着枚举出继承的Object对象上的属性
console.log(Object.getOwnPropertyNames(person.__proto__.__proto__))
- 换一种方式定义构造函数原型上的属性
function Person(){
}
Person.prototype = {
constructor : Person, //如果没有这句,constructor 属性就指不到构造函数了
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
此方法带来的问题:constructor 的[[Enumerable]]值为true可枚举了
使用Object.defineProperty重设构造函数
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
}); //此写法适用于es5
- 原型的动态性:很好理解,实例对象(person1)的prototype属性指针指向原型对象,所以在原型对象上多次修改属性值,在实例上也同样能被正确的访问
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"(没有问题!)
当然,你不能重写原型对象,切断与构造函数的联系
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
在new 构造函数时,构造函数会为实例创造指针__proto__指向原型对象。重写原型后,旧的实例依然和旧的原型保持着联系,和新的原型没有联系
- 原生对象的原型:原生(Array,String)构造函数都在其原型(prototype)上定义了方法,所以我们可以通过例如String.substring()来操作字符串实例
String.prototype.startsWith() =function(){
...//你可以给原生对象添加属性,让所有实例化的字符串都能访问,但是不建议这样做
}
- 原型对象的问题:修改原型对象上的属性值会对其他实例对象造成影响(我们有时候需要它[所有string的实例都能使用String.substring()],有时候又不需要它)
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);
}
}
5. 动态原型模式:在构造函数中使用原型模式,并且避免重复挂载到原型模式
它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点
换句话说,第一次new Person()的时候,sayName肯定是不存在的,所以会挂载sayName和其他方法到原型上,而下一次newPerson的时候,sayName()已经在原型上了[注意if判断的作用],sayName包括其他if语句中的方法都不会被重复挂载!
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);
};
Person.prototype.sayAge = function () {
alert(this.age);
};
}
}
6. 寄生构造函数模式
类似于工厂模式,不过使用new function()的形式创建
function Person(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 friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊
数组。由于不能直接修改 Array 构造函数,因此可以使用这个模式
注意:返回的对象与构造函数或者与构造函数的原型属性之间没有关系。 instanceof 不能溯源,所以建议在可以使用其他模式的情况下,不要使用这种模式。
7. 稳妥构造函数模式(闭包)
function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
除了使用 sayName()方法之外,没有其他办法访问 name 的值(有点用到了闭包,让匿名函数返回私有变量)
同样和寄生构造模式一样不能溯源,不能使用instanceof
继承
- 函数签名(或者类型签名,抑或方法签名)定义了 函数或方法的输入与输出。JavaScript 是一种类型松散或动态语言。这意味着您不必提前声明变量的类型。以下是java的函数签名
public static void main(String[] args)
-
继承分为接口继承和实现继承
接口继承:接口是一种特殊的抽象类,即继承函数的签名,故js中没有接口继承
实现继承:实现继承是继承函数实际的方法 -
子类型超类型
子类型:继承者
超类型:被继承者(下文用父类型)
1.原型链
-
注意此时的 instance 应该是指向B的原型,因为A的prototype被b的实例重写了。
-
在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上
-
所有函数的默认原型都是 Object 的实例:原型链的底层永远都是Object。因此默认原型都会包含一个内部指针,指向 Object.prototype(也就是继承于Object原生)。这也是为什么所有自定义类型都会继承toString(),valueOf()方法的原因。上图的B其实还继承了原生Object,如下图。
默认原型都会指向Object.prototype, -
确定原型和实例的关系的方式:
1.instanceof
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
- isPrototypeOf
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
- 在完成继承后再添加额外的方法,否则额外的方法会被继承覆盖掉。先.prototype = new Function(),再.prototype.xx=xx.更不能使用.prototype={ }字面量重写原型
- 原型继承带来的引用类型的问题:原型中包含引用类型,A构造函数的原型继承于B的实例,基于A创建M实例,修改引用类型值,那么B的原型中的值也相应会改变,如果再基于A创建N实例,那么M,N就会拥有相同的引用。
不能向超类型的构造函数传递参数
2.借用构造函数
- 利用apply或者call在自己的构造函数中调用别人的构造函数
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了 SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
- 缺点很明显:方法都在构造函数中定义,因此函数复用就无从谈起。而且父类型原型中的方法子类型也用不了
3.组合继承
- 组合继承是ji中常用的实现继承的方式,将原型继承和构造函数继承集合
function f1(name) {
this.name = name
this.colors = ['blue', 'red', 'green'] //将需要被继承的引用类型定义在构造函数中
}
f1.prototype.sayName = function () {
alert(this.name)
}
function f2(name, age) {
f1.call(this, name) //此时,f2上已经继承f1构造函数中的属性
this.age = age
}
f2.prototype = new f1(); //此时,f2已经继承f1原型对象上的属性
var F2 = new f2()
for (const x in F2) {
console.log(x)//name color age sayname
}
4.原型式继承
- 将一个对象作为子对象的原型对象实现继承
function object(o) {
function F() { }
F.prototype = o; //person对象被共享到了通过object创造出来的对象的原型上
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
debugger
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
- Es5规范化方法:Object.creat()
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式
继承是完全可以胜任的.
- 个人理解:其实就是原型链继承的缩写方案,同样存在原型链继承引用类型被共享的问题
5.寄生式继承
function createAnother(original) {
var clone = Object.create(original); //通过调用函数创建一个新对象
clone.sayHi = function () { //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
注意点: clone.sayHi 可以给新对象上添加属性,和寄生构造函数模式有点像,createAnother存在的意义就是在新的实例上添加公有属性
6.寄生组合式继承
- 还记得组合继承模式吗?通过f2.call(this)和.protype=new f2()分别调用了两次父类型,寄生组合式继承就是为了解决这个问题。
- inheritPrototype接受两个参数,分别是子类型的构造函数和夫类型的构造函数
function inheritPrototype(subType, superType){
var prototype = Object.creat(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType); //SubType.protoype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
};
- 只调用了一次 SuperType 构造函数,并且因此避免了在 SubType. prototype 上面创建不必要的、多余的属性
- 个人觉得是拷贝父类型的prototype到子类型的protype上,inheritPrototype的作用其实就是SubType.protoype = new SuperType();
ps:终于看完了,真特么累,感觉迷迷糊糊的,肯定要刷第二遍的~。
网友评论