美文网首页
第六章 面向对象的程序设计(js高级程序设计)

第六章 面向对象的程序设计(js高级程序设计)

作者: 简默丶XS | 来源:发表于2019-02-20 22:04 被阅读0次

Object-Oriented 面向对象

理解对象

  • 对象属性分为 【数据属性】 和 【访问器属性】
  • 对象属性中的【数据属性】
    1. configurable 描述该属性能否通过delete删除或被重新定义(为false时defineProperty也无法使用了)
    2. enumerable 能否被for-in循环返回属性
    3. writable 能否修改属性的值
    4. value 这个属性的数据值
  • 利用defineProperty来定义数据属性


    因为writable属性被设置为false,我尝试修改xusheng的值,但是无效
  • 对象属性中的的 【访问器属性】
  1. configurable 描述该属性能否通过delete删除或被重新定义(为false时defineProperty也无法使用了)
  2. enumerable 能否被for-in循环返回属性
  3. get 在读取属性时调用的函数。
  4. set 在写入属性时调用的函数。
当获取_name值时,调用get方法得到name的值。当设置_name值时,其实时将name值改为了‘默认值’

_ name 前面 _ 的书写代表只能通过对象方法访问的属性。

  • 定义多个属性:defineProperties可以创建多个对象属性


    例子创建了_year和edition两个数据属性和year访问器属性
  • 读取属性的特性
    通过getOwnPropertyDescriptor获取到的上面例子的属性描述符
    var descriptor = Object.getOwnPropertyDescriptor(book, "_year");

上述book对象的_ year属性的属性描述符
  • 创建对象方法:
    1. new Object()
    2. 对象字面量{ }
      缺点:使用同一个接口创建很多对象,会产生大量的重复代码。

其他创建对象的方法

1. 工厂模式
这种模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。

工厂模式用函数封装了创建对象的过程
缺点:工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

2. 构造函数模式
构造函数可以用来创建特定类型的对象

书中的案例
  • 构造函数开头字母需要大写(Person)

  • 在new 构造函数时,程序会执行以下步骤

    1. 创建一个新对象
    2. 将构造函数的作用域赋值给新对象
    3. 执行构造函数中的代码
    4. 返回新对象
  • 对象的 constructor 是用来标识对象类型的

  • 通过构造函数创建对象有一个constructor属性,指向原构造函数

  • 创建得对象是构造函数的实例


    实例和构造函数关系
  • 以上构造函数带来的问题,相同作用的函数被反复复制,改进:

sayName其实做了同一件事,但是却被创建成了两个不同的function,浪费 将sayName拎出来
  • 新的问题:全局下又不可能有多个类似于sayName函数的东西,会很乱

3. 原型模式

关系图 实例的sayName均来自构造函数的原型 构造函数同样实现效果

回到问题本质,与构造函数的区别在哪里?
新对象的这些属性和方法是由所有实例共享(不会再创建相同作用的函数)的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName()函数

  • 理解原型对象
我的关系图:person1实例与构造函数没有直接关系,person1实例可以调用sayName是因为他在原型对象上进行查找.!注意这里有个错误:应该是__proto__。两个下划线! 书中关系图
  • __proto__ 是两个下划线!!

  • 确定实例和原型对象关系可以用:

  1. isPrototypeOf()判断对象是否和原型对象有关
  2. 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(“属性名”)可以判断这个属性是来自于原型对象还是自己本身

person1实例上name属性来自本身还是来自于原型对象上
  • 与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.原型链

继承图解A继承B
  • 注意此时的 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
  1. 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:终于看完了,真特么累,感觉迷迷糊糊的,肯定要刷第二遍的~。

相关文章

  • prototype (原型) 属性

    这篇文章是基于 <>第六章面向对象的程序设计 6.2.3原型模式 我们每创建...

  • JS的面向对象编程设计

    最近阅读了《JS高级程序设计3》,刚看完第六章面向对象,于是将大体的核心概念整理总结一下。 1.理解对象 属性类型...

  • 扒一扒所谓的面向对象

    面向对象是很多高级程序程序设计语言的核心。面向对象的标志就是类、对象、继承、派生等。js严格意义上来讲并不是面向对...

  • 深入了解javascript(三)——面向对象与原型

    面向对象是js最难弄懂的一部分,我在看了《Javascript高级程序设计》之后还看了李炎恢老师关于面向对象与原型...

  • javascript对象继承详解 及react的对象继承原理

    总结《javascript高级程序设计》第六章有关于对象继承的内容。看这篇文章之前看js对象创建详解。对于OO语言...

  • 第四章 对象与类

    1 面向对象程序设计概述 面向对象程序设计(简称OOP)是当今主流的程序设计范型 Java是完全面向对象的,必须熟...

  • 三、对象与类

    面向对象程序设计概述 面向对象的程序设计(简称OOP)时当今主流的程序设计范型,已经取代了“结构化”过程化程序设计...

  • python面向对象编程

    面向对象编程(一) 1、面向过程程序设计与面向对象程序设计: 面向过程的程序设计把计算机程序视为一系列的命令集合,...

  • 工厂模式、原型模式、组合模式温习

    重新温习了下JavaScript高级程序设计第六章,有些知识不常用已经忘记,这次重新拾起。 所谓的面向对象,其中最...

  • 《Java核心技术卷 I》之 Java对象与类

    Java对象与类 面向对象程序设计 面向对象程序设计,简称OOP,Java语言就是完全面向对象的。 类 类(cla...

网友评论

      本文标题:第六章 面向对象的程序设计(js高级程序设计)

      本文链接:https://www.haomeiwen.com/subject/yishyqtx.html