[JS] Topic - define "class" by tricky methods
Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class
(类)。
那么,如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象,我们应该怎么做呢?
程序员们做了很多探索,研究如何用Javascript模拟"类"。本文总结了Javascript定义"类"的三种方法,讨论了每种方法的特点,着重介绍了我眼中的最佳方法。
一、构造函数法
From: Javascript 面向对象编程系列(一):封装
一、 生成实例对象的原始模式
变量赋值的方法。
var Cat = {
name: '',
color: ''
} Cat
{name: "", color: ""} Cat.height = 10 //直接给对象添加了属性
10
Cat
{name: "", color: "", height: 10}
实例(下)与原型(上)之间,没有任何办法,可以看出有什么联系。
var cat1 = {}; // 创建一个空对象 cat1.name = "大毛"; // 按照原型对象的属性赋值 cat1.color = "黄色"; var cat2 = {}; cat2.name = "二毛"; cat2.color = "黑色";
二、 原始模式的改进
函数返回的方法。
function Cat(name,color) { return { name:name, color:color } }
然后生成实例对象,就等于是在调用函数:
var cat1 = Cat("大毛","黄色"); var cat2 = Cat("二毛","黑色");
cat1和cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例。
三、 构造函数模式【官方提供了暂时的方案】
Javascript提供了一个构造函数(Constructor)模式:
“一个普通函数,但是内部使用了this变量”。
function Cat(name,color){ this.name=name; this.color=color; }
这时cat1
和cat2
会自动含有一个constructor
属性,指向它们的构造函数。
var cat1 = new Cat("大毛","黄色"); var cat2 = new Cat("二毛","黑色"); // cat1内的constructor指向构造函数Cat.
alert(cat1.constructor == Cat); //true alert(cat2.constructor == Cat); //true
Javascript还提供了一个instanceof
运算符,验证原型对象与实例对象之间的关系。
alert(cat1 instanceof Cat); //true alert(cat2 instanceof Cat); //true
四、构造函数模式的问题
五、 Prototype模式
function Cat(name,color){ this.name = name; this.color = color; } Cat.prototype.type = "猫科动物"; Cat.prototype.eat = function(){alert("吃老鼠")};
也就是prototype类型的属性是shared,不用重复保存,节省了内存空间。
六、 Prototype模式的验证方法
6.1 isPrototypeOf()
对象与实例之间的关系。
alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true
6.2 hasOwnProperty()
每个实例对象都有一个hasOwnProperty()
方法,用来判断某一个属性到底是本地属性,还是继承自prototype
对象的属性。
alert(cat1.hasOwnProperty("name")); // true
alert(cat1.hasOwnProperty("type")); // false
6.3 in运算符
in
运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
alert("name" in cat1); // true
alert("type" in cat1); // true
in
运算符还可以用来遍历某个对象的所有属性。
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
二、Object.create()法
为了解决"构造函数法"的缺点,更方便地生成对象,Javascript的国际标准ECMAScript第五版(目前通行的是第三版),提出了一个新的方法Object.create()。
用这个方法,"类"就是一个对象,不是函数。
var Cat = {
name: "大毛",
makeSound: function(){ alert("喵喵喵"); }
};
然后,直接用Object.create()生成实例,不需要用到new。
var cat1 = Object.create(Cat);
alert(cat1.name); // 大毛
cat1.makeSound(); // 喵喵喵
目前,各大浏览器的最新版本(包括IE9)都部署了这个方法。如果遇到老式浏览器,可以用下面的代码自行部署。
if (!Object.create) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
这种方法比"构造函数法"简单,但是不能实现私有属性和私有方法,实例对象之间也不能共享数据,对"类"的模拟不够全面。
三、极简主义法
荷兰程序员Gabor de Mooij提出了一种比Object.create()更好的新方法,他称这种方法为"极简主义法"(minimalist approach)。这也是我推荐的方法。
3.1 封装
这种方法不使用this和prototype,代码部署起来非常简单,这大概也是它被叫做"极简主义法"的原因。
首先,它也是用一个对象模拟"类"。在这个类里面,定义一个构造函数createNew(),用来生成实例。
var Cat = {
createNew: function() {
// some code here
}
};
然后,在createNew()里面,定义一个实例对象,把这个实例对象作为返回值。
var Cat = {
createNew: function() { // 属性的值是个方法!
var cat = {};
cat.name = "大毛";
cat.makeSound = function() { alert("喵喵喵"); };
return cat;
}
};
使用的时候,调用createNew()方法,就可以得到实例对象。
var cat1 = Cat.createNew();
cat1.makeSound(); // 喵喵喵
这种方法的好处是,容易理解,结构清晰优雅,符合传统的"面向对象编程"的构造,因此可以方便地部署下面的特性。
3.2 继承
让一个类继承另一个类,实现起来很方便。只要在前者的createNew()方法中,调用后者的createNew()方法即可。
先定义一个Animal类。
var Animal = {
createNew: function(){
var animal = {};
animal.sleep = function(){ alert("睡懒觉"); };
return animal;
}
};
然后,在Cat的createNew()方法中,调用Animal的createNew()方法。
var Cat = {
createNew: function(){
var cat = Animal.createNew();
cat.name = "大毛";
cat.makeSound = function(){ alert("喵喵喵"); };
return cat;
}
};
这样得到的Cat实例,就会同时继承Cat类和Animal类。
var cat1 = Cat.createNew();
cat1.sleep(); // 睡懒觉
3.3 私有属性和私有方法
在createNew()方法中,只要不是定义在cat对象上的方法和属性,都是私有的。
var Cat = {
createNew: function() {
var cat = {};
var sound = "喵喵喵";
cat.makeSound = function() { alert(sound); };
return cat;
}
};
上例的内部变量sound,外部无法读取,只有通过cat的公有方法makeSound()来读取。
var cat1 = Cat.createNew();
alert(cat1.sound); // undefined
3.4 数据共享
有时候,我们需要所有实例对象,能够读写同一项内部数据。这个时候,只要把这个内部数据,封装在类对象的里面、createNew()方法的外面即可。
var Cat = {
sound : "喵喵喵",
createNew: function(){
var cat = {};
cat.makeSound = function(){ alert(Cat.sound); };
cat.changeSound = function(x){ Cat.sound = x; };
return cat;
}
};
然后,生成两个实例对象:
var cat1 = Cat.createNew();
var cat2 = Cat.createNew();
cat1.makeSound(); // 喵喵喵
这时,如果有一个实例对象,修改了共享的数据,另一个实例对象也会受到影响。
cat2.changeSound("啦啦啦");
cat1.makeSound(); // 啦啦啦
[JS] Topic - define "class" by tricky methods的更多相关文章
- [JS] Topic - why "strict mode" here
Ref: Javascript 严格模式详解 使得Javascript在更严格的条件下运行: - 消除Javascript语法的一些不合理.不严谨之处,减少一些怪异行为; - 消除代码运行的一些不安全 ...
- vue.js初探:计算属性和methods
在vue.js中,计算属性和methods方法的函数相同时,两者的最终执行结果都是相同的.然而不同的是,计算属性是基于它的依赖缓存.计算属性只有在它的相关依赖发生改变时才会重新取值.这就意味着只要 m ...
- 阅读zepto.js的core中的Core methods
学习zepto.js,參考资料:http://www.zeptojs.cn/ 跟jQuery一样.其选择符号也是$; 首先接触的是 $.() 选择 $(selector, [context]) ⇒ ...
- [JS] Topic - variable and function hoisting
Ref: 深入理解js的变量提升和函数提升 一.变量提升 简直就是es5的遗毒! console.log(global); // undefined 竟然能打印?因为变量提升,下一行就有定义 var ...
- [JS] Topic - Object.create vs new
故事背景 Ref: 你不知道的javascript之Object.create 和new区别 var Base = function () {} (1) var o1 = new Base(); (2 ...
- [JS] Topic - hijack this by "apply" and "call"
Ref: 详解js中的apply与call的用法 call 和 apply二者的作用完全一样,只是接受参数的方式不太一样. 参数形式: Function.apply(obj,args) call方法与 ...
- [JS] Topic - this is ”closure“
Ref: 为什么要用闭包? 背景 闭包是自带运行环境的函数 发哥是自带背景音乐的男人~ 就是有权访问另一个函数作用域的变量的函数. 函数式编程的闭包,就是函数的调味包.方便用户调用函数.不必为了维护繁 ...
- [Code::Blocks] Install wxWidgets & openCV
The open source, cross platform, free C++ IDE. Code::Blocks is a free C++ IDE built to meet the most ...
- 本人SW知识体系导航 - Programming menu
将感悟心得记于此,重启程序员模式. js, py, c++, java, php 融汇之全栈系列 [Full-stack] 快速上手开发 - React [Full-stack] 状态管理技巧 - R ...
随机推荐
- java中哪些数值不能被初始化
main方法中的变量不能被初始化 final修饰的变量不能被初始化·
- 几种Unity运行平台的判断
这里就介绍几种常见的,也是便于使用的几种平台判断的方法. 1.先说第一种,也是我用的顺手的一个.利用RuntimePlatform判断,API上的解释是[The platform applicatio ...
- vsftp安装与配置for Linux
以Centos6中使用为例 1.安装 yum -y install vsftpd 2.基本配置 vi /etc/vsftpd/vsftpd.conf 匿名访问和切换根目录都会给服务器带来安全风险,我们 ...
- Reveal:分析iOS UI的利器
转:http://security.ios-wiki.com/issue-3-4/ Reveal简介 Reveal是分析iOS应用UI的利器: Reveal能够在运行时调试和修改iOS应用程序.它能连 ...
- 删除weblogic应用缓存,weblogic 缓存的删除方法
security (注意:security目录下为安全认证文件,切不可删除!) 正常情况下只需删除tmp目录下缓存文件(不同的工程在tmp下面有不同的路径,如果不清楚的话可以一起删除). 个别情况下会 ...
- App架构师实践指南四之性能优化一
App架构师实践指南四之性能优化一 1.性能维度常见用来衡量App性能的维度如图9-1所示.其中,性能指标包括电池(电量/温度).流量(上行流量/下行流量等).CPU(平均/最大/最小).内存 ...
- Java时间串获取(格式:yyyyMMddHHmmss)
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");Calendar calendar = Calendar.getI ...
- Redis 常用监控信息命令总结
查看启动到当前处理命令总数: # redis-cli info stats | grep total_commands total_commands_processed:23693286991 查看每 ...
- Ansible 使用普通用户远程执行playbook
设置ansible使用普通用户jsxge远程连接执行playbook 1. ansible控制端创建普通用户jsxgecd /homeuseradd jsxgechown -R jsxge.wheel ...
- Spring Boot 2.0 整合 FreeMarker 模板引擎
本篇博文将和大家一起使用Spring Boot 2.0 和FreeMarker 模板引擎整合实战. 1. 创建新的项目 2. 填写项目配置信息 3. 勾选web 模块 4. 勾选freemarker模 ...