前言
虽然现在已经是 ES6 的时代, 但是, 还是有必要了解下 ES5 是怎么写一个类的.
本文详述 JavaScript 面向对象编程中的类写法, 并分步骤讲述如何写出优雅的类.
一, 例子
例子为一个轻提示组件 Toast.
需要实现的功能:
on 方法, 显示提示
off 方法, 隐藏提示
init 方法, 初始化提示语
- function Toast(option){
- this.prompt = '';
- this.elem = null;
- this.init(option);
- }
- Toast.prototype = {
- // 构造器
- constructor: Toast,
- // 初始化方法
- init: function(option){
- this.prompt = option.prompt || '';
- this.render();
- this.bindEvent();
- },
- // 显示
- show: function(){
- this.changeStyle(this.elem, 'display', 'block');
- },
- // 隐藏
- hide: function(){
- this.changeStyle(this.elem, 'display', 'none');
- },
- // 画出 dom
- render: function(){
- var html = '';
- this.elem = document.createElement('div');
- this.changeStyle(this.elem, 'display', 'none');
- HTML += '<a class="J-close"href="javascript:;">x</a>'
- HTML += '<p>'+ this.prompt +'</p>';
- this.elem.innerHTML = HTML;
- return document.body.appendChild(this.elem);
- },
- // 绑定事件
- bindEvent: function(){
- var self = this;
- this.addEvent(this.elem, 'click', function(e){
- if(e.target.className.indexOf('J-close') != -1){
- console.log('close Toast!');
- self.hide();
- }
- });
- },
- // 添加事件方法
- addEvent: function(node, name, fn){
- var self = this;
- node.addEventListener(name, function(){
- fn.apply(self, Array.prototype.slice.call(arguments));
- }, false);
- },
- // 改变样式
- changeStyle: function(node, key, value){
- node.style[key] = value;
- }
- };
- var T = new Toast({prompt:'I\'m Toast!'});
- T.show();
二, 类的构成
JavaScript 的类, 是用函数对象来实现.
类的实例化形式如下:
var T = new Toast();
其中的重点, 就是 Function 的编写.
类分为两部分: constructor+prototype. 也即构造器 + 原型.
2.1 构造器
构造器从直观上来理解, 就是写在函数内部的代码.
从 Toast 例子上看, 构造器就是以下部分:
- function Toast(option){
- this.prompt = '';
- this.elem = null;
- this.init(option);
- }
这里的 this, 指向的是实例化的类.
每次通过 new Toast()的方式进行实例化, 构造器都会执行一遍.
2.2 原型
原型上的方法和变量的声明, 都是通过 Toast.prototype.* 的方式.
那么在原型上普通的写法如下:
- Toast.prototype.hide = function(){
- /*code*/
- }
- Toast.prototype.myValue = 1;
但是, 该写法不好的地方: 就是每次都要写前半部分 Toast.prorotype, 略显累赘.
在代码压缩优化方面也不友好, 无法做到最佳的压缩.
改进的方式如下:
- Toast.prorotype = {
- constructor: Toast,
- hide: function(){/*code*/},
- myValue: 1
- }
这里的优化, 是把原型指向一个新的空对象{}.
带来的好处, 就是可以用 {key:value} 的方式写原型上的方法和变量.
但是, 这种方式会改变原型上构造器 prototype.constructor 的指向.
如果不重新显式声明 constructor 的指向, Toast.constructor.prototype.constructor 的会隐式被指向 Object. 而正确的指向, 应该是 Toast.
虽然通过 new 实例化没有出现异常, 但是在类继承方面, constructor 的指向异常, 会产生不正确的继承判断结果. 这是我们不希望看到的.
所以, 需要修正 constructor.
2.3 构造器和原型的不同
原型上的方法和变量, 是该类所有实例化对象共享的. 也就是说, 只有一份.
而构造器内的代码块, 则是每个实例化对象单独占有. 不管是否用 this.** 方式, 还是私有变量的方式, 都是独占的.
所以, 在写一个类的时候, 需要考虑该新增属性是共享的, 还是独占的. 以此, 决定在构造器还是原型上进行声明.
前端全栈交学习流圈: 866109386. 欢迎大家进入交流吹水.
三, 代码规范
类的命名规范, 业界有不成文的规定, 就是首字母大写.
原型上的私有方法, 默认以下划线开始. 这种只是团队合作方面有 review 代码的好处, 实际上还是暴露出来的方法.
- function Toast(option){
- if(!(this instanceof Toast)){
- return new Toast(option);
- }
- this.prompt = '';
- this.elem = null;
- this.init(option);
- }
- function Toast(option){
- if(!(this instanceof Toast)){
- throw new Error('Toast instantiation error');
- }
- this.prompt = '';
- this.elem = null;
- this.init(option);
- }
来源: http://www.qdfuns.com/article/51070/5aa26397ab4b614ad0b2599f5df44237.html