/*本文并非是介绍JavaScript的原型的原理的文章, **仅仅讨论function作为类来使用时如何近似传统的面向对象设计 **/ /*function作为类的用法 **下面是一个最简单的类 **实例将有自己的属性val和pVal,也有方法printVal和pMethod **/ function Class01( val, pVal ) { this.val = val; /*实例可直接读写的属性*/ var pVal = pVal; /*实例无法直接读写的属性*/ } Class01.prototype.printVal = function () { var _this = this; /*尽管此处并不会出现this丢失的情况,但推荐总是这么做*/ console.log( _this.val ); }; /*实例可直接读写的属性和方法不再多说 **对于不可直接读写的属性pVal(这是传统面向对象语言中的私有属性) **需要通过公有接口来访问pVal,如下面的getpVal和setpVal **这样做的弊端十分明显,每一个实例都将拥有getpVal和setpVal **是否可以在原型上定义getpVal和setpVal,先来试试 **/ function Class01( val, pVal ) { this.val = val; /*实例可直接读写的属性*/ var pVal = pVal; /*实例无法直接读写的属性*/ this.getpVal = function () { return pVal; }; this.setpVal = function ( v ) { pVal = v; return this; } } /*下面的例子是通过原型方法读取“私有”属性pVal **试过发现,运行报错,pVal并不存在 **路子都被堵死了,采用这种方式定义“私有”属性将只能通过实例方法读写 **这种方式显然不能在工程中使用,下面介绍另外一种方法 **/ function Class01( val, pVal ) { this.val = val; /*实例可直接读写的属性*/ var pVal = pVal; /*实例无法直接读写的属性*/ } Class01.prototype.printVal = function () { var _this = this; /*尽管此处并不会出现this丢失的情况,但推荐总是这么做*/ console.log( _this.val ); }; Class01.prototype.getpVal = function () { console.log( pVal ); }; var ins01 = new Class01( 1, 2 ); ins01.getpVal(); /*此处报错*/ /*采用高阶函数的方式,return Class的方式 **在Class01的内部只定义可直接读写的属性 **把“私有”属性或方法放到Class作用域外部 **/ var Class01 = ( function () { var pValue = ‘‘; /*实例无法直接读写的属性*/ function hello () { console.log( ‘欢迎来到nDos的博客‘ ); } function Class( val, pVal ) { this.val = val; /*实例可直接读写的属性*/ pValue = pVal; } Class.prototype.printVal = function () { var _this = this; /*尽管此处并不会出现this丢失的情况,但推荐总是这么做*/ console.log( _this.val ); }; Class.prototype.getpVal = function () { console.log( pValue ); return pValue; }; Class.prototype.setpVal = function ( v ) { pValue = v; return this; }; Class.prototype.sayHello = function () { hello(); return this; }; return Class; } )(); var ins01 = new Class01( 1, 2 ); ins01.getpVal(); ins01.setpVal( ‘2222‘ ).getpVal(); ins01.sayHello(); /*小问题: **实例ins01是由Class实例化而来 **还是由Class01实例化而来 **读者可先自行思考 **/ console.log( ins01.constructor.name ); /*此处是Class*/ /*Class01这个变量在ins01中找不到任何的痕迹 **想要搞清楚可能需要到JS引擎中去找 **本文目的显然并不是要搞清楚这个问题,留给读者研究或者持续关注本博客 **console.log( ins01 ) **在Google的开发者工具中找到如下的属性 **Class.__proto__.constructor["[[Scopes]]"] **Google工具显示了与Class有关的闭包,在内部可以看到pValue和hello() **显然Class在全局作用域中也不存在 **/ /*该类已经实现的内容有 **公有、私有属性和方法、原型方法 **至于公有方法,实例化之后在定义 **对于工程化来讲,私有属性和方法这么写维护会很困难 **可以将私有属性和方法都放到object中方便维护 **var pV = { pVal:‘‘, hello:()=>console.log(‘ok‘) } **/ /*类还有静态方法和静态属性 **通过Class.staticVal和Class.staticMethod来定义 **原型上也可以定义属性,该属性被所有的实例所共享 **原型上的属性只能是引用(array和object)时,才能被存储 **/ var Class01 = ( function () { /*代码略*/ Class.prototype.nDos = { name: ‘nDos‘, sayHello: hello }; Class.prototype.noChangeVal = ‘实例拿我没办法‘; Class.staticVal = ‘Class的静态属性‘; Class.staticMethod = function () { console.log( ‘Class的静态方法‘ ); }; return Class; } )(); var ins01 = new Class01( 1, 2 ), ins02 = new Class01( ‘a‘, ‘b‘ ); ins01.nDos.name = ‘ins01 say hello nDos‘; /*对于数组也是一样的*/ console.log( ‘ins02中的name值:‘ + ins02.nDos.name ); try { ins01.noChangeVal = ‘实例1改变原型属性值‘; console.log( ins01.noChangeVal ); console.log( ins02.noChangeVal ); ins01.prototype.noChangeVal = ‘我就是想改变它‘; /*报错*/ } catch ( e ) { console.Error( e ); } /*Class的静态属性和方法都可以在Class01上找到 **原型上的属性通过上述的了解也学习过了 **是时候来一波总结 **并不是总结学习了什么,而是思考可以用来干什么 **以及其中可能存在的缺陷 **/ /*总结 **1、静态属性和原型属性,都可以用来储存实例化次数 ** 同样也可以用来储存每个实例的引用 此处建议将实例化次数和对实例的引用都储存在静态属性中 因为原型属性在编程过程中 并不容易分清楚它究竟是原型属性还是实例属性 **2、显然,function也是可以被当作函数而执行 ** 避免这种情况,需要加入防御性代码 if ( this.constructor.name !== ‘Class‘ ) { throw new Error( ‘类只能被实例化‘ ); } ** 就算这么做了,还是可以绕过去,比如 function fakeClass () { Class01.call(this, ‘1‘, ‘2‘); } fakeClass.prototype = Class01.prototype; var ins = new fakeClass(); ** 当然这也可以算是继承的一种 **3、为避免2中出现的情况,就是加入以下代码 if ( !new.target || new.target.name !== ‘Class‘ ) { throw new Error( ‘类只能被实例化‘ ); } **4、只有函数才能被实例化 实例化之后得到的变量是实例并不是函数或者说是object **5、function类实例化过程,实际上是函数体的执行过程 这个执行过程也就是初始化的过程 在这种过程当中可以做相当多的事情 上述的防御性代码 实例(this)的传递与储存 使用Object.create给this扩充功能(Mixin) 加入钩子函数,让类消费者自行决定如何实例化 **6、function类一般不会有return当然也可以存在 这也是实例化的变化所在 通过不同的钩子从而决定返回什么样的实例 希望下一篇能学懂并讲解这种方式 这种实例化方式与多态继承等也有关系 **/ /*function与类的学习希望对你有帮助 **下篇文章介绍Class(ES2015新语法)与类 **最后附上最终的Class源代码 **/ var Class01 = ( function () { var pValue = ‘‘; /*实例无法直接读写的属性*/ function hello () { console.log( ‘欢迎来到nDos的博客‘ ); } function Class( val, pVal ) { if ( this.constructor.name !== ‘Class‘ ) { throw new Error( ‘类只能被实例化‘ ); } if ( !new.target || new.target.name !== ‘Class‘ ) { throw new Error( ‘类只能被实例化‘ ); } this.val = val; /*实例可直接读写的属性*/ pValue = pVal; } Class.prototype.printVal = function () { var _this = this; /*尽管此处并不会出现this丢失的情况,但推荐总是这么做*/ console.log( _this.val ); }; Class.prototype.getpVal = function () { console.log( pValue ); return pValue; }; Class.prototype.setpVal = function ( v ) { pValue = v; return this; }; Class.prototype.sayHello = function () { hello(); return this; }; Class.prototype.nDos = { name: ‘nDos‘, sayHello: hello }; Class.prototype.noChangeVal = ‘实例拿我没办法‘; Class.staticVal = ‘Class的静态属性‘; Class.staticMethod = function () { console.log( ‘Class的静态方法‘ ); }; return Class; } )(); var ins01 = new Class01( 1, 2 ), ins02 = new Class01( ‘a‘, ‘b‘ ); ins01.nDos.name = ‘ins01 say hello nDos‘; /*对于数组也是一样的*/ console.log( ‘ins02中的name值:‘ + ins02.nDos.name ); try { ins01.noChangeVal = ‘实例1改变原型属性值‘; console.log( ins01.noChangeVal ); console.log( ins02.noChangeVal ); /* ins01.prototype.noChangeVal = ‘我就是想改变它‘; 报错*/ } catch ( e ) { console.error( e ); } function fakeClass() { Class01.call( this, ‘1‘, ‘2‘ ); } fakeClass.prototype = Class01.prototype; var ins = new fakeClass();
原文地址:https://www.cnblogs.com/ndos/p/8127571.html
时间: 2024-11-07 01:14:59