Skip to content

Latest commit

 

History

History
257 lines (182 loc) · 9.97 KB

红宝书学习笔记(十五).md

File metadata and controls

257 lines (182 loc) · 9.97 KB
name title tags categories info time desc keywords
红宝书学习笔记(十五)
红宝书(javascirpt高级程序设计)学习笔记(十五)
技术
学习笔记
JavaScript高级程序设计
学习笔记
人类的好伙伴,红宝书(十三) 第22章 高级技巧
2019/2/18
javascirpt高级程序设计, 红宝书, 资料下载, 学习笔记, 第22章
前端
红宝书笔记
学习笔记
第22章 高级技巧

红宝书(javascirpt高级程序设计)学习笔记(十五)

第22章 高级技巧

干货章节,建议认真阅读,做好笔记

22.1 高级函数

22.1.1 安全的类型检测

JavaScript内置的类型监测机制并非完全可靠,typeofinstanceof操作符很多时候都会返回预想之外的结果(比如原生的JSON库和非原生的JSON库)。

解决方法是对要检测的对象执行Object.prototype.toString.call()以确定其是否为原生对象。

22.1.2 作用域安全的构造函数

安全的构造函数,this指针不会由于错误的调用而指向window污染全局作用域,如下

function Person (name, age, job) {
    if (this instanceof Person) {
        this.name = name
        this.age = age
        this.obj = obj
    } else {
        return new Person(name, age, job)
    }
}
var person1 = Person("joy", 18, "Software")

使用以上代码可以锁定调用构造函数的环境,但这也意味着你不能使用Person.call()的方式在其他的作用域中调用这段赋值代码。

22.1.3 惰性载入函数

核心思想就是在执行分支代码时牺牲一点性能进行判断,但最终只需要做一次初始化。

22.1.4 函数绑定

function bind(fn, context) {
    return function () {
        return fn.apply(context, arguments)
    }
}

使用该函数可以保证函数执行时其this指针是绑定在其定义时候的对象。原理跟之前讲过的Function.prototype.bind一样(其实更简单)。

22.1.5 函数柯里化

函数柯里化用于创建已经设置好了一个或多个参数的函数。其基本方法和函数绑定时一样的:使用一个闭包返回一个函数,两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

function curry (fn, currArgs) {
    return function() {
        let args = [].slice.call(arguments);

        // 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接
        if (currArgs !== undefined) {
            args = args.concat(currArgs);
        }

        // 递归调用
        if (args.length < fn.length) {
            return curry(fn, args);
        }

        // 递归出口
        return fn.apply(null, args);
    }
}

// 作者:Micherwa
// 链接:https://juejin.im/post/5c677041f265da2de25b7707

柯里化函数和绑定函数为JavaScript提供了强大的动态函数创建功能。当然,使用bind()还是curry()要根据是否需要object对象响应来决定。它们都能用于创建复杂的算法和功能,但两者都不应滥用,因为每个函数都会带来额外的开销。

22.2 防篡改对象

ECMAScript 5增加了几个方法,通过它们可以指定对象的行为。但要注意的是,这些行为是不可逆的

22.2.1 不可扩展对象

默认情况下,所有对象都是可以扩展的,你可以随时向任何对象中添加任何属性和方法。现在,使用Object.preventExtensions()方法可以改变这个行为,让你不能再给对象添加属性和方法。

var a = {b: 1}
Object.preventExtensions(a)
a.age = 3
a
// {b: 1}

在调用该方法后,就不能给该对象添加新属性和方法了,在严格模式下,给不可扩展对象添加新成员会导致抛出错误。

此外,虽然对象不可扩展,但你依旧可以修改和删除已有的成员,使用Object.istExtensible()方法还可以确定对象是否可以扩展。

22.2.2 密封的对象

第二个保护级别是密封对象(sealed object)。密封对象不可扩展,也不能删除属性和方法,在严格模式下,任何尝试删除或者添加对象成员的操作都会报错。

可以使用Object.seal()方法来定义一个密封对象,使用Object.isSealed()方法确定对象是否被密封。

22.2.3 冻结的对象

最高级别的防篡改对象是冻结对象(frozen object)。冻结对象不可扩展且密封,且不允许修改对象内的所有成员。

使用Object.freeze()方法可以冻结一个对象,使用Object.isFrozen()方法可以检测一个对象是否冻结。

22.3 高级定时器

由于JavaScript是运行在单线程环境中的语言,所以定时器仅仅只是计划代码在未来的某个时间执行,而执行时机是不能保证的,比如setTimeout函数和setInterval函数,两者都只是计划在多少秒以后将要执行的函数放入执行队列中,如果当前执行队列中有任务,那么就要等待当前任务完成才能执行,所以是不准确的。

22.3.1 重复的定时器

使用setInterval函数的问题在于,定时器代码可能在代码再次被添加到队列之前还没有执行完成,结果导致定时器代码连续运行了好几次且中间没有任何停顿。

关于这个问题,JavaScript的解决方式是,当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才会将定时器添加到队列中。

但是这样也会出现2点问题:

  • 某些间隔会被跳过
  • 多个定时器的代码执行之间的间隔会比预期的小

为了避免这两个缺点,最好使用链式setTimeout()来替代setInterval()

setTimeout(function () {
    // 逻辑代码
    setTimeout(auguments.callee, interval)
}, interval)

这样做的好处是,在前一个定时器代码执行完之前,绝对不会向队列插入新的定时器代码,确保不会有任何的缺失间隔。

22.3.2 Yielding Processes

由于浏览器对JavaScript的运行时间是有严格限制的(防止有些恶意程序员占用过多计算机资源导致崩溃),所以为了确保运行时间,对于一些占用大量时间的循环,可以使用定时器来分割,这种技术名为数组分块(array chunking),用于小块小块地处理数组。

// @params: 待处理的项目数组,用于处理项目的函数,可选的运行该函数的执行上下文环境 
function chunk (array, process, context) {
    setTimeout(function () {
        var item = array.shift()
        process.call(context, item)
        if (array.length > 0) {
            setTimeout(arguments.callee, 100)
        }
    }, 100)
}

要注意的是,上面的chunk()函数是会对数组中的元素进行处理改变的,如果不想改变,可以对传入数组使用concat()函数来进行一个浅复制(或者来个深复制也不错)

22.3.3 函数节流

在浏览器中,一些操作(比如操作DOM、直接对浏览器大小进行操作)会比其他操作"昂贵"的多,甚至在某些高频率的更改中,会使得浏览器直接崩溃。为了避免该问题,可以使用定时器对该函数进行节流。

函数节流的基本思想是,某个函数的执行不能不间断,下一次执行必须要跟上一次的执行保留一定间隔。

// 节流函数 时间戳
const myThrottle = function (func, wait = 50) {
  let lastTime = 0
  return function (...args) {
    let now = new Date()
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

// 使用setTimeOut的另一种写法
const myThrottle2 = function (func, wait = 50) {
  var canRun = true
  return function (...args) {
    if (!canRun) {
      return
    } else {
      canRun = false
      func.apply(this, args) // 将方法放在外面, 这样即便该函数是异步的,也可以保证在下一句之前执行
      setTimeout(function () {canRun = true}, wait)
    }
  }
}

setInterval(myThrottle(() => console.log('hi'), 1000), 1)
setInterval(myThrottle2(() => console.log('hi'), 1000), 1)

除此以外,还有一个防抖函数。

// 防抖函数
const myDebounce = (func, wait = 50) => {
  let timer = 0
  return function (...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

const test = document.getElementById('test')
test.onclick = myDebounce(() => {console.log('click')}, 3000)

面试题:节流函数和单例函数有什么不同,是怎么实现的?

防抖函数:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

节流函数:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

人话:简单来说就是,防抖函数会取消掉之前生成的定时器,并重新生成一个n秒的计时器。而节流函数,则是如果上一次函数执行完毕后还未超过n秒,会阻止后面的定时器产生。

22.4 自定义事件

事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。

观察者模式由两类对象构成:主体观察者

像Vue、React、Angular等主流框架都大量运用了观察者模式,这是一种高级的设计思想,在此暂不介绍。

22.5 拖放

拖放的基本概念:创建一个绝对定位的元素,使其可以用鼠标移动。这个技术源自一种叫做"鼠标拖尾"的经典网页技巧,即一个或多个图片跟着鼠标指针移动。事件处理程序如下

document.addEventListener(document, 'mousemove', function (event) {
    var myDiv = document.getElementById("myDiv")
    myDiv.style.left = event.clientX + 'px'
    myDiv.style.top = event.clientY + 'px'
})

22.5.1 修缮拖动功能

该小节略

22.5.2 添加自定义事件

用自定义事件完善拖放功能,该小节略