搜索
首页web前端js教程javascript框架设计之类工厂_javascript技巧

类与继承在javascript的出现,说明javascript已经达到大规模开发的门槛了,在之前是ECMAScript4,就试图引入类,模块等东西,但由于过分引入太多的特性,搞得javascript乌烟瘴气,导致被否决。不过只是把类延时到ES6.到目前为止,javascript还没有正真意义上的类。不过我们可以模拟类,曾近一段时间,类工厂是框架的标配,本章会介绍各种类实现,方便大家在自己的框架中或选择时自己喜欢的那一类风格。

1.javascript对类的支持

在其它语言中 ,类的实例都要通过构造函数new出来。作为一个刻意模仿java的语言。javascript存在new操作符,并且所有函数都可以作为构造器。构造函数与普通的方法没有什么区别。浏览器为了构建它繁花似锦的生态圈,比如Node,Element,HTMLElement,HTMLParagraphElement,显然使用继承关系方便一些方法或属性的共享,于是javascript从其它语言借鉴了原型这种机制。Prototype作为一个特殊的对象属性存在于每一个函数上。当一个函数通过new操作符new出其“孩子”——“实例”,这个名为实例的对象就拥有这个函数的Prototype对象所有的一切成员,从而实现实现所有实例对象都共享一组方法或属性。而javascript所谓的“类”就是通过修改这个Prototype对象,以区别原生对象及其其它定义的“类”。在浏览器中,node这个类基于Object修改而来的,而Element则是基于Node,而HTMLElement又基于Element....相对我们的工作业务,我们可以创建自己的类来实现重用与共享。

  function A(){

  }
  A.prototype = {
    aa:"aa",
    method:function(){
    }
  };
  var a = new A;
  var b = new A;
  console.log(a.aa === b.aa);
  console.log(a.method === b.method)

一般地,我们把定义在原型上的方法叫原型方法,它为所有的实例所共享,这有好也有不好,为了实现差异化,javascript允许我们直接在构造器内指定其方法,这叫特权方法。如果是属性,就叫特权属性。它们每一个实例一个副本,各不影响。因此,我们通常把共享用于操作数据的方法放在原型,把私有的属性放在特权属性中。但放于this上,还是让人任意访问到,那就放在函数体内的作用域内吧。这时它就成为名副其实的私有属性。

  function A() {
    var count = 0;
    this.aa = "aa";
    this.method = function() {
      return count;
    }
    this.obj = {}
  }
  A.prototype = {
    aa:"aa",
    method:function(){

    }
  };
  var a = new A;
  var b = new A;
  console.log(a.aa === b.aa);//true 由于aa的值为基本类型,比较值
  console.log(a.obj === b.obj) //false 引用类型,每次进入函数体都要重新创建,因此都不一样。
  console.log(a.method === b.method); //false

特权方法或属性只是只是遮住原型的方法或属性,因此只要删掉特权方法,就能方法到同名的原型方法或属性。

  delete a.method;
  delete b.method;
  console.log(a.method === A.prototype.method);//true
  console.log(a.method === b.method); //true

用java的语言来说,原型方法与特权方法都属性实例方法,在java中还有一种叫类方法与类属性的东西。它们用javascript来模拟也非常简单,直接定义在函数上就行了。

  A.method2 = function(){} //类方法
  var c = new A;
  console.log(c.method2); //undefined

接下来,我们看下继承的实现,上面说过,Prototype上有什么东西,它的实例就有什么东西,不论这个属性是后来添加的,还是整个Prototype都置换上去的。如果我们将这个prototype对象置换为另一个类的原型,那么它就轻而易举的获得那个类的所有原型成员。

  function A() {};
  A.prototype = {
    aaa : 1
  }
  function B() {};
  B.prototype = A.prototype;
  var b = new B;
  console.log(b.aaa); //=> 1;
  A.prototype.bb = 2;
  console.log(b.bb) //=> 2;

由于是引用着同一个对象,这意味这,我们修改A类的原型,也等同于修该了B类的原型。因此,我们不能把一个对象赋值给两个类。这有两种办法,

方法1:通过for in 把父类的原型成员逐一赋给子类的原型
方法2是:子类的原型不是直接由父类获得,先将父类的原型赋值给一个函数,然后将这个函数的实例作为子类的原型。

方法一,我们通常要实现mixin这样的方法,有的书称之为拷贝继承,好处就是简单直接,坏处就是无法通过instanceof验证。Prototype.js的extend方法就用来干这事。

  function extend (des, source) { //des = destination
    for (var property in source)
      des[property] = source[property];
    return des;
  }

