关于javascript:JavaScript-面向对象编程

9次阅读

共计 9820 个字符,预计需要花费 25 分钟才能阅读完成。

面向对象编程 Object Oriented Programming

面向对象编程用对象把数据和办法聚合起来。

面向对象编程的长处

  • 能写出模块化的代码
  • 能使得代码更灵便
  • 能进步代码的可重用性

面向对象编程的准则

  • 继承 (inheritance):子类 / 派生类从父类 / 基类 / 超类中派生,造成继承构造
  • 封装 (encapsulation):代码的实现对用户不可见,例如调用 toUpperCase(),间接调用即可,不必思考函数外部的实现
  • 形象 (abstraction):形象就是形象出你正在尝试做的事件的概念,而不是解决这个概念的具体表现形式
  • 多态 (polymorphism):多种状态

对象

创建对象

  • 应用对象字面量创立:
let dog = {
    name: "Mit",
    numLegs: 2
}
  • 应用 Object.create() 创立:(所有的对象都来自某个类,对象是类的实例,也是一种层级构造)
class Animal {/* code */}

var myDog = Object.create(Animal);
console.log(Animal);
  • 应用 new 关键字:(当应用默认或者空构造函数办法,JS 会隐式调用对象的超类来创立实例)
class Animal {/* code here */}

var myDog = new Animal();
console.log(Animal)

拜访对象属性

let dog = {
    name: "Mit",
    numLegs: 2
}
console.log(dog.name); // Mit

对象办法 Method

对象能够有一个叫做 method 的非凡属性。

办法属性也就是函数。这给对象增加了不同的行为。

let dog = {
  name: "Spot",
  numLegs: 4,
  sayLegs: function() {return "This dog has 4 legs."}
};

dog.sayLegs();

this

this 代表上下文环境中援用的对象。

let dog = {
  name: "Spot",
  numLegs: 4,
  sayLegs: function() {return "This dog has" + this.numLegs + "legs.";}
};

dog.sayLegs(); // This dog has 4 legs.

构造函数

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

构造函数遵循一些常规规定:

  • 构造函数函数名的首字母大写,这是为了不便辨别构造函数(constructors)和其余非构造函数。
  • 构造函数应用 this 关键字来给它将创立的这个对象设置新的属性。在构造函数外面,this 指向的就是它新创建的这个对象。
  • 构造函数定义了属性和行为就可创建对象,而不是像其余函数一样须要设置返回值。
function Dog() {
  this.name = "N";
  this.color = "red";
  this.numLegs = 2;
}

function Dog(name, color) {
  this.name = name;
  this.color = color;
  this.numLegs = 4;
}

let terrier = new Dog("fsd","fa");

JS 中有很多内置对象类型,称为“原生对象(native object)”。有些类型能够应用构造函数获取一个实例,然而有些类型没有也不须要构造函数。例如 Date 能够用 new 关键字创建对象,然而 Math 对象就不行。

如果对象类型是根本类型(primitive),应用构造函数通常不是最佳实际:

let apple = new String('apple');
apple; // string

对于惯例对象,最好应用对象字面量语法 {},而不是 new。例如:数组,函数,正则表达式等。

下列类型能够思考构造函数:

new Date();
new Error();
new Map();
new Promise();
new Set();
new WeakSet();
new WeakMap();

应用构造函数创建对象

留神: 构造函数内的 this 总是指被创立的对象。

留神:通过构造函数创建对象的时候要应用 new 操作符。

function Dog() {
  this.name = "Rupert";
  this.color = "brown";
  this.numLegs = 4;
}
let hound = new Dog();

instanceof

但凡通过构造函数创立出的新对象,这个对象都叫做这个构造函数的 instance。JavaScript 提供了一种很简便的办法来验证这个事实,那就是通过 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

属性

本身属性 Own Property

请看上面的实例,Bird 构造函数定义了两个属性:namenumLegs

function Bird(name) {
  this.name  = name;
  this.numLegs = 2;
}

let duck = new Bird("Donald");
let canary = new Bird("Tweety");

namenumLegs 被叫做 本身属性,因为它们是间接在实例对象上定义的。这就意味着 duckcanary 这两个对象别离领有这些属性的独立正本。事实上,Bird 的所有实例都将领有这些属性的独立正本。

