`
liuguofeng
  • 浏览: 456083 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

所谓的javascript高级技巧

 
阅读更多

Js学的也差不多了,该是来总结一下Js中一些比较高级的智慧结晶了。基于Js的动态性、对象都是易变的、函数是第一对象等等其他语言所不包含的特性,可以在使用Js的时候创造出更高效、组织性更好的代码。下面提到的一些概念,是不是很熟悉:

分支、惰性实例化、惰性载入函数、单例的两种模式、享元类、函数绑定(纠正函数一个执行上下文)、函数curry化、高级定时器、保护上下文的构造函数、函数节流、自定义事件……

js中的继承、原型、构造函数这些都是老生常谈的了。但是对于构造函数式继承和原型式继承的优缺点,还是有必要了解一下的。原型式继承的主要优点就是共享方法和属性,使得从原型对象中继承出来的子对象都可以在内存中共享全部的方法和属性,这如果是在大型的继承链中将会大大的改善性能和减少内存的使用量。

 

接下来看看上面所罗列的每一个所谓的“高级”技巧的具体细节咧:

  1. 惰性实例化
    惰性实例化所要解决的问题是这样的:避免了在页面中js初始化执行的时候就实例化了类,如果在页面中没有使用到这个实例化的对象,那么这就造成了一定的内存的浪费和性能的消耗,那么如果将一些类的实例化推迟到需要使用它的时候才开始去实例化,那么这就避免了刚才说的问题,做到了“按需供应”,简单代码示例如下:
    var myNamespace = function(){
    var Configure = function(){
    var privateName = "someone's name";
    var privateReturnName = function(){
    return privateName;
    }
    var privateSetName = function(name){
    privateName = name;
    }
    //返回单例对象
    return {
    setName:function(name){
    privateSetName(name);
    },
    getName:function(){
    return privateReturnName();
    }
    }
    }
    //储存configure的实例
    var instance;
    return {
    getInstance:function(){
    if(!instance){
    instance = Configure();
    }
    return instance;
    }
    }
    }();
    //使用方法上就需要getInstance这个函数作为中间量了:
    myNamespace.getInstance().getName();

    上面的就是简单的惰性实例化的示例,但是有一点缺点就是需要使用中间量来调用内部的Configure函数所返回的对象的方法(当然也可以使用变量来储存myNamespace.getInstance()返回的实例对象)。将上面的代码稍微修改一下,就可以只用比较得体的方法来使用内部的方法和属性:

    //惰性实例化的变体
    var myNamespace2 = function(){
    var Configure = function(){
    var privateName = "someone's name";
    var privateReturnName = function(){
    return privateName;
    }
    var privateSetName = function(name){
    privateName = name;
    }
    //返回单例对象
    return {
    setName:function(name){
    privateSetName(name);
    },
    getName:function(){
    return privateReturnName();
    }
    }
    }
    //储存configure的实例
    var instance;
    return {
    init:function(){
    //如果不存在实例,就创建单例实例
    if(!instance){
    instance = Configure();
    }
    //将Configure创建的单例
    for(var key in instance){
    if(instance.hasOwnProperty(key)){
    this[key]=instance[key];
    }
    }
    this.init = null;
    return this;
    }
    }
    }();
    //使用方式:
    myNamespace2.init();
    myNamespace2.getName();

    上面修改了自执行函数返回的对象的代码,在获取Configure函数返回的对象的时候,将该对象的方法赋给myNamespace2,这样,调用方式就发生了一点改变了。

  2. 分支
    分支技术解决的一个问题是处理浏览器之间兼容性的重复判断的问题。普通解决浏览器之间的兼容性的方式是使用if逻辑来进行特性检测或者能力检测,来实现根据浏览器不同的实现来实现功能上的兼容,但问题是,没执行一次代码,可能都需要进行一次浏览器兼容性方面的检测,这个是没有必要的,能否在代码初始化执行的时候就检测浏览器的兼容性,在之后的代码执行过程中,就无需再进行检测了呢?答案是有的,分支技术就可以解决这个问题(同样,惰性载入函数也可以实现Lazy Definied,这个在后面将会讲到),下面以声明一个XMLHttpRequest实例对象为例子:
    //分支
    var XHR= function(){
    var standard = {
    createXHR : function(){
    return new XMLHttpRequest();
    }
    }
    var newActionXObject = {
    createXHR : function(){
    return new ActionXObject("Msxml2.XMLHTTP");
    }
    }
    var oldActionXObject = {
    createXHR : function(){
    return new ActionXObject("Microsoft.XMLHTTP");
    }
    }
    if(standard.createXHR()){
    return standard;
    }else{
    try{
    newActionXObject.createXHR();
    return newActionXObject;
    }catch(o){
    oldActionXObject.createXHR();
    return oldActionXObject;
    }
    }
    }();

    从上面的例子可以看出,分支的原理就是:声明几个不同名称的对象,但是给这些对象都声明一个名称相同的方法(这个就是关键),并给这些来自于不同的对象但是拥有相同的方法进行浏览器之间各自的实现,接着就开始进行一次浏览器检测,并经过浏览器检测的结果来决定返回哪一个对象,这样不论返回的是哪一个对象,最后名称相同的方法都作为了对外一致的接口。

    这个是在Javascript运行期期间进行动态检测,并将检测的结果返回赋值给其他的对象,并提供相同的接口,这样储存的对象就可以使用名称相同的接口了。其实,惰性载入函数跟分支在原理是非常相近的,只是在代码实现方面有差异而已。

  3. 惰性载入函数
    惰性载入函数就是英文中传说的“Lazy Defined”,它的主要解决的问题也是为了处理兼容性。原理跟分支类似,下面是简单的代码示例:
    var addEvent = function(el,type,handle){
    addEvent = el.addEventListener ? function(el,type,handle){
    el.addEventListener(type,handle,false);
    }:function(el,type,handle){
    el.attachEvent("on"+type,handle);
    };
    //在第一次执行addEvent函数时,修改了addEvent函数之后,必须执行一次。
    addEvent(el,type,handle);
    }

    从代码上看,惰性载入函数也是在函数内部改变自身的一种方式,这样之后,当重复执行的时候,就不会再进行兼容性方面的检测了。

  4. 单例的两种模式
    单例模式是家喻户晓的了,也是当前最流行的一种编写方式,注明的模块模式的编写方式也是从这个思想中衍生出来的。单例模式有两种方式:一种是所谓的“门户大开型”,另外一种就是使用闭包来创建私有属性和私有方法的方式。第一种方式跟构造函数的“门户大开型”是一个样的,声明的方法和属性对外都是开放的,可以通过实例来调用。但是使用闭包来实现的单例模式,可以在一个“封闭”的作用域内声明一些不为外部所调用的私有属性和私有方法,而且还有一个很主要的功能,就是私有属性可以作为一个“数据存储器”,在闭包内声明的方法都可以访问这些私有属性,但是外部不可访问。那么重复添加、修改、删除这些私有属性所储存的数据是安全的,不受外部其他程序的影响。
  5. 享元类
    顾名思义,“享”、“元”。就是共享通用的方法和属性,将差异比较大的类中相同功能的方法集中到一个类中声明,这样需要这些方法的类就可以直接从享元类中进行扩展,这样使得这样通用的方法只需要声明一遍就行了。这个从代码的大小、质量来说还是有一定的效益的。
  6. 函数绑定
    函数绑定就是为了纠正函数的执行上下文,特别是函数中带有this关键字的时候,这点尤其显得重要,稍微不小心,使得函数的执行上下文发生了跟预期的不同的改变,导致了代码执行上的错误(有时候也不会出现错误,这样调试起来,会很变态)。对于这个问题,bind函数是再熟悉不过的了,bind函数的功能就是提供一个可选的执行上下文传递给函数,并且在bind函数内部返回一个函数,来纠正在函数调用上出现的执行上下文发生的变化。最容易出现的错误就是回调函数和事件处理程序一起使用了,下面是摘自《Javascript高级程序设计第二版》的一个示例:
    var handler = {
    message:"Event handler",
    handlerClick:function(e){
    alert(this.message);
    }
    }
    var btn = document.getElementById("my-btn");
    //这句就造成了回调函数执行上下文的改变了
    EventUtil.addHandler(btn,"click",handler.handlerClick);

    解决的办法之一,就是纠正一下handler.handlerClick执行的上下文环境,改为:

    //这样运行的很好
    EventUtil.addHandler(btn,"click",function(e){
    handler.handlerClick(e);
    });

    上面就很好的纠正了回调函数的执行上下文了。而且,也可以使用传说中的bind函数来解决:

    var bind = function(fn,context){
    return function(){
    return fn.apply(context || this,arguments);
    }
    }
    EventUtil.addHandler(btn,"click",bind(handler.handlerClick));// So Good!
  7. 函数curry化
    函数curry化的主要功能就是提供了强大的动态函数创建的功能。通过调用另一个函数并为它传入要curry的函数和必要的参数。说白点就是利用已有的函数,再创建一个动态的函数,该动态的函数内部还是通过该已有的函数来发生作用,只是传入更多的参数来简化函数的参数方面的调用。具体示例:
    //curry function
    function curry(fn){
    var args = [].slice.call(arguments,1); //这个就相当于一个存储器了。
    return function(){
    return fn.apply(null,args.concat([].slice.call(arguments,0)));
    }
    }
    //Usage:
    function add(num1,num2){
    return num1+num2;
    }
    var newAdd = curry(add,5);
    alert(newAdd(6));

    在curry函数的内部,私有变量args就相当于一个存储器,来暂时的存储在curry函数调用的时候所传递的参数值,这样跟后面的动态创建的函数调用的时候的参数合并,并执行,就得到了一样的效果了。

  8. 高级定时器
    提到定时器,无非就是利用setTimeout/setInterval了。但问题是定时器并不是相当于新开一个线程来执行js程序,也不会说是在指定的时间间隔内就会一定执行。指定的时间间隔表示何时将定时器的代码添加到浏览器的执行队列,而不是合适实际执行代码。对此,就有这样的一个问题了:如果代码执行时间超过了定时器指定的时间间隔,那么在指定的时间里代码还是加入的执行队列,但是并没有执行,这样就会造成了无意义的代码执行,这也是使用setInterval的弊端。为此,使用setTimeout才能更好的避免这个问题,在代码本身中执行完毕了,再通过setTimeout来重新设定定时器,把代码加入到执行队列。比如:
    setTimeout(function(){
    //many code here...
    setTimeout(arguments.callee,100); //Key
    },100);

    当然了,定时器还有很多其他的技巧和实际作用,看需求而定,更详细的解释可以查看《Javascript高级程序设计第二版》(第467页)。

  9. 保护上下文的构造函数
    这个主要是避免构造函数在没有使用new来实例化的时候,内部的this指向错误问题。通常没有使用new的话,this一般执行window去了,因此造成了执行错误,给代码带来了灾难。使用下面的方式就可以避免这个问题:
    function myClass(name,size){
    if(this instanceof myClass){ //Key,使用instanceof来检测当前实例是否是myClass的实例化对象
    this.name = name;
    this.size = size;
    }else{
    return new myClass(name,size);
    }
    }

    但是上面通过instanceof的方式,给继承造成了一定的困扰,因为子类并不是myClass的实例对象,所以会出现属性和方法无法被继承的方式。在说解决办法之前,先来了解一下instanceof操作符的原理:它首先会检测对象当前的原型是否指向右边的构造函数,如果找不到,就会往上一级的原型去查找,直到找到为止,并返回true,否则就返回false。

    基于上面的instanceof的原理,在继承的时候,就可以给子类的prototype原型赋于一个父类的实例化对象就行了,这样就可以在子类继承的时候绕过instanceof的检测。

  10. 函数节流

    函数节流函数节流解决的问题是一些代码(特别是事件)在无间断的执行,这严重的影响了浏览器的性能,再没有给它设定间断来执行的话,可能造成浏览器反应速度变慢或者直接就崩溃了。比如:resize事件、mousemove、mouseover、mouseout等等事件。

    这个时候,就可以加入定时器的功能了,将事件进行“节流”,即是:在事件触发的时候,设定一个定时器来执行事件处理程序,这样可以很大的程度上缓解浏览器的负担,又缓冲的余地去更新页面。具体的实例可以查看支付宝中部“导购场景”的导航:http://life.alipay.com/?src=life_alipay_index_big,以及当当网首页左边的导航栏:http://www.dangdang.com/等等,这些都是为了解决mouseover和mouseout移动过快的时候加大浏览器处理的负担,特别是在涉及到有Ajax调用,而且Ajax调用是么有缓存的情况下,给服务器也造成了很大的负担。为此,函数节流就派上用场了。比如简单的示例如下(出自本人写的:http://www.ilovejs.net/lab/tween/tweener_tab_modify.html):

    oTrigger.onmouseover=function(e){
    //如果上一个定时器还没有执行,则先清除掉定时器
    oContainer.autoTimeoutId && clearTimeout(oContainer.autoTimeoutId);
    e = e || window.event;
    var target = e.target || e.srcElement;
    if((/li$/i).test(target.nodeName)){
    oContainer.timeoutId = setTimeout(function(){
    addTweenForContainer(oContainer,oTrigger,target);
    },300);
    }
    }
  11. 自定义事件

    首先要说的是,这里并不是说自定义事件可以真的自定义跟mouseout、click等一样性质的“事件”。这里的自定义事件在执行的时候还是需要依赖已有的键盘、鼠标、HTML等事件来执行,或者又其他函数“触发”执行,这里的“触发”是指直接调用自定义事件中声明的某个接口方法,来轮询的执行全部相关的添加到自定义事件中的函数。

    自定义事件内部有一个“事件”存储器,根据添加的事件的类型的不同,来储存各类的事件执行函数,这样再出发这类事件的时候,就轮询执行添加到该类型下的函数。“自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件”来自《Javascript高级程序设计第二版》的解释。基于自定义事件的原理,可以想象自定义事件很多时候是用于“订阅—发布—接收”性质的功能。

文章写的有点多了,但是上面介绍的Javascript高级技巧还远不止这些,特别是在Ajax方面的一些模式和技巧都还没有介绍,何况是客户端和服务端结合的一些技巧(比如压缩、Minify、服务器“推技术”等等)。更多的有待以后了解了介绍一二。

在前端基本技术方面,Javascript、HTML、CSS等大家都是已经掌握的差不多了,但是利用这些已有的基本技术,能否创造出不一般的应用和模式呢?这个才是在掌握了基本的能力之后接下来需要掌握的,比如下面是本人在每天晚上睡觉之前所总结的几点:

  1. 学会重构代码的技术,有计划性的重构下自己之前所编写过的一些代码,加强自己掌控代码的能力。
  2. Code review。这里说的review,并不是指个人,而是对团队来说的,一个人编写的代码的想象空间有限,如果在自己编写代码完成之后,邀请其他团队内的伙伴来查看你的代码,及时发现问题以及提出更好的解决方案,这也不失为一种即时重构的方式,提高代码的质量。
  3. 编写具体的功能代码之前,首先设计代码、规划代码、组织代码的模式。
  4. 在代码的质量、性能、大小之间能作出合理的权衡。
  5. 编写阅读性良好、一目了然、扩展性、可维护性良好、重复利用的代码,也是一门艺术。
  6. 关注web前端的性能优化,包括Javascript、HTML、CSS、客户端、服务端、前端、后端等整体性的优化。
  7. 最后一点或许也是最重要的:善于总结。这点比上面的任何一点都来的重要,因为上面的每一点都是出自这点的积累。

上面我总结的几点,也是后期自己要着重提高的能力,当然了,在实际的编码方面,还有很多的东西还需要去挖掘和了解。继续革命吧,将互联网革命进行到底……

 

http://www.ilovejs.net/archives/1232

分享到:
评论

相关推荐

    JavaScript Novice to Ninja电子书附源码

    《JavaScript Novice to Ninja》是一本专为JavaScript初学者打造的深入学习指南,旨在帮助读者从零基础逐步成长为JavaScript编程的高手,也就是所谓的"忍者"。这本书详细讲解了JavaScript的核心概念、语法特性以及...

    JavaScript 网页开发实例教程(PDG)

    在实际开发中,JavaScript经常与CSS和HTML结合,形成所谓的"前端三剑客"。CSS用于样式化,HTML用于结构化,而JavaScript则负责网页的逻辑和交互。了解如何使用JavaScript操作CSS(如改变样式、动画效果)和HTML(如...

    javascript的函数 入门详解

    - 一个函数可以定义在另一个函数内部,这种情况下,内部函数可以访问外部函数的变量,形成闭包,这在管理私有变量和实现记忆化等高级技巧中非常有用。 了解JavaScript的函数机制对于深入学习和掌握JavaScript至关...

    4-advanced-JS:JavaScript Udemy类-高级主题:对象和函数

    在这个"4-advanced-JS" Udemy课程中,我们将探索JavaScript中的高级对象和函数概念,这对于提升编程技巧至关重要。 首先,我们来讨论JavaScript的对象。在JavaScript中,对象是一种数据结构,它可以存储多个属性和...

    Eloquent_JavaScript_Exercises

    通过这一系列的练习,你将有机会运用所学的 JavaScript 知识解决实际问题,进一步提升编程技巧。 首先,我们要了解 JavaScript 的基础,包括变量、数据类型、运算符以及流程控制语句。JavaScript 支持动态类型,这...

    JavaScript基础重点(必看)

    JavaScript是一种高级的、解释型的编程语言,它广泛应用于网页开发中,实现动态交互效果。作为Web开发的三大核心技术之一,JavaScript的重要性不言而喻。本篇关于JavaScript基础重点的内容,将会涉及ECMAScript的...

    javascript挑战

    总的来说,JavaScript挑战是一个全面提高JavaScript技能的有效途径,涵盖了语言基础、高级特性、DOM操作和现代Web开发技术等多个方面。积极参与这些挑战,你将在解决实际问题的过程中不断提升自己的编程能力,成为一...

    javascript-challenge

    通过解决这些挑战,你可以深入理解JavaScript的核心概念,包括变量、数据类型、控制流、函数、对象以及更高级的主题。 在JavaScript中,变量是存储值的容器,它们可以动态地改变数据类型,这得益于JavaScript的弱...

    简单了解JavaScript作用域

    立即调用的函数表达式(IIFE)是一种避免创建全局变量的技巧,常用于库和框架中,以保持代码的封装性。IIFE 将函数定义和执行结合在一起,确保函数内部的变量不会污染全局作用域: ```javascript (function() { ...

    贝塞尔曲线高阶匀速运动算法 HTML5/JS 实现

    贝塞尔曲线是一种在计算机图形学中广泛使用的数学工具,它能生成平滑、连续的曲线,常用于动画、游戏开发、界面设计等领域。...不断探索和实践,你将能够掌握更多关于贝塞尔曲线和动画实现的高级技巧。

    JavaScript_D.O.M._bubbeling

    此外,事件委托是一种利用冒泡机制的高级技巧。通过在父元素上设置一个事件监听器,可以捕获到该父元素所有子元素的事件。这种方式不仅可以减少内存消耗(因为只需要一个监听器),还可以方便地处理动态添加的元素,...

    MYSQL数据库分页查询源码(高级表格显示)

    高级表格显示通常指的是在前端利用JavaScript库,如jQuery DataTables、ag-Grid等,提供排序、过滤、搜索等交互功能。这些库不仅可以处理分页,还能提升用户体验,比如动态加载、延迟渲染等。 在实际应用中,后端...

    jquery验证框架学习教程

    jQuery验证框架学习教程详细介绍了jQuery及其验证插件的使用方法,旨在帮助开发者快速掌握jQuery这一强大的JavaScript...推荐进一步阅读相关书籍,如“jQuery实战”,以深入理解和掌握jQuery的各种使用技巧和高级功能。

    编程参考宝典电子书

    本书【编程参考宝典电子书.exe】作为一个综合性的资源,很可能会包括HTML的标签详解、CSS的选择器和布局技巧、JavaScript的语法和高级特性等内容,并可能通过实例演示如何将这些知识应用到实际项目中。无论是初学者...

    Javascript 遍历对象中的子对象

    这些高级的遍历技巧在处理复杂数据结构时非常有用。 通过这些遍历方法的应用,我们可以灵活地访问和操作嵌套对象的任何部分,无论是基本数据类型还是函数类型的属性。这些技能是任何JavaScript开发人员必备的基础...

    训练营X

    训练营的内容可能涵盖JavaScript调试技巧、性能优化、模块化开发、测试驱动开发以及当前流行的前端框架和技术。完成训练后,学员将能够自信地运用JavaScript开发出功能丰富、用户体验优秀的Web应用。

    example2

    总的来说,"example2"可能是一个展示JavaScript功能、技巧或应用的实例,涵盖了从基础语法到高级特性的各种知识点。通过研究这个项目,开发者可以加深对JavaScript的理解,提高编程技能,并学习如何构建实际的Web...

    战舰项目

    《战舰项目——深入探索JavaScript编程实践》 在IT领域,尤其是前端开发中,...通过参与这个项目,开发者不仅可以提升JavaScript编程技巧,还能锻炼问题解决能力和逻辑思维能力,为未来更复杂的项目开发打下坚实基础。

    dot-connector:像没人管一样连接点

    点连接器,正如其标题“dot-connector:像没人...通过学习和实践使用这个工具,开发者可以提高他们的JavaScript编码技巧,编写出更加高效和优雅的代码。为了进一步探索这个工具,应该仔细研究解压后的源代码和相关文档。

Global site tag (gtag.js) - Google Analytics