方法二,就在原型上动脑筋,因此称之为原型继承。下面是个范本

  function A() {};
  A.prototype = {
    aa:function(){
      alert(1)
    }
  }
  function bridge() {

  };
  bridge.prototype = A.prototype;

  function B() {}
  B.prototype = new bridge();

  var a = new A;
  var b = new B;

  console.log(a == b) //false 证明成功分开原型
  console.log(A.prototype == B.prototype) //true 子类共享父类的原型方法
  console.log(a.aa === b.aa); //为父类动态添加新的方法
  A.prototype.bb = function () {
    alert(2)
  }
  //true,继承父类的方法
  B.prototype.cc = function (){
    alert(3)
  }
  //false 父类未必有子类的new实例
  console.log(a.cc === b.cc)
  //并且它能够正常通过javascript自带的验证机制instanceof
  console.log(b instanceof A) ;//true
  console.log(b instanceof B) ; //true

方法二能通过instanceof验证,es5就内置了这种方法来实现原型继承,它就是Object.create,如果不考虑第二个参数,它约等于下面的代码。

  Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
  }

上面的方法,要求传入一个父类的原型作为参数,然后返回子类的原型

不过,我们这样还是遗漏了一点东西——子类不只是继承父类的遗产,还应该有自己的东西,此外,原型继承并没有让子类继承父类的成员与特权成员。这些我们都得手动添加,如类成员,我们可以通过上面的extend方法,特权成员我们可以在子类构造器中,通过apply实现。

  function inherit(init, Parent, proto){
    function Son(){
      Parent.apply(this, argument); //先继承父类的特权成员
      init.apply(this, argument); //在执行自己的构造器
    }
  }
  //由于Object.create是我们伪造的,因此避免使用第二个参数
  Son.prototype = Object.create(Parent.prototype,{});
  Son.prototype.toString = Parent.prototype.toString; //处理IEbug
  Son.prototype.valueOf = Parent.prototype.valueOf; //处理IEbug
  Son.prototype.constructor = Son; //确保构造器正常指向,而不是Object
  extend(Son, proto) ;//添加子类的特有的原型成员
  return Son;

下面,做一组实验,测试下实例的回溯机制。当我们访问对象的一个属性,那么他先寻找其特权成员,如果有同名就返回,没有就找原型,再没有,就找父类的原型...我们尝试将它的原型临时修改下,看它的属性会变成那个。

  function A(){

  }
  A.prototype = {
    aa:1
  }
  var a = new A;
  console.log(a.aa) ; //=>1

  //将它的所有原型都替换掉
  A.prototype = {
    aa:2
  }
  console.log(a.aa); //=>1

  //于是我们想到每个实例都有一个constructor方法,指向其构造器
  //而构造器上面正好有我们的原型,javascript引擎是不是通过该路线回溯属性呢
  function B(){

  }
  B.prototype = {
    aa:3
  }
  a.constructor = B;
  console.log(a.aa) //1 表示不受影响

因此类的实例肯定通过另一条通道进行回溯,翻看ecma规范可知每一个对象都有一个内部属性[[prototype]],它保存这我们new它时的构造器所引用的Prototype对象。在标准浏览器与IE11里,它暴露了一个叫__proto__属性来访问它。因此,只要不动__proto__上面的代码怎么动,a.aa始终坚定不毅的返回1.

再看一下,new时操作发生了什么。

1.创建了一个空对象 instance
2.instance.__proto__ = intanceClass.prototype
3.将构造函数里面的this = instance
4.执行构造函数里的代码
5.判定有没有返回值,没有返回值就返回默认值为undefined,如果返回值为复合数据类型,则直接返回,否则返回this
于是有了下面的结果。

  function A(){
    console.log(this.__proto__.aa); //1
    this.aa = 2
  }
  A.prototype = {aa:1}
  var a = new A;
  console.log(a.aa)
  a.__proto__ = {
    aa:3
  }
  console.log(a.aa) //=>2
  delete a. aa; //删除特权属性,暴露原型链上的同名属性
  console.log(a.aa) //=>3

有了__proto__,我们可以将原型设计继承设计得更简单,我们还是拿上面的例子改一改,进行试验

  function A() {}
  A.prototype = {
    aa:1
  }
  function bridge() {}
  bridge.prototype = A.prototype;

  function B(){}
  B.prototype = new bridge();
  B.prototype.constructor = B;
  var b = new B;
  B.prototype.cc = function(){
    alert(3)
  }
  //String.prototype === new String().__proto__ => true
  console.log(B.prototype.__proto__ === A.prototype) //true
  console.log(b.__proto__ == B.prototype); //true 
  console.log(b.__proto__.__proto__ === A.prototype); //true 得到父类的原型对象

因为b.__proto__.constructor为B,而B的原型是从bridge中得来的,而bride.prototype = A.prototype,反过来,我们在定义时,B.prototype.__proto__ = A.prototype,就能轻松实现两个类的继承.

