Javascript 进阶

一、事件代理

事件绑定后,检测顺序就会从被绑定的DOM下滑到触发的元素,再冒泡到绑定的DOM上,也就是说,如果你监听了一个DOM节点,那就等于你监听了其所有的后代节点。

代理的意思就是之间厅父节点的时间触发,以来代理对其后代节点的监听,而你需要做的只是通过currentTarget 属性得到触发元素并作出回应。

使用事件代理意味着你可以节省大量重复的事件监听,以减少浏览器资源消耗。还有一个好处就是让HTL独立起来,比如之后还有要加子元素的需求,也不需要再为其单独加时间监听了

常用的属性与方法:

  • type(string):事件的名称
  • target(node):事件要触发的目标节点
  • currentTartget:指向整下处理事件的元素(this event.currentTarget)
  • bubbles(boolean):表明该事件是否是在冒泡阶段触发的
  • preventDefault(function):静止一切默认的行为,event.preventDefault 方法来禁用默认行为。
  • stopPropagation(function):很多时候,我们触发某个元素,会顺带触发出它父级身上的事件,这有时候是我们不想要的,大多数我们想要的还是事件相互独立。所以我们可以选择阻止事件冒泡,使用event.stopPropagation().
  • stopImmediatePropagation(function): 与 stopPropagation 类似,就是阻止触发其他监听函数。但是与 stopPropagation 不同的是,它更加 “强力”,阻止除了目标之外的事件触发,甚至阻止针对同一个目标节点的相同事件
  • cancelable(boolean):这个属性表明该事件是否可以通过调用
  • eventPhase(number):这个属性的数字表示当前时间触发在什么阶段 0:none 1:捕获 2:目标 3:冒泡
  • isTrusted (boolean): 表明该事件是浏览器触发(用户真实操作触发),还是 JavaScript 代码触发的。

二、常用事件

  1. load 资源加载完成时触发。这个资源可以是图片、CSS 文件、JS 文件、视频、document 和 window 等等。
  2. DOMContentLoaded DOM构建完毕的时候触发, jQuery的ready方法包裹的就是这个事件。
  3. beforeunload
    当浏览者在页面上的输入框输入一些内容时,未保存、误操作关掉网页可能会导致输入信息丢失。当浏览者输入信息但未保存时关掉网页,我们就可以开始监听这个事件,这时候试图关闭网页的时候,会弹窗阻止操作,点击确认之后才会关闭。当然,如果没有必要,就不要监听,不要以为使用它可以为你留住浏览者。
  4. resize 当节点尺寸发生变化时,触发这个事件。通常用在 window
    上,这样可以监听浏览器窗口的变化。通常用在复杂布局和响应式上。出于对性能的考虑,你可以使用函数 throttle 或者 debounce
    技巧来进行优化,throttle 方法大体思路就是在某一段时间内无论多次调用,只执行一次函数,到达时间就执行;debounce
    方法大体思路就是在某一段时间内等待是否还会重复调用,如果不会再调用,就执行函数,如果还有重复调用,则不执行继续等待。
  5. error 当我们加载资源失败或者加载成功但是只加载一部分而无法使用时,就会触发 error
    事件,我们可以通过监听该事件来提示一个友好的报错或者进行其他处理。比如 JS
    资源加载失败,则提示尝试刷新;图片资源加载失败,在图片下面提示图片加载失败等。该事件不会冒泡。因为子节点加载失败,并不意味着父节点加载失败,所以你的处理函数必须精确绑定到目标节点。

三、闭包

javascript语言的特殊之处,就在于函数内部可以直接访问全局变量。另一方面,在函数外部自然无法读取函数内的局部变量。但是通过闭包,可以再函数外面访问到内部的变量 比如:

function num(){
    var a = 1
    function addNum(){
        console.log(a)  //1
    }
}

函数addNum被包裹在函数num内部,这时num内部的所有局部变量,对addNum都是可见的。这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

闭包允许将函数与其所操作的某些数据(环境)关连起来。这显然类似于面向对象编程。在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。因而,一般说来,可以使用只有一个方法的对象的地方,都可以使用闭包。

我们在举个栗子:

function num(){
    var a = 100
    function addNum(){
        console.log(a++)
    }
    return addNum
}
var result = num()
result()  //100
result()  //101

