ECMAScript a deux modèles de développement : 1. Fonctionnel (procédural) ; 2. Orienté objet (POO) ;
1 Créer un objet
1. Création ordinaire d'objets
// 创建一个对象,然后给这个对象新的属性和方法; var box = new Object(); // 创建一个Object对象; box.name = 'lee'; // 创建一个name属性并赋值; box.age = 100; box.run = function(){ // 创建一个run()方法并返回值; return this.name+this.age+'运行中...'; } console.log(box.run()); // 输入属性和方法的值; // 缺点:想创建类似的对象,就会产生大量的代码;
// 这种方法就是为了解决实例化对象产生大量代码重复的问题; function createObject(name,age){ // 集中创建函数体; var obj = new Object; // 函数体内创建Object; obj.name = name; obj.age = age; obj.run = function(){ return this.name+this.age+"运行中..."; }; return obj; } var box1 = createObject("lee",100); // 实例化;调用函数并传参; var box2 = createObject("jack",200); // 实例二; console.log(box1.run()+box2.run()); // 实例保持相对独立; // 缺点:对象与实例的识别问题;无法搞清楚它们到底是那个对象的实例; console.log(typeof box1); // Object;
// ECMAScript采用构造函数(构造方法)可用来创建特定的对象; function Box(name,age){ // 构造函数模式; this.name = name; // this代表对象Box; this.age = age; this.run = function(){ return this.name+this.age+"运行中..."; }; } var box1 = new Box("lee",100); // 要创建对象的实例必须用new操作符; var box2 = new Box("jack",200); // box1和box2都是Box对象的实例; console.log(box1 instanceof Box); // true;很清晰的识别box1从属于Box; // 使用构造函数,即解决了重复实例化的问题,有解决了对象识别的问题;
La différence entre l'utilisation d'un constructeur et d'un modèle d'usine :
(1). La méthode constructeur n'affiche pas l'objet créé (nouvel Objet);
(2). Attribuez directement des propriétés et des méthodes à cet objet
(3). Aucune instruction de retour ;1 //Spécification du constructeur :
(1). Le nom de la fonction (fonction Box) et le nom de la construction d'instanciation (nouvelle Box) sont identiques et en majuscules
.
(2). Pour créer un objet instance via le constructeur, vous devez utiliser l'opérateur new
;
// 构造函数和普通函数的区别: var box = new Box('lee',100); // 构造模式调用; Box('lee',200); // 普通模式调用,无效; var o = new Object(); Box.call(o,'jack',200); // 对象冒充调用; // 将Box对象作用域扩充到对象o;Box()方法的运行环境已经变成了对象o里;
Problème avec le constructeur :
Lors de l'utilisation du constructeur pour créer chaque instance, les méthodes du constructeur doivent être recréées sur chaque instance
Parce que les fonctions dans ECMAScript sont des objets, chaque fois qu'une fonction est définie, un objet est instancié
;
La création de fonctions de cette manière entraîne différentes chaînes de portée et résolution d'identifiants ;
Deux prototypes
//Chaque fonction que nous créons a un attribut prototype, qui est un objet
;
// Compréhension : prototype est l'objet prototype de l'objet créé en appelant le constructeur;
// L'avantage d'utiliser un prototype est que toutes les instances d'objet peuvent partager les propriétés et les méthodes qu'il contient;
// En d'autres termes, vous n'avez pas besoin de définir les informations sur l'objet (propriétés/méthodes) dans le constructeur, mais vous pouvez directement ajouter ces informations au prototype;
1. Mode prototype (le prototype ajoute des attributs et des méthodes)
1.原型模式 function Box(){} // 声明构造函数; Box.prototype.name = 'Lee'; // 在原型里添加属性和方法; Box.prototype.age = 100; Box.prototype.run = function() { return this.name+this.age+'运行中...'; }; var box1 = new Box(); var box2 = new Box(); console.log(box1.run==box2.run); // =>true;方法引用的地址保持一致; // 在原型中多了两个属性,这两个原型属性都是创建对象时自动生成的; // 1.__proto__:构造函数指向原型对象的一个指针;它的作用:指向构造函数的原型的属性constructor; 14// IE浏览器在脚本访问__proto__会不能识别; 15 // 判断一个实例对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试; console.log(Box.prototype.isPrototypeOf(box)); // =>true; 只要实例化对象,即都会指向; // 原型模式的执行流程: // 1.先查找构造函数对象的实例里的属性或方法,若有,立刻返回; // 2.若构造函数对象的实例里没有,则去它的原型对象里找,若有,就返回; // 虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值; var box1 = new Box(); console.log(box1.name); // Lee; 原型里的值; bo1.name = 'jack'; console.log(box1.name); // Jack;实例自己赋的值; var box2 = new Box(); console.log(box2.name); // Lee;原型里的值;没有被box1修改; // 如果想要box1继续访问原型里的值,可以把构造函数里的属性删除即可; delete box1.name; // 删除实例自己的属性; console.log(box1.name); // Lee; 原型里原来的值;
2. Prototype et en opérateur
Comment déterminer si la propriété est dans l'instance du constructeur ou dans le prototype ? Vous pouvez utiliser la fonction hasOwnProperty() pour vérifier ;
console.log(box.hasOwnProperty('name')); // S'il existe une instance, renvoie true, sinon renvoie false
L'opérateur in retournera true lorsque la propriété donnée est accessible via l'objet, que la propriété existe dans l'instance ou dans le prototype
;
console.log('name' in box); // =>true, existe dans l'instance ou le prototype; 3. Syntaxe de prototype plus simple (mode littéral du prototype)
3. Syntaxe de prototype plus simple (mode littéral du prototype)
function Box(){}; Box.prototype = { // 以字面量形式创建包含属性和方法的新对象; name:'Lee', age:100, run:function(){ return this.name+this.age+'运行中...'; } }; // 使用构造函数创建原型对象和使用字面量创建原型对象在使用上基本相同; // 但是,使用字面量创建的原型对象使用constructor属性不会指向实例,而是指向原型对象Object;构造函数的方式则相反; var box = new Box(); console.log(box instanceof Box); console.log(box instanceof Object); console.log(box.constructor == Box); // 字面量方式,返回false; console.log(box.constructor == Object); // 字面量方式,返回true; // 如果想让字面量方式的constructor指向实例对象: Box.prototype = { constructor:Box, // 直接强制指向即可; } // PS:字面量方式为什么constructor会指向Object? // 因为Box.prototype={}这种字面量写法就是创建一个新对象; // 而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性; // 所以,新对象的constructor重写了Box原来的constructor,因此指向了新对象, // 那个新对象没有指定构造函数,那么就默认为是Object;
4. Nature dynamique du prototype (la réécriture écrasera le contenu précédent)
// 原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, run:function(){ return this.age+'运行中...'; } }; Box.prototype = { // 原型重写了,覆盖了之前的原型; age:200, run:function(){ return this.age+'运行中...'; } } var box = new Box(); console.log(box.run()); // =>200运行中...; // 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;对象实例引用的仍然是最初的原型;
5. Prototype d'objet natif
// Les objets prototypes peuvent non seulement être utilisés dans des objets personnalisés, mais également les types de référence intégrés ECMAScript peuvent être utilisés de cette manière,
// Et le type de référence intégré lui-même utilise également des prototypes ;
console.log(Array.prototype.sort); // =>function sort() { [code natif] };
console.log(String.prototype.substring); // =>function substring() { [code natif] };
6. Problèmes avec les objets prototypes
// 原型模式创建对象缺点:省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的; // 而原型最大的有点就是共享,属性共享; // 但是,如果原型中的属性包含引用类型(对象),共享就会存在一定问题; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, family:['father','mother'], run:function(){ return this.name+this.age+this.family; } }; var box1 = new Box(); box1.family.push('sister'); // 为box1的family属性添加了sister;而这个属性被共享到原型了; console.log(box1.run()); // =>Lee100father,mother,sister; var box2 = new Box(); console.log(box2.run()); // =>Lee100father,mother,sister; // 数据共享导致实例化出的数据不能保存自己的特性;
7. Utilisez une combinaison de modèle de constructeur (données non partagées par les objets) et de modèle de prototype (données partagées par les objets)
// 为了解决构造传参和共享问题,组合构造函数+原型模式: function Box(name,age){ // 不共享的使用构造函数; this.name = name; this.age = age; this.family = ['father','moter']; }; Box.prototype = { // 共享的使用原型模式; constructor:Box, run:function(){ return this.name+this.age+this.family; } }; // PS:这种混合模式很好的解决了传参和引用共享的大难题;是创建对象比较好的方法;
8. Mode prototype dynamique (encapsuler le prototype dans le constructeur)
// 原型模式,不管是否调用了原型中的共享方法,它都会初始化原型中的方法; // 并且在声明一个对象时,构造函数+原型让人感觉怪异;最好把构造函数和原型封装到一起; function Box(name,age){ // 将所有信息封装到构造函数体内; this.name = name; this.age = age; // 当第一次调用构造函数时,run()方法不存在,然后执行初始化原型; // 当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会载初始化; // 这样既得到了封装,又实现了原型方法共享,并且属性都保持独立; if(typeof this.run != 'function'){ // 仅在第一次调用时初始化; Box.prototype.run = function (){ return this.name+this.age+'运行中...'; }; } }; var box = new Box('lee',10); console.log(box.run()); // PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系;
9. Constructeur parasite
// 寄生构造函数,其实就是工厂模式+构造模式;这种模式比较通用,但不能确定对象关系; function Box(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = function (){ return this.name+this.age+'运行中...'; }; return obj; }
Trois héritages
1. Chaîne prototype
// 继承是面向对象中一个比较核心的概念; // 其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承; // 而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成; // 实质:利用原型让一个引用类型继承另一个引用类型的属性和方法; // 原型继承链:Box ==>> Desk ==>> Table; function Box(){ // Box构造; this.name = 'Lee'; } function Desk(){ // Desk构造; this.age = 100; } Desk.prototype = new Box(); // 通过创建Box实例,并赋值给Desk.prototype实现的;通过原型,形成链条; // 实质是:重写了Desk的原型对象,取而代之的是一个新类型Box的实例; // 也就是说原来存在于Box实例中的属性和方法,现在也存在与Desk.prototype中了; var desk = new Desk(); console.log(desk.age); // 100; console.log(desk.name); // =>Lee; function Table(){ this.level = 'AAA'; } Table.prototype = new Desk(); // 继续原型链继承;Table继承了Desk; var table = new Table(); console.log(table.name); // Lee;
// PS:以上原型链继承缺少一环,那就是Object,所有的构造函数都继承自Object; // 而继承Object是自动完成的,并不需要手动继承; console.log(table instanceof Object); // =>true; console.log(desk instanceof Table); // =>false;Desk是Table的超类; console.log(table instanceof Desk); // =>true; console.log(table instanceof Box); // =>true; // 在JS中,被继承的函数称为超类型(父类,基类); // 继承的函数称为子类型(子类,派生类); // 继承问题: // 字面量重写原型会中断关系; // 子类型无法给超类型传递参数;
//Afin de résoudre le problème du partage de références et de l'impossibilité de passer des paramètres aux super types ;
// 在子类型构造函数的内部调用超类型构造函数; function Box(age){ this.name = ['Lee','Jack','Hello']; this.age = age; } function Desk(age){ // 继承了Box;同时还传递了参数; // 这样一来,就会在新Desk对象上执行Box()函数中定义的所有对象初始化代码; Box.call(this,age); // 对象冒充,Desk继承Box,并可以给超类型传参; // 为了确保Box构造函数不会重写子类型的属性,可以在超类型构造函数后,再添加应该在子类型中定义的属性; this.height = 175; } var desk = new Desk(200); // 向Desk()函数传参,再通过函数冒用向Box()函数传参; console.log(desk.age); // =>200; console.log(desk.name); // =>['Lee','Jack','Hello']; desk.name.push('AAA'); // =>添加的新数据,只添加给desk; console.log(desk.name); // =>['Lee','Jack','Hello','AAA'];
// Bien que l'emprunt de constructeurs résout le problème du partage de références et de l'incapacité de transmettre des paramètres aux super types, il n'utilise pas de prototypes et la réutilisation est donc impossible, le modèle d'héritage combiné est nécessaire ;
// 使用原型链实现对原型属性和方法的继承; // 通过借用构造函数来实现对实例属性的继承; // 这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有他自己的属性; function Box(age){ // 构造函数; this.name = ['Lee','Jack','Hello']; this.age = age; } Box.prototype.run = function(){ // 原型; return this.name+this.age; } function Desk(age){ Box.call(this,age); // 继承属性; 对象冒充; 将Box对象的作用域扩充到Desk中,Desk就会继承Box里的属性和方法; } Desk.prototype = new Box(); // 继承方法; 原型链继承; var desk = new Desk(100); console.log(desk.run()); // =>Lee,Jack,Hello100 // 最常用的继承模式;
// 这种继承借助原型并基于已有的对象创建对象,同时还不必因此创建自定义类型; function obj(o){ // 传递一个字面量函数; function F(){}; // 创建一个构造函数; F.prototype = o; // 把字面量函数赋值给构造函数的原型; return new F(); // 返回实例化的构造函数; } var box = { // 字面量对象; name:'Lee', arr:['brother','sisiter'] }; var box1 = obj(box); console.log(box1.name); // =>Lee; box1.name = 'Jack'; console.log(box1.name); // =>Jack; console.log(box1.arr); // =>brother,sister; box1.arr.push('father'); // console.log(box1.arr); // =>brother,sister,father; var box2 = obj(box); console.log(box2.name); // =>Lee; console.log(box2.arr); // =>brother,sister,father;引用类型共享了;
// 把原型式+工厂模式结合而来,目的是为了封装创建对象的过程; // 创建一个仅用于封装继承过程的函数, function create(o){ // 封装创建过程; var f = obj(o); f.run = function(){ return this.arr; // 同样会共享引用; }; return f; }
Quatre résumés
1. Créer un objet
Les objets peuvent être créés et améliorés lors de l'exécution du code et sont donc des entités dynamiques plutôt que strictement définies
;
En l'absence de classes, les objets peuvent être créés en utilisant les modèles suivants ;
(1). Modèle d'usine : utilisez des fonctions simples pour créer des objets, ajouter des propriétés et des méthodes aux objets, puis renvoyer les objets
;
Ce modèle a ensuite été remplacé par le modèle constructeur ;
(2). Mode constructeur : vous pouvez personnaliser le type de référence et utiliser l'opérateur new pour créer une instance d'objet intégrée en un coup d'œil
;
Inconvénients : chacun de ses membres ne peut pas être réutilisé, y compris les fonctions ; puisque les fonctions ne peuvent être limitées à aucun objet, il n'y a aucune raison de ne pas partager les fonctions entre plusieurs objets
;
(3). Mode prototype : utilisez l'attribut prototype de la fonction pour spécifier les attributs et les méthodes qui doivent être partagés
;
Lorsque vous utilisez le modèle de constructeur et le modèle de prototype en combinaison, utilisez le constructeur pour définir les propriétés de l'instance et utilisez le prototype pour définir les propriétés et les méthodes partagées ;
2. Chaîne prototype
La construction de la chaîne de prototypes est réalisée en attribuant une instance d'un type au prototype d'un autre constructeur
;
Les sous-types peuvent accéder à toutes les propriétés et méthodes du supertype ;
Le problème avec la chaîne de prototypes est que les instances d'objet partagent toutes les propriétés et méthodes héritées, elles ne peuvent donc pas être utilisées seules ;
Solution : emprunter le constructeur, c'est-à-dire appeler le constructeur de supertype à l'intérieur du constructeur de sous-type
;
De cette façon, chaque instance peut avoir ses propres propriétés, et elle peut également garantir que seul le constructeur est utilisé pour définir le type ;
Le modèle d'héritage le plus utilisé est l'héritage compositionnel ; il utilise la chaîne de prototypes pour hériter des propriétés et des méthodes partagées, et hérite des propriétés d'instance en empruntant des constructeurs ;
3. Mode Héritage
(1). Héritage prototypique : L'héritage peut être réalisé sans avoir à définir un constructeur à l'avance ; son essence est d'effectuer une copie superficielle d'un objet donné
;
La copie copiée peut être transformée davantage ;
(2) Héritage parasite : créer un objet basé sur un objet ou des informations, puis améliorer l'objet, et enfin renvoyer l'objet
;
Afin de résoudre le problème d'inefficacité causé par le modèle d'héritage combiné en raison de plusieurs appels au constructeur de supertype, ce modèle peut être utilisé avec l'héritage combiné ;
(3) Héritage combiné parasite : il combine les avantages de l'héritage parasitaire et de l'héritage combiné et constitue le moyen le plus efficace de mettre en œuvre l'héritage basé sur le type ;