__proto__属性已经加入es6,因此可以通过防止大胆的使用

2.各种类工厂的实现。

上节我们演示了各种继承方式的实现,但都很凌乱。我们希望提供一个专门的方法,只要用户传入相应的参数,或按照一定简单格式就能创建一个类。特别是子类。

由于主流框架的类工厂太依赖他们庞杂的工具函数,而一个精巧的类工厂也不过百行左右

相当精巧的库,P.js

https://github.com/jiayi2/pjs

使用版:https://github.com/jiayi2/factoryjs

这是一个相当精巧的库,尤其调用父类的同名方法时,它直接将父类的原型抛在你面前,连_super也省了。

  var P = (function(prototype, ownProperty, undefined) {
 return function P(_superclass /* = Object */, definition) {
  // handle the case where no superclass is given
  if (definition === undefined) {
   definition = _superclass;
   _superclass = Object;
  }

  // C is the class to be returned.
  //
  // When called, creates and initializes an instance of C, unless
  // `this` is already an instance of C, then just initializes `this`;
  // either way, returns the instance of C that was initialized.
  //
  // TODO: the Chrome inspector shows all created objects as `C`
  //    rather than `Object`. Setting the .name property seems to
  //    have no effect. Is there a way to override this behavior?
  function C() {
   var self = this instanceof C ? this : new Bare;
   self.init.apply(self, arguments);
   return self;
  }

  // C.Bare is a class with a noop constructor. Its prototype will be
  // the same as C, so that instances of C.Bare are instances of C.
  // `new MyClass.Bare` then creates new instances of C without
  // calling .init().
  function Bare() {}
  C.Bare = Bare;

  // Extend the prototype chain: first use Bare to create an
  // uninitialized instance of the superclass, then set up Bare
  // to create instances of this class.
  var _super = Bare[prototype] = _superclass[prototype];
  var proto = Bare[prototype] = C[prototype] = C.p = new Bare;

  // pre-declaring the iteration variable for the loop below to save
  // a `var` keyword after minification
  var key;

  // set the constructor property on the prototype, for convenience
  proto.constructor = C;

  C.extend = function(def) { return P(C, def); }

  return (C.open = function(def) {
   if (typeof def === 'function') {
    // call the defining function with all the arguments you need
    // extensions captures the return value.
    def = def.call(C, proto, _super, C, _superclass);
   }

   // ...and extend it
   if (typeof def === 'object') {
    for (key in def) {
     if (ownProperty.call(def, key)) {
      proto[key] = def[key];
     }
    }
   }

   // if no init, assume we're inheriting from a non-Pjs class, so
   // default to using the superclass constructor.
   if (!('init' in proto)) proto.init = _superclass;

   return C;
  })(definition);
 }

 // as a minifier optimization, we've closured in a few helper functions
 // and the string 'prototype' (C[p] is much shorter than C.prototype)
})('prototype', ({}).hasOwnProperty);

我们尝试创建一个类:

  var Dog = P (function(proto, superProto){
    proto.init = function(name) { //构造函数
      this.name = name;
    }
    proto.move = function(meters){ //原型方法
      console.log(this.name + " moved " + meters + " m.")
    }
  });
  var a = new Dog("aaa")
  var b = new Dog("bbb"); //无实例变化
  a.move(1);
  b.move(2);

我们在现在的情况下,可以尝试创建更简洁的定义方式

  var Animal = P (function(proto, superProto){
    proto.init = function(name) { //构造函数
      this.name = name;
    }
    proto.move = function(meters){ //原型方法
      console.log(this.name + " moved " + meters + " m.")
    }
  });
  var a = new Animal("aaa")
  var b = new Animal("bbb"); //无实例变化
  a.move(1);
  b.move(2);
  //...............
  var Snake = P (Animal, function(snake, animal){
    snake.init = function(name, eyes){
      animal.init.call(this, arguments); //调运父类构造器
      this.eyes = 2;
    }
    snake.move = function() {
      console.log('slithering...');
      animal.move.call(this, 5); //调运父类同名方法
    }
  });
  var s = new Snake("snake", 1);
  s.move();
  console.log(s.name);
  console.log(s.eyes);

私有属性演示,由于放在函数体内集中定义,因此安全可靠!

  var Cobra = P (Snake, function(cobra){
    var age = 1;//私有属性
    //这里还可以编写私有方法
    cobra.glow = function(){ //长大
      return age++;
    }
  });
  var c = new Cobra("cobra");
  console.log(c.glow()); //1
  console.log(c.glow()); //2
  console.log(c.glow()); //3

以上所述就是本文的全部内容了,希望大家能够喜欢。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版