name | title | tags | categories | info | time | desc | keywords | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
红宝书学习笔记(十五) |
红宝书(javascirpt高级程序设计)学习笔记(十五) |
|
学习笔记 |
人类的好伙伴,红宝书(十三) 第22章 高级技巧 |
2019/2/18 |
javascirpt高级程序设计, 红宝书, 资料下载, 学习笔记, 第22章 |
|
干货章节,建议认真阅读,做好笔记
JavaScript内置的类型监测机制并非完全可靠,typeof
和instanceof
操作符很多时候都会返回预想之外的结果(比如原生的JSON库和非原生的JSON库)。
解决方法是对要检测的对象执行Object.prototype.toString.call()
以确定其是否为原生对象。
安全的构造函数,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()
的方式在其他的作用域中调用这段赋值代码。
核心思想就是在执行分支代码时牺牲一点性能进行判断,但最终只需要做一次初始化。
function bind(fn, context) {
return function () {
return fn.apply(context, arguments)
}
}
使用该函数可以保证函数执行时其this
指针是绑定在其定义时候的对象。原理跟之前讲过的Function.prototype.bind
一样(其实更简单)。
函数柯里化用于创建已经设置好了一个或多个参数的函数。其基本方法和函数绑定时一样的:使用一个闭包返回一个函数,两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
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
对象响应来决定。它们都能用于创建复杂的算法和功能,但两者都不应滥用,因为每个函数都会带来额外的开销。
ECMAScript 5增加了几个方法,通过它们可以指定对象的行为。但要注意的是,这些行为是不可逆的。
默认情况下,所有对象都是可以扩展的,你可以随时向任何对象中添加任何属性和方法。现在,使用Object.preventExtensions()
方法可以改变这个行为,让你不能再给对象添加属性和方法。
var a = {b: 1}
Object.preventExtensions(a)
a.age = 3
a
// {b: 1}
在调用该方法后,就不能给该对象添加新属性和方法了,在严格模式下,给不可扩展对象添加新成员会导致抛出错误。
此外,虽然对象不可扩展,但你依旧可以修改和删除已有的成员,使用Object.istExtensible()
方法还可以确定对象是否可以扩展。
第二个保护级别是密封对象(sealed object)。密封对象不可扩展,也不能删除属性和方法,在严格模式下,任何尝试删除或者添加对象成员的操作都会报错。
可以使用Object.seal()
方法来定义一个密封对象,使用Object.isSealed()
方法确定对象是否被密封。
最高级别的防篡改对象是冻结对象(frozen object)。冻结对象不可扩展且密封,且不允许修改对象内的所有成员。
使用Object.freeze()
方法可以冻结一个对象,使用Object.isFrozen()
方法可以检测一个对象是否冻结。
由于JavaScript是运行在单线程环境中的语言,所以定时器仅仅只是计划代码在未来的某个时间执行,而执行时机是不能保证的,比如setTimeout
函数和setInterval
函数,两者都只是计划在多少秒以后将要执行的函数放入执行队列中,如果当前执行队列中有任务,那么就要等待当前任务完成才能执行,所以是不准确的。
使用setInterval
函数的问题在于,定时器代码可能在代码再次被添加到队列之前还没有执行完成,结果导致定时器代码连续运行了好几次且中间没有任何停顿。
关于这个问题,JavaScript的解决方式是,当使用setInterval()
时,仅当没有该定时器的任何其他代码实例时,才会将定时器添加到队列中。
但是这样也会出现2点问题:
- 某些间隔会被跳过
- 多个定时器的代码执行之间的间隔会比预期的小
为了避免这两个缺点,最好使用链式setTimeout()
来替代setInterval()
setTimeout(function () {
// 逻辑代码
setTimeout(auguments.callee, interval)
}, interval)
这样做的好处是,在前一个定时器代码执行完之前,绝对不会向队列插入新的定时器代码,确保不会有任何的缺失间隔。
由于浏览器对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()
函数来进行一个浅复制(或者来个深复制也不错)
在浏览器中,一些操作(比如操作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秒,会阻止后面的定时器产生。
事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。
观察者模式由两类对象构成:主体和观察者。
像Vue、React、Angular等主流框架都大量运用了观察者模式,这是一种高级的设计思想,在此暂不介绍。
拖放的基本概念:创建一个绝对定位的元素,使其可以用鼠标移动。这个技术源自一种叫做"鼠标拖尾"的经典网页技巧,即一个或多个图片跟着鼠标指针移动。事件处理程序如下
document.addEventListener(document, 'mousemove', function (event) {
var myDiv = document.getElementById("myDiv")
myDiv.style.left = event.clientX + 'px'
myDiv.style.top = event.clientY + 'px'
})
该小节略
用自定义事件完善拖放功能,该小节略