原型属性 Prototype Property

所有 Bird 实例可能会有雷同的 numLegs 值,所以在每一个 Bird 的实例中实质上都有一个反复的变量 numLegs

当只有两个实例时可能并不是什么问题,但设想一下如果有数百万个实例。这将会产生许许多多反复的变量。

更好的办法是应用 Birdprototypeprototype 是一个能够在所有 Bird 实例之间共享的对象。以下是一个在 Bird prototype 中增加 numLegs 属性的示例:

Bird.prototype.numLegs = 2;

当初所有的 Bird 实例都领有了独特的 numLegs 属性值。

console.log(duck.numLegs);
console.log(canary.numLegs);

因为所有的实例都能够继承 prototype 上的属性,所以能够把 prototype 看作是创建对象的 “ 配方 ”。请留神:duckcanaryprototype 属于 Bird 的构造函数,即 Bird 的原型 Bird.prototype。JavaScript 中简直所有的对象都有一个 prototype 属性,这个属性是属于它所在的构造函数。

迭代属性 Iterate Property

本身属性是间接在对象上定义的。而原型属性在 prototype 上定义。

function Dog(name) {this.name = name;}

Dog.prototype.numLegs = 4;

let beagle = new Dog("Snoopy");

let ownProps = [];
let prototypeProps = [];

for (let property in beagle) {if (beagle.hasOwnProperty(property)) {ownProps.push(property); // let in 只能遍历本身属性
  } else {prototypeProps.push(property);
  }
}

构造函数属性 Constructor Property

在上一个挑战中创立的实例对象 duckbeagle 都有一个非凡的 constructor 属性:

let duck = new Bird();
let beagle = new Dog();

console.log(duck.constructor === Bird); // true
console.log(beagle.constructor === Dog); // true

须要留神到的是: constructor 属性是对创立实例的构造函数的一个援用。constructor 属性的一个益处是能够通过查看这个属性来找出它是一个什么对象。
留神: 因为 constructor 属性能够被重写,所以最好应用 instanceof 办法来查看对象的类型。

给对象增加属性

一种更无效的办法就是给对象的 prototype 设置为一个曾经蕴含了属性的新对象。这样一来,所有属性都能够一次性增加进来:

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

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

手动设置一个新对象的原型有一个重要的副作用。它革除了 constructor 属性!此属性能够用来查看是哪个构造函数创立了实例,但因为该属性已被笼罩,它当初给出了谬误的后果:

duck.constructor === Bird; // false
duck.constructor === Object; // true
duck instanceof Bird; // true

为了解决这个问题,但凡手动给新对象从新设置过原型对象的,都别忘记在原型对象中定义一个 constructor 属性:

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

原型 Prototype

在 JS 中,原型是一种属性能够被多个其余对象共享的对象。

对象的原型

来自同一原型的对象有雷同的性能。

对象也可间接从创立它的构造函数那里继承其 prototype。请看上面的例子:Bird 构造函数创立了一个 duck 对象:

function Bird(name) {this.name = name;}

let duck = new Bird("Donald");

duckBird 构造函数那里继承了它的 prototype。你能够应用 isPrototypeOf 办法来验证他们之间的关系:

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

原型链 Prototype Chain

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

function Bird(name) {this.name = name;}

typeof Bird.prototype; // object

正因为 prototype 是一个对象,所以 prototype 对象也有它本人的 prototype!这样看来的话,Bird.prototypeprototype 就是 Object.prototype

Object.prototype.isPrototypeOf(Bird.prototype);

hasOwnProperty 是定义在 Object.prototype 上的一个办法,只管在 Bird.prototypeduck 上并没有定义该办法,然而仍然能够在这两个对象上拜访到。这就是 prototype 链的一个例子。在这个 prototype 链中,Birdducksupertype,而 ducksubtypeObject 则是 Birdduck 实例独特的 supertypeObject 是 JavaScript 中所有对象的 supertype,也就是原型链的最顶层。因而,所有对象都能够拜访 hasOwnProperty 办法。

继承 Inherit

依据以上所说的 DRY 准则,通过创立一个 Animal supertype(或者父类)来重写这段代码:

function Animal() {};

Animal.prototype = {
  constructor: Animal, 
  describe: function() {console.log("My name is" + this.name);
  }
};

Animal 构造函数中定义了 describe 办法,可将 BirdDog 这两个构造函数的办法删除掉:

Bird.prototype = {constructor: Bird};

Dog.prototype = {constructor: Dog};

继承应用 extends 关键字,例如 class B extends class A

class Animal {/* code */}
class Bird extends Animal {/* code */}
class Eagle extends Bird {/* code */}

从超类继承行为 Inherit Behaviors from a Supertype

这里用到构造函数的继承个性。首先创立一个超类 supertype(或者叫父类)的实例。

let animal = new Animal();

此语法用于继承时会存在一些毛病。相同,另外一种没有这些毛病的办法来代替 new 操作:

let animal = Object.create(Animal.prototype);

Object.create(obj) 创立了一个新对象,并指定了 obj 作为新对象的 prototype。回顾一下,之前说过 prototype 就像是创建对象的“配方”。如果把 animalprototype 设置为与 Animal 构造函数的 prototype 一样,那么就相当于让 animal 这个实例具备与 Animal 的其余实例雷同的“配方”了。

第二个步骤:给子类型(或者子类)设置 prototype。这样一来,Bird 就是 Animal 的一个实例了。

Bird.prototype = Object.create(Animal.prototype);

请记住,prototype 相似于创建对象的“配方”。从某种意义上来说,Bird 对象的配方蕴含了 Animal 的所有要害“成分”。

let duck = new Bird("Donald");
duck.eat();

duck 继承了 Animal 的所有属性,其中包含了 eat 办法。

super 关键字能够借用父类中的性能。

class Animal {constructor(color = 'yellow', energy = 100) {
        this.color = color;
        this.energy = energy;
    }
    isActive() {if(this.energy > 0) {
            this.energy -= 20;
            console.log('Energy is decreasing, currently at:', this.energy)
        } else if(this.energy == 0){this.sleep();
        }
    }
    sleep() {
        this.energy += 20;
        console.log('Energy is increasing, currently at:', this.energy)
    }
    getColor() {console.log(this.color)
    }
}

class Cat extends Animal {constructor(sound = 'purr', canJumpHigh = true, canClimbTrees = true, color, energy) {super(color, energy);
        this.sound = sound;
        this.canClimbTrees = canClimbTrees;
        this.canJumpHigh = canJumpHigh;
    }
    makeSound() {console.log(this.sound);
    }
}

class Bird extends Animal {constructor(sound = 'chirp', canFly = true, color, energy) {super(color, energy);
        this.sound = sound;
        this.canFly = canFly;
    }
    makeSound() {console.log(this.sound);
    }

留神⚠️:如果在子类中不应用 super 关键字,一旦运行下面的程序,会失去 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor. 谬误。

重置结构属性 Reset

当一个对象从另一个对象那里继承了其 prototype 时,那它也继承了父类的 constructor 属性。

请看上面的举例:

function Bird() {}
Bird.prototype = Object.create(Animal.prototype);
let duck = new Bird();
duck.constructor // Bird

然而 duck 和其余所有 Bird 的实例都应该表明它们是由 Bird 创立的,而不是由 Animal 创立的。为此,你能够手动将 Bird 的构造函数属性设置为 Bird 对象:

Bird.prototype.constructor = Bird;
duck.constructor // Bird

继承后增加办法

从超类构造函数继承其 prototype 对象的构造函数,除了继承的办法外,还能够领有本人的办法。

除了从 Animal 构造函数继承的行为之外,还须要给 Bird 对象增加它独有的行为。这里,给 Bird 对象增加一个 fly() 函数。函数会以一种与其余构造函数雷同的形式增加到 Bird'sprototype 中:

Bird.prototype.fly = function() {console.log("I'm flying!");
};

当初 Bird 的实例中就有了 eat()fly() 这两个办法:

let duck = new Bird();
duck.eat();
duck.fly();

duck.eat() 将在控制台中显示字符串 nom nom nomduck.fly() 将显示字符串 I'm flying!

重写继承的办法 Override

通过应用一个与须要重写的办法雷同的办法名, 向 ChildObject.prototype 中增加办法。请看上面的举例:Bird 重写了从 Animal 继承来的 eat() 办法:

function Animal() {}
Animal.prototype.eat = function() {return "nom nom nom";};
function Bird() {}

Bird.prototype = Object.create(Animal.prototype);

Bird.prototype.eat = function() {return "peck peck peck";};

如果一个实例:let duck = new Bird();,而后调用了 duck.eat(),以下就是 JavaScript 在 duckprototype 链上寻找办法的过程:

  1. duck => eat() 是定义在这里吗?不是。
  2. Bird => eat() 是定义在这里吗?=> 是的。执行它并进行往上搜寻。
  3. Animal => 这里也定义了 eat() 办法,然而 JavaScript 在达到这层原型链之前已进行了搜寻。
  4. Object => JavaScript 在达到这层原型链之前也曾经进行了搜寻

多态 Polymorphism

多态来源于希腊语,意思是“多种状态”。

例如门和自行车上都有铃,然而它们的应用目标,状态都可能不同。

function ringTheBell(thing) {console.log(thing.bell())
}

ringTheBell(door);
ringTheBell(bicycle);

例如 concat() 办法和 + 都能够起到字符串拼接的作用,然而它们的状态不同。

多态性容许开发人员构建具备完全相同性能的对象,即具备完全相同名称、行为完全相同的函数。但同时,开发者能够在 OOP 构造的其余局部笼罩共享性能的某些局部,甚至笼罩残缺性能。

举例:

class Bird {useWings() {console.log('flying');
  }
}

class Eagle {useWings() {super.useWings();
    console.log('barely flying');
  }
}

class Penguin extends Bird {useWings() {console.log('diving');
  }
}

var baldEagle = new Eagle();
var kingPenguin = new Penguin();
baldEagle.useWings(); // flying barely flying
kingPenguin.useWings() // diving

应用 Mixin 在不相干对象之间增加独特行为

对于不相干的对象,更好的办法是应用 mixins。mixin 容许其余对象应用函数汇合。

let bird = {
  name: "Donald",
  numLegs: 2
};

let boat = {
  name: "Warrior",
  type: "race-boat"
};

let glideMixin = function(obj) {obj.glide = function() {console.log("glide");
  }
};

glideMixin(bird);
glideMixin(boat);

私有化 Private

使属性私有化最简略的办法就是在构造函数中创立变量。能够将该变量范畴限定在构造函数中,而不是全局可用。这样,属性只能由构造函数中的办法拜访和更改。

在 JavaScript 中,函数总是能够拜访创立它的上下文。这就叫做 closure(闭包)。

function Bird() {
  let hatchedEgg = 10;

  this.getHatchedEggCount = function() {return hatchedEgg;};
}
let ducky = new Bird();
ducky.getHatchedEggCount();

立刻调用函数表达式(IIFE)

JavaScript 中的一个常见模式就是,函数在申明后立即执行:

(function () {console.log("Chirp, chirp!");
})();

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

请留神,函数没有名称,也不存储在变量中。函数表达式开端的两个括号()会让它被立刻执行或调用。这种模式被叫做立刻调用函数表达式(immediately invoked function expression) 或者 IIFE。

一个立刻调用函数表达式(IIFE)通常用于将相干性能分组到单个对象或者是 module 中。例如,先前的挑战中定义了两个 mixins:

function glideMixin(obj) {obj.glide = function() {console.log("Gliding on the water");
  };
}
function flyMixin(obj) {obj.fly = function() {console.log("Flying, wooosh!");
  };
}

能够将这些 mixins 分成以下模块:

let motionModule = (function () {
  return {glideMixin: function(obj) {obj.glide = function() {console.log("Gliding on the water");
      };
    },
    flyMixin: function(obj) {obj.fly = function() {console.log("Flying, wooosh!");
      };
    }
  }
})();

留神:一个立刻调用函数表达式(IIFE)返回了一个 motionModule 对象。返回的这个对象蕴含了作为对象属性的所有 mixin 行为。module 模式的长处是,所有的静止相干的行为都能够打包成一个对象,而后由代码的其余局部应用。上面是一个应用它的例子:

motionModule.glideMixin(duck);
duck.glide();

正文完
 0