如何封装常用的JS方法?

本人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面向对象的封装

采用对象的方式,使用时实例化对象即可。

搜了半天,没看到什么通用的解决办法,方式太多不知道怎样处理比较合理呢。

阅读 20k
7 个回答

个人给一点建议不要再搞什么命名空间了。已经进入 2016 年啦,JavaScript 现在的模块化机制已经相当成熟,学习一下如何用 ES2015 Module 来封装模块,实际用的时候可以考虑各种模块转换编译器/加载器,浏览器的兼容性不会是问题。比如说 babel/webpack/jspm 等等都可以……这样吧,我简单帮你梳理一下:

  1. 首先学会怎么用 ES2015 来编写/封装模块(node/npm,加上 babel 的入门知识)

    记住,在编写封装模块时是不需要考虑兼容性的,后面有办法让你向后兼容(至少到 ES5)
    
  2. 学会发布它,比如说发布到 npm

  3. 学会如何引入模块到你的应用体系中去,如何加载/打包(用上 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]

于是无论是具体实现还是接口定义都能保持简洁一致。

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

你不就需要一个命名空间吗。对象就是天然的命名空间。

我觉得最好写一个私有的npm木块

nightire的回复比较有参考性,学习了

命名空间或者export

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