本人JS新手,最近项目很多复用的函数,不知道如何处理,只是简单的写成了函数,放在一个JS文件里面,但这样肯定有问题,比如函数重名。
看了一下网上的封装方式,感觉花样太多,不知道有什么常用通用的处理方式呢?我主要就是封装常用函数,比如数组处理、时间处理这种。
网上看了主要是两种方式(或者这根本是两个不同东西 -。-,大概这个意思吧):
1.创建js库,大多采用以下模版
<span style="font-family:FangSong_GB2312;">//JavaScript库模板代码
(function (){
function $(){
alert("被调用到喽!");
/*alert()是JavaScript脚本语言中窗口window对象的一个常用方法;
其主要用法就是在你自己定义了一定的函数以后,通过执行相应的操作,
所弹出对话框的语言。并且alert对话框通常用于一些对用户的提示信息。*/
}
//注册命名空间 'myNameSpace' 到window对象上
window['myNameSpace'] = {}
//把$函数注册到 'myNameSpace'命名空间中
window['myNameSpace']['$']=$;
})();
</span>
看起来有点复杂,另外,搞不懂为什么非要放在span里面,这喵的不是js嘛。
2.js面向对象的封装
采用对象的方式,使用时实例化对象即可。
搜了半天,没看到什么通用的解决办法,方式太多不知道怎样处理比较合理呢。
个人给一点建议不要再搞什么命名空间了。已经进入 2016 年啦,JavaScript 现在的模块化机制已经相当成熟,学习一下如何用 ES2015 Module 来封装模块,实际用的时候可以考虑各种模块转换编译器/加载器,浏览器的兼容性不会是问题。比如说 babel/webpack/jspm 等等都可以……这样吧,我简单帮你梳理一下:
-
首先学会怎么用 ES2015 来编写/封装模块(node/npm,加上 babel 的入门知识)
记住,在编写封装模块时是不需要考虑兼容性的,后面有办法让你向后兼容(至少到 ES5)
学会发布它,比如说发布到 npm
学会如何引入模块到你的应用体系中去,如何加载/打包(用上 gulp/webpack/jspm 等等,取决于项目)
这是一个完整的生态系统,封装不只是要学习代码怎么写,更要知道如何维护,如何应用,否则封装的没有普适性就没有价值。
至于具体到新的模块语法怎么写,我这里有之前回答别人(不在 SF)的一部分内容供你参考——别想太复杂,可以很简单的:
我不喜欢用 Class
,不管是过去的构造器模式还是现在的新语法。不是因为对任何编程范式有偏见,本来 JavaScript 就不是 Class Based OO 语言,硬生生的去模仿就是会觉得别扭罢了。和 Java、C# 等语言不同,class
不是必需品,这也就意味着你完全可以不用。然而奇怪的是使用 JavaScript 的人很多却是不用 class
不行,这是不是对这门语言存在很大的误解呢?
更重要的是在实践中我们会发现使用其他的模式——比如工厂函数(Factory Functions)要远比 class
简洁、灵活,用它来替代 class
几乎总能得到更好的结果。
简单工厂函数
// person.js
export default _ => {
return {
greet() {
console.log('Hello world!')
}
}
}
用起来和一个 Class 几乎一模一样——除了不需要用 new
,这不算坏处吧?
import Person from './person'
const nightire = Person()
nightire.greet() // "Hello world!"
依赖注入
console.log
限制了 greet
方法的行为,为了不局限问候的方式,可以使用依赖注入——这是解耦的一种简便易行的方法。即使在现实中很多时候看不出需要依赖注入的迹象,我们也应该有意识的这么做。在定义一种“类型”的时候对外界知道的越少越好(于是就更容易复用、扩展、组合……)。
// person.js
export default ioStream => {
return {
greet() {
ioStream.send('Hello world!')
}
}
}
比如说我们可以把 console
封装一下,让系统内所有的 ioStream
都具有统一的接口,然后就可以直接使用:
import Person from './person'
import ConsoleStream from 'console-stream'
const nightire = Person(ConsoleStream())
nightire.greet() // "Hello world!"
不用 Mocking 的单元测试
这个是顺带一提的事情,因为我注意到不懂得处理依赖注入(或者说更高层次上的解耦概念)的人通常都会把单元测试写得无比蛋疼……实际上,对象字面量在很多时候胜过一切构造模式:
import test from 'ava'
import Person from './person'
test(`a person can send message to io stream`, t => {
const ioStream = {
send(anything) {
t.same(anything, 'Hello world!');
}
}
const anyone = Person(ioStream)
anyone.greet()
})
封装
其实私有成员可以变得很自然很自然,闭包一样在用,只是不那么扎眼了:
// person.js
export default ioStream => {
let _message = 'Hello world!'
return {
greet() {
ioStream.send('Hello world!')
},
get message() {
return _message
},
set message(message) {
_message = message
}
}
}
用法就不写了,和之前没什么区别。getter/setter 也不是必须的,看接口设计需求了。
组合
这才是对 OO 来说最重要的(相较于怎么定义/创建对象来说),总的来说组合总是要优于继承,工厂模式搞起来尤其轻松。
比方说我们已经有了一个动作“类”:
// action.js
export default ioStream => {
return {
wave() {
ioStream.send('(Waving Hands...)')
}
}
}
那么与 Person
的组合可以这样:
import Person from './person'
import Action from './action'
import ConsoleStream from 'console-stream'
const _console = ConsoleStream()
const nightire = Object.assign({}, Person(_console), Action(_console))
nightire.message = 'Farewell, my friend!'
nightire.wave() // "(Waving Hands...)"
nightire.greet() // "Farewell, my friend!"
事前绑定的方法引用
这是我觉得最好的一个优点。由于 this
在 JavaScript 中是在运行时动态绑定的,如果使用你代码的人不理解这一点,那么他们就会犯错误(而且会指责是你写的不对……)。有些人是因为不理解 this
而不敢用,有些人则是为了迁就前者而干脆不去用,架构师会比较容易体会这类情况。
这是典型的容易犯错的例子:
// stepper.js
export default class Stepper {
constructor(offset) {
this.offset = offset
}
add(amount) {
return amount + this.offset
}
}
// main.js
import Stepper from './stepper'
const stepper = new Stepper(1)
[1, 2, 3].map(stepper.add.bind(stepper))
容易犯错的地方就是最后一行,如果不加 .bind(stepper)
的话最终 this
的指向就是错误的。但往往使用者并不理解这一点,反正看到你的文档就知道这个能加上初始化传入的 offset
就是了,除非你不厌其烦的在文档里强调:“注意上下文的变化,如有必要请用 bind()
明确 this
的指向“……啊,说不定你还得培训一下让大家都知道如有“必要”的确切范围。
然而你也可以这样来重写一下:
// stepper.js
export default offset => {
return {
add(amount) {
return amount + offset
}
}
}
// main.js
import Stepper from './stepper'
const stepper = Stepper(1)
[1, 2, 3].map(stepper.add) // [2, 3, 4]
于是无论是具体实现还是接口定义都能保持简洁一致。
我觉得最好写一个私有的npm木块
你不就需要一个命名空间吗。对象就是天然的命名空间。
nightire的回复比较有参考性,学习了
1)你的确定你的span这个写法能运行?你测试过了么?
2)要避免重复,并且封装自己的函数,那么就是用命名空间
`其实就是一个对象,最大程度避免函数重名及被覆盖问题
(function(global){
var myfun1=function(){
console.log('myfun1');
};
var myfun2=function(){
console.log('myfun2');
};
//设置你的命名空间
var mypackageName="com.mydomain.utils";
var packageArray=mypackageName.split(".");
var finalObj=packageArray.reduce(function(prev,current){
return prev[current]||(prev[current]={});
}, global);
//将你的函数绑定到命名空间上
finalObj.myfun1= myfun1;
finalObj.myfun2= myfun2;
}(window));
com.mydomain.utils.myfun1();//输出 myfun1
com.mydomain.utils.myfun2();//输出 myfun2
http://www.ruanyifeng.com/blog/2012/10/javascript_module.html,先学习下js模块化。
命名空间或者export