原因:num() 是 addNum() 的父函数,而addNum() 被赋给了一个全局变量,这导致addNum始终存在内存中,而addNum的存在依赖于num,因此num也始终在内存中,一个牵着一个,不会在调用结束后,被垃圾回收机制回收

用闭包模拟私有方法(模块模式)

JavaScript 并不提供原生的支持,但是可以使用闭包模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

下面的示例展现了如何使用闭包来定义公共函数,且其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):

var Counter = (function() {
    var privateCounter = 0
    function changeBy(val){
        privateCounter += val
    }
    return {
        increment:function(){
            changeBy(1)
        }
        decrement:function(){
            changeBy(-1)
        }
        value:function(){
            return privateCounter
        }
    }
})
console.log(Counter.value())  //0
Counter.increment()
Counter.increment()
console.log(Counter.value())  //2
Counter.decrement()
console.log(Counter.value())  //1

在循环总创建闭包

function showHelp(help){
    document.getElementById('help').innerHTML = help
}

function makeHelpCallback(help){
        return function(){
            showHelp(help)
    }
}

function setupHelp(){
    var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ]

    for(var i = 0; i < helpText.length; i++){
        var item = helpText[i]
        document.getElementById(item.id).onfocus = makeHelpCallback(item.help)
    }
}

使用闭包的注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

四、原型继承

在javascript中,每个函数都有一个原型属性prototype指向函数自身的原型,而由这个函数创建的对象也有一个proto属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个proto指向自己的原型,这样逐层深入到Object对象的原型(null),这样就形成了原型链

  • prototype: 在函数身上,指向原型对象
  • proto: 在对象身上(包括函数创建的对象, 函数本身和原型对象),指向自身的原型
  • constructor: 在原型对象上,指向构造函数, 在多级继承的时候,指明构造函数方便在对象上扩展原型属性
  • Object.protp为null: 原型的顶端

原型使用方式

一、通过Calculator 对象的prototype属性复制对象字面量来设定Calculator对象的原型

var Calculator = function (decimalDigits, tax) {
    this.decimalDigits = decimalDigits;
    this.tax = tax;
};
Calculator.prototype = {
    add: function (x, y) {
    return x + y;
},

subtract: function (x, y) {
    return x - y;
    }
};
//alert((new Calculator()).add(1, 3));

二、分别设置原型对象:

var Calculator = function(decimalDigits, tax) {
      this.decimalDigits = decimalDigits
      this.tax = tax
}

Calculator.prototype.add = function (x, y) {
    return x + y;
}

Calculator.prototype.subtract = function (x, y) {
    return x - y;
}

三、赋值原型prototype的时候使用function立即执行的表达式来赋值,即如下格式:Calculator.prototype = function () { } ();, 可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果。

Calculator.prototype = function () {
            add = function (x, y) {
                return x + y;
            },

            subtract = function (x, y) {
                return x - y;
            }
            return {
                add: add,
                subtract: subtract
            }
} ();

五、JS的模块化

1.CommonJS
CommonJS就是为JS的表现来制定规范,因为js没有模块的功能所以CommonJS应运而生,它希望js可以在任何地方运行,不只是浏览器中。

require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。

//out.js
exports.test = function(){
    ....
}
//in.js
var math = require('test')
exports.add = function(n) {
    return math.test(val,n)
}

node.js 就是遵从common.js规范的
为什么说common.js不适合前端呢?是因为common.js是同步加载,而代码需要从一个服务器端分发到多个客户端执行,加载需要通过网络,这就不可避免的产生延迟,从而同步加载会大大降低网站的性能。所以,AMD应运而生。

2.AMD
AMD就只有一个接口:define(id?,dependencies?,factory);
它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中,像这样:

define(['dep1','dep2'], function(dep1,dep2){...})

define(function(){
    var exports = {}
    exports.method = function(){...}
    return exports;
})

RequireJS就是实现了AMD规范了的

3.CMD
大名远扬的玉伯写了seajs,就是遵循他提出的CMD规范,与AMD蛮相近的,不过用起来感觉更加方便些,最重要的是中文版,应有尽有:cmd官网

define(function(require,exports,module){...})

CMD与AMD的不同之处:

  1. 定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
  2. 遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
  3. 推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
  4. 对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
  5. 插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。

你可能感兴趣的:(Javascript)