hercule_poirt
^

javaScript - 面向对象编程

面向对象编程

OOP,即面向对象编程,是软件开发过程的主要方法之一。在 OOP 中,我们用对象和类来组织代码,以描述事物的特点以及它们可以做什么。

1. 在对象上创建方法(method)

方法/函数其实也是属性

对象可以有一个叫做 method 的特殊属性。

方法属性也就是函数。 

let duck = {

  name: "Aflac",

  numLegs: 2,

  sayName: function() {return "The name of this duck is " + duck.name + ".";}

};

duck.sayName();

2. 使用 this 关键字提高代码重用性

为什么使用this?

虽然通过duck.name是访问对象属性的有效方法,但是这里有一个陷阱。 如果变量名(如duck)发生了改变,那么引用了原始名称的任何代码都需要更新。

在一个简短的对象定义中,这并不是问题,但是如果对象有很多对其属性的引用,那么发生错误的可能性就更大了。

解决方法:我们可以使用 this 关键字来避免这一问题

let duck = {
  name: "Aflac",
  numLegs: 2,
  sayName: function() {return "The name of this duck is " + this.name + ".";}
};

在当前的上下文环境中,this 指向与这个方法有关的 duck 对象。 如果把对象的变量名改为 mallard,那使用 this 后就没有必要在代码中找到所有指向 duck 的部分。 这样可以使得代码更具有可读性和复用性。

3. 构造函数

1)定义构造函数

Constructors 是创建对象的函数。 函数给这个新对象定义属性和行为。 可将它们视为创建的新对象的蓝图。

构造函数遵循以下惯例规则:

  • 构造函数函数名的首字母大写

这是为了方便我们区分构造函数( constructors)和其他非构造函数。

  • 构造函数使用 this 关键字来给它将创建的这个对象设置新的属性。 在构造函数里面,this 指向的就是它新创建的这个对象。
  • 构造函数定义了属性和行为就可创建对象,不需要设置返回值。
function Bird() {

this.name = "Albert";

this.color = "blue";

this.numLegs = 2;

}

注意:构造函数内的 this 总是指被创建的对象。

2)使用构造函数

function Bird() {

  this.name = "Albert";

  this.color  = "blue";

  this.numLegs = 2;

}

let blueBird = new Bird();

//使用new为构造函数创建新的对象

注意:通过构造函数创建对象的时候要使用 new 操作符。

 

因为只有这样,JavaScript 才知道要给 Bird 这个构造函数创建一个新的实例:blueBird。

如果不使用 new 操作符来新建对象,那么构造函数里面的 this 就无法指向新创建的这个对象实例,从而产生不可预见的错误。

现在 blueBird 这个实例就继承了Bird 构造函数的所有属性,如下:

blueBird.name;

blueBird.color;

blueBird.numLegs;

由构造函数创建的实例也和其他对象一样,它的属性可以被访问和修改:

blueBird.name = 'Elvira';

blueBird.name;

3)扩展构造函数以接收参数

以下为可以接收参数的构造函数:

function Bird(name, color) {

  this.name = name;

  this.color = color;

  this.numLegs = 2;

}

let terrier = new Bird();

terrier.name= “Jhon”;// 通过参数的形式传入terrier.name

terrier.color = “Yellow”;

// 通过参数的形式传入terrier.color

4)使用 instanceof 验证对象的构造函数

凡是通过构造函数创建出的新对象,这个对象都叫做这个构造函数的instance(实例)

要想验证这个事实,那就是通过 instanceof 操作符。 instanceof 允许你将对象与构造函数之间进行比较,根据对象是否由这个构造函数创建的返回 true 或者 false。 以下是一个示例:

let Bird = function(name, color) {

  this.name = name;

  this.color = color;

  this.numLegs = 2;

}

let crow = new Bird("Alexis", "black");

crow instanceof Bird;

//返回true,因为验证出了crow是Bird的实例

5)自有属性(自身属性)

function Bird(name) {
  this.name  = name;//name是自有属性
  this.numLegs = 2; //numLegs是自有属性
}
 
let duck = new Bird("Donald");
let canary = new Bird("Tweety");

自有属性是指直接在实例对象上定义的属性

而Bird对象的实例duck与canary都拥有这些属性的独立副本

下面的代码将 duck 的所有自身属性都存到一个叫作 ownProps 的数组里面(类似于创建一个实例的副本?):

let ownProps = [];
 
for (let property in duck) {
  if(duck.hasOwnProperty(property)) {
    ownProps.push(property);
  }
}
 
console.log(ownProps);

6)prototype(_proto_)

返回对象类型原型的引用

适用于要对原型的所有实例删改属性,但是原型不能更改(比如是引用的函数)

class.prototype(注意::是类名,不是对象名)

以下是一个在 Bird prototype 中添加 numLegs 属性的示例:

注意:

Bird.prototype.numLegs指在Bird原型的所有实例中添加numLegs属性,并不会对原型有所影响(constructor原型也可以更改)

而Bird.numLegs则是直接对原型影响

Bird.prototype.numLegs = 2;
console.log(duck.numLegs);//普通的调用即可

_proto_属性是个对象都有的属性

它不是一个规范属性,该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它。

__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。它已被不推荐使用, 现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf和Object.setPrototypeOf/Reflect.setPrototypeOf。

-------------------------------------------------------------

(duck和canary是Bird的实例)

7)更有效的给protytype添加属性

原本给对象的prototyppe属性是一个一个添加属性的

其实可以可以一次性添加属性的,相当于把对象的 prototype 设置为一个已经包含了属性的新对象。

Bird.prototype = {
  numLegs: 2, 
  eat: function() {
    console.log("nom nom nom");
  },
  describe: function() {
    console.log("My name is " + this.name);
  }
};

6)使用constructor属性找出所属对象

function joinBirdFraternity(candidate) {
  if (candidate.constructor === Bird) {
    return true;
  } else {
    return false;
  }
}

注意:由于 constructor 属性可以被重写,所以最好使instanceof 方法来检查对象的类型。

6.1)验证对象与原型之间的关系

使用 isPrototypeOf 方法来验证对象与原型之间的关系

原型.属性名.isPrototypeOf(实例);

例如duck 从 Bird 构造函数那里继承了它的 prototype。

你可以使用 isPrototypeOf 方法来验证他们之间的关系:

Bird.prototype.isPrototypeOf(duck);//返回true

7)更改原型时,记得设置构造函数属性

构造函数属性即constructor:xxx,

手动设置一个新对象的原型会清除constructor 属性。

Bird.prototype = {
  constructor: Bird,//不要忘了添加constructor属性
  numLegs: 2,
  eat: function() {
    console.log("nom nom nom");
  },
  describe: function() {
    console.log("My name is " + this.name); 
  }
};

更改原型如设置原型的prototype{}

8)Object.create(x,y)

创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 

x为创建对象的原型对象

y为可选的需要传入一个对象

 

4. 原型链

JavaScript 中所有的对象(除了少数例外)都有自己的 prototype。 而且,对象的 prototype 本身也是一个对象。

 

5. Mixin

属性是可以通过继承原型来共享的。

然而,在有些情况下,继承不是最好的解决方案。继承不适用于不相关的对象。对于不相关的对象,更好的方法是使用 mixins。

mixin 允许其他对象使用函数集合。

let bird = {
  name: "Donald",
  numLegs: 2
};
let plane = {
  model: "777",
  numPassengers: 524
};
flyMixin(bird);
flyMixin(plane);

这里的 flyMixin 接收了bird 和 plane 对象,然后将 fly 方法分配给了每一个对象。 现在 bird 和 plane 都有可以fly属性了。

6. 闭包(closure)

闭包能够保护对象内的属性不被外部修改

公共属性的定义就是:它可以在原型 的定义范围之外被访问和更改。

-------------------------------------------------------------

使属性私有化最简单的方法就是在构造函数中创建变量。

可以将该变量范围限定在构造函数中,而不是全局可用。

这样,属性只能由构造函数中的方法访问和更改

function Bird() {

  let hatchedEgg = 10;//重点还是let的作用域只在函数内

  this.getHatchedEggCount = function() {

    return hatchedEgg;

  };

}

let ducky = new Bird();

ducky.getHatchedEggCount();

这里的 getHatchedEggCount 是一种特权方法,因为只有它可以访问私有属性 hatchedEgg。

这是因为 hatchedEgg 是在与 getHatchedEggCount 相同的上下文中声明的。 (其实主要还是因为let的作用域仅在函数内,而return又在里面)

在 JavaScript 中,函数总是可以访问创建它的上下文。

这就叫做 closure。

7. 立即调用函数表达(IIFE)

(function () {

  console.log("Chirp, chirp!");

})();//函数在声明后立即执行

这是一个匿名函数表达式,立即执行并输出 Chirp, chirp!。

请注意:函数没有名称,也不存储在变量中。 函数表达式末尾的两个括号()会让它被立即执行或调用。

这种模式被叫做立即调用函数表达式(immediately invoked function expression) 或者IIFE。

posted @ 2021-08-20 20:48  hercule_poirt  阅读(47)  评论(0)    收藏  举报
Copyright © 2021 hercule_poirt