목차
1. 안전한 유형 감지
2. 惰性载入函数
3. 函数绑定
4. 柯里化
5. 防止篡改对象
6. 定时器
7. 函数节流throttling
웹 프론트엔드 JS 튜토리얼 당신이 알아야 할 고급 JS 기술(요약)

당신이 알아야 할 고급 JS 기술(요약)

Sep 15, 2021 pm 02:14 PM
js

이전 글 "튜토리얼: JS 및 API를 사용하여 날씨 웹 애플리케이션(컬렉션) 만들기 "에서 JS 및 API를 사용하여 날씨 웹 애플리케이션을 만드는 방법을 소개했습니다. 다음 기사에서는 JS의 고급 기술을 소개합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

당신이 알아야 할 고급 JS 기술(요약)

이 글은 "JS 고급 프로그래밍" 23장 "고급 기술"을 기반으로 한 독서 공유입니다. 이 글은 책에 담긴 사상을 따라가며, 나의 이해와 경험을 바탕으로 이를 확장, 확장하고, 책에 담긴 몇 가지 문제점을 지적합니다. 안전한 유형 검사, 함수의 지연 로딩, 고정된 객체, 타이머 등과 같은 주제에 대해 논의합니다.

1. 안전한 유형 감지

변수가 배열인지 확인하는 등 변수의 유형을 안전하게 감지하는 방법에 대한 질문입니다. 일반적인 접근 방식은 다음 코드에 표시된 대로 instanceof를 사용하는 것입니다. instanceof,如下代码所示:

let data = [1, 2, 3];
console.log(data instanceof Array); //true
로그인 후 복사

但是上面的判断在一定条件下会失败——就是在iframe里面判断一个父窗口的变量的时候。写个demo验证一下,如下主页面的main.html

<script>
    window.global = {
        arrayData: [1, 2, 3]
    }
    console.log("parent arrayData installof Array: " + 
          (window.global.arrayData instanceof Array));
</script>
<iframe src="iframe.html"></iframe>
로그인 후 복사

iframe.html判断一下父窗口的变量类型:

<script>
    console.log("iframe window.parent.global.arrayData instanceof Array: " + 
        (window.parent.global.arrayData instanceof Array));
</script>
로그인 후 복사

iframe里面使用window.parent得到父窗口的全局window对象,这个不管跨不跨域都没有问题,进而可以得到父窗口的变量,然后用instanceof判断。最后运行结果如下:

당신이 알아야 할 고급 JS 기술(요약)

可以看到父窗口的判断是正确的,而子窗口的判断是false,因此一个变量明明是Array,但却不是Array,这是为什么呢?既然这个是父子窗口才会有的问题,于是试一下把Array改成父窗口的Array,即window.parent.Array,如下图所示:

당신이 알아야 할 고급 JS 기술(요약)

这次返回了true,然后再变换一下其它的判断,如上图,最后可以知道根本原因是上图最后一个判断:

Array !== window.parent.Array

它们分别是两个函数,父窗口定义了一个,子窗口又定义了一个,内存地址不一样,内存地址不一样的Object等式判断不成立,而window.parent.arrayData.constructor返回的是父窗口的Array,比较的时候是在子窗口,使用的是子窗口的Array,这两个Array不相等,所以导致判断不成立。

那怎么办呢?

由于不能使用Object的内存地址判断,可以使用字符串的方式,因为字符串是基本类型,字符串比较只要每个字符都相等就好了。ES5提供了这么一个方法Object.prototype.toString,我们先小试牛刀,试一下不同变量的返回值:

당신이 알아야 할 고급 JS 기술(요약)

可以看到如果是数组返回"[object Array]",ES5对这个函数是这么规定的:

ES5函数地址:https://262.ecma-international.org/5.1/#sec-15.2.4.2

당신이 알아야 할 고급 JS 기술(요약)

也就是说这个函数的返回值是“[object”开头,后面带上变量类型的名称和右括号。因此既然它是一个标准语法规范,所以可以用这个函数安全地判断变量是不是数组。

可以这么写:

Object.prototype.toString.call([1, 2, 3]) ===
    "[object Array]"
로그인 후 복사

注意要使用call,而不是直接调用,call的第一个参数是context执行上下文,把数组传给它作为执行上下文。

有一个比较有趣的现象是ES6class也是返回function

당신이 알아야 할 고급 JS 기술(요약)

所以可以知道class也是用function实现的原型,也就是说classfunction

//UA的类型
getUAType: function() {
    let ua = window.navigator.userAgent;
    if (ua.match(/renren/i)) {
        return 0;
    }
    else if (ua.match(/MicroMessenger/i)) {
        return 1;
    }
    else if (ua.match(/weibo/i)) {
        return 2;
    }
    return -1;
}
로그인 후 복사
로그인 후 복사

그러나 위 판단은 특정 조건에서 실패합니다. 즉, iframe</code에서 상위 창을 판단합니다. > 변수일 때. 메인 페이지의 <code>main.html에 아래와 같이 demo를 작성하여 확인하세요. 🎜
//UA的类型
getUAType: function() {
    let ua = window.navigator.userAgent;
    if(ua.match(/renren/i)) {
        pageData.getUAType = () => 0;
        return 0;
    }
    else if(ua.match(/MicroMessenger/i)) {
        pageData.getUAType = () => 1;
        return 1;
    }
    else if(ua.match(/weibo/i)) {
        pageData.getUAType = () => 2;
        return 2;
    }
    return -1;
}
로그인 후 복사
로그인 후 복사
🎜 iframe.html<에서 상위 창의 변수 유형을 판단하세요. /code>: 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>let ua = window.navigator.userAgent; let UAType = ua.match(/renren/i) ? 0 : ua.match(/MicroMessenger/i) ? 1 : ua.match(/weibo/i) ? 2 : -1;</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜<code>iframe에서 window.parent를 사용하여 상위 창의 전역 window 개체를 가져옵니다. 여부는 중요하지 않습니다. 도메인 간인지 아닌지를 확인한 다음 상위 창의 변수를 가져온 다음 instanceof를 사용하여 판단할 수 있습니다. 최종 실행 결과는 다음과 같습니다. 🎜🎜WeChat 스크린샷_20210915134726 .png🎜🎜부모창의 판단은 맞으나, 자식창의 판단은 false이므로 변수는 당연히 배열</code인 것을 알 수 있습니다. >인데 <code>배열이 아닌데 왜 그럴까요? 이는 상위-하위 창에만 해당되는 문제이므로 배열을 상위 창의 배열, 즉 window.parent.Array로 변경해 보세요. code> , 아래 그림과 같이: 🎜🎜WeChat 스크린샷_ 20210915134759.png🎜🎜이번에는 true를 반환한 뒤, 위 그림과 같이 다른 판단을 변경한 결과, 최종적으로 근본 원인이 최종 판단임을 알 수 있습니다. 위 그림에서: 🎜🎜🎜Array != = window.parent.Array🎜🎜🎜그들은 각각 두 개의 함수입니다. 상위 창은 하나를 정의하고 하위 창은 <코드>객체<를 정의합니다. /code> 다른 메모리 주소의 방정식 판단 사실이 아니며 window.parent.arrayData.constructor는 비교 시 상위 창의 배열을 반환합니다. 자식 창과 자식 창의 배열이 사용되는 경우 이 두 배열은 동일하지 않으므로 판단이 유효하지 않습니다. 🎜🎜우리는 어떻게 해야 할까요? 🎜🎜Object의 메모리 주소를 사용하여 판단할 수 없으므로 문자열을 사용할 수 있습니다. 문자열은 기본 유형이므로 문자열 비교는 각 문자에 대해 동일하면 됩니다. ES5Object.prototype.toString 메소드를 제공합니다. 먼저 이를 시도해 보고 다양한 변수의 반환 값을 시도해 보겠습니다. 🎜🎜WeChat 스크린샷_20210915134920.png🎜🎜🎜보면 알 수 있어요 array return "[object Array]", ES5는 이 함수를 다음과 같이 규정합니다: 🎜🎜ES5 함수 주소: https://262.ecma-international.org/5.1/#sec-15.2.4.2🎜🎜🎜WeChat 스크린샷_20210915135119.png🎜🎜즉, 이 기능은 반환 값은 "[object"로 시작하고 그 뒤에 변수 유형 이름과 오른쪽 괄호가 옵니다. 따라서 표준 구문 사양이므로 이 함수를 사용하여 변수가 배열인지 안전하게 확인할 수 있습니다. 🎜🎜다음과 같이 작성할 수 있습니다. 🎜
Data.localStorageEnabled = true;
// Safari的无痕浏览会禁用localStorage
try{
    window.localStorage.trySetData = 1;
} catch(e) {
    Data.localStorageEnabled = false;
}
setLocalData: function(key, value) { 
    if (Data.localStorageEnabled) {
        window.localStorage[key] = value;
    }
    else {   
        util.setCookie("_L_" + key, value, 1000);
    }
}
로그인 후 복사
로그인 후 복사
🎜직접 호출하는 대신 call을 사용하도록 주의하세요. call의 첫 번째 매개변수는 컨텍스트</code입니다. > 실행 컨텍스트, 배열을 실행 컨텍스트로 전달합니다. 🎜🎜흥미로운 현상은 <code>ES6클래스함수도 반환한다는 것입니다: 🎜🎜WeChat 스크린샷_20210915135206.png🎜🎜그래서 수업을 알 수 있습니다. 또한 함수를 사용하여 구현된 프로토타입입니다. 이는 클래스함수가 본질적으로 동일하지만 다르게 작성되었음을 의미합니다. 🎜

那是不是说不能再使用instanceof判断变量类型了?不是的,当你需要检测父页面的变量类型就得使用这种方法,本页面的变量还是可以使用instanceof或者constructor的方法判断,只要你能确保这个变量不会跨页面。因为对于大多数人来说,很少会写iframe的代码,所以没有必要搞一个比较麻烦的方式,还是用简单的方式就好了。

2. 惰性载入函数

有时候需要在代码里面做一些兼容性判断,或者是做一些UA的判断,如下代码所示:

//UA的类型
getUAType: function() {
    let ua = window.navigator.userAgent;
    if (ua.match(/renren/i)) {
        return 0;
    }
    else if (ua.match(/MicroMessenger/i)) {
        return 1;
    }
    else if (ua.match(/weibo/i)) {
        return 2;
    }
    return -1;
}
로그인 후 복사
로그인 후 복사

这个函数的作用是判断用户是在哪个环境打开的网页,以便于统计哪个渠道的效果比较好。

这种类型的判断都有一个特点,就是它的结果是死的,不管执行判断多少次,都会返回相同的结果,例如用户的UA在这个网页不可能会发生变化(除了调试设定的之外)。所以为了优化,才有了惰性函数一说,上面的代码可以改成:

//UA的类型
getUAType: function() {
    let ua = window.navigator.userAgent;
    if(ua.match(/renren/i)) {
        pageData.getUAType = () => 0;
        return 0;
    }
    else if(ua.match(/MicroMessenger/i)) {
        pageData.getUAType = () => 1;
        return 1;
    }
    else if(ua.match(/weibo/i)) {
        pageData.getUAType = () => 2;
        return 2;
    }
    return -1;
}
로그인 후 복사
로그인 후 복사

在每次判断之后,把getUAType这个函数重新赋值,变成一个新的function,而这个function直接返回一个确定的变量,这样以后的每次获取都不用再判断了,这就是惰性函数的作用。你可能会说这么几个判断能优化多少时间呢,这么点时间对于用户来说几乎是没有区别的呀。确实如此,但是作为一个有追求的码农,还是会想办法尽可能优化自己的代码,而不是只是为了完成需求完成功能。并且当你的这些优化累积到一个量的时候就会发生质变。我上大学的时候C++的老师举了一个例子,说有个系统比较慢找她去看一下,其中她做的一个优化是把小数的双精度改成单精度,最后是快了不少。

但其实上面的例子我们有一个更简单的实现,那就是直接搞个变量存起来就好了:

let ua = window.navigator.userAgent;
let UAType = ua.match(/renren/i) ? 0 :
                ua.match(/MicroMessenger/i) ? 1 :
                ua.match(/weibo/i) ? 2 : -1;
로그인 후 복사
로그인 후 복사

连函数都不用写了,缺点是即使没有使用到UAType这个变量,也会执行一次判断,但是我们认为这个变量被用到的概率还是很高的。

我们再举一个比较有用的例子,由于Safari的无痕浏览会禁掉本地存储,因此需要搞一个兼容性判断:

Data.localStorageEnabled = true;
// Safari的无痕浏览会禁用localStorage
try{
    window.localStorage.trySetData = 1;
} catch(e) {
    Data.localStorageEnabled = false;
}
setLocalData: function(key, value) { 
    if (Data.localStorageEnabled) {
        window.localStorage[key] = value;
    }
    else {   
        util.setCookie("_L_" + key, value, 1000);
    }
}
로그인 후 복사
로그인 후 복사

在设置本地数据的时候,需要判断一下是不是支持本地存储,如果是的话就用localStorage,否则改用cookie。可以用惰性函数改造一下:

setLocalData: function(key, value) {
    if(Data.localStorageEnabled) {
        util.setLocalData = function(key, value){
            return window.localStorage[key];
        }
    } else {
        util.setLocalData = function(key, value){
            return util.getCookie("_L_" + key);
        }
    }
    return util.setLocalData(key, value);
}
로그인 후 복사

这里可以减少一次if/else的判断,但好像不是特别实惠,毕竟为了减少一次判断,引入了一个惰性函数的概念,所以你可能要权衡一下这种引入是否值得,如果有三五个判断应该还是比较好的。

3. 函数绑定

有时候要把一个函数当作参数传递给另一个函数执行,此时函数的执行上下文往往会发生变化,如下代码:

class DrawTool {
    constructor() {
        this.points = [];
    }
    handleMouseClick(event) {
        this.points.push(event.latLng);
    }
    init() {
        $map.on(&#39;click&#39;, this.handleMouseClick);
    }
}
로그인 후 복사

click事件的执行回调里面this不是指向了DrawTool的实例了,所以里面的this.points将会返回undefined。第一种解决方法是使用闭包,先把this缓存一下,变成that

class DrawTool {
    constructor() {
        this.points = [];
    }
    handleMouseClick(event) {
        this.points.push(event.latLng);
    }
    init() {
        let that = this;
        $map.on(&#39;click&#39;, event => that.handleMouseClick(event));
    }
}
로그인 후 복사

由于回调函数是用that执行的,而that是指向DrawTool的实例子,因此就没有问题了。相反如果没有that它就用的this,所以就要看this指向哪里了。

因为我们用了箭头函数,而箭头函数的this还是指向父级的上下文,因此这里不用自己创建一个闭包,直接用this就可以:

init() {    $map.on(&#39;click&#39;,             event => this.handleMouseClick(event));}复制代码
这种方式更加简单,第二种方法是使用ES5的bind函数绑定,如下代码:
init() {    $map.on(&#39;click&#39;,             this.handleMouseClick.bind(this));}
로그인 후 복사

这个bind看起来好像很神奇,但其实只要一行代码就可以实现一个bind函数:

Function.prototype.bind = function(context) {
    return () => this.call(context);
}
로그인 후 복사

就是返回一个函数,这个函数的this是指向的原始函数,然后让它call(context)绑定一下执行上下文就可以了。

4. 柯里化

柯里化就是函数和参数值结合产生一个新的函数,如下代码,假设有一个curry的函数:

function add(a, b) {
    return a + b;
}
let add1 = add.curry(1);
console.log(add1(5)); // 6
console.log(add1(2)); // 3
로그인 후 복사

怎么实现这样一个curry的函数?它的重点是要返回一个函数,这个函数有一些闭包的变量记录了创建时的默认参数,然后执行这个返回函数的时候,把新传进来的参数和默认参数拼一下变成完整参数列表去调原本的函数,所以有了以下代码:

Function.prototype.curry = function() {
    let defaultArgs = arguments;
    let that = this;
    return function() {
        return that.apply(this, 
                          defaultArgs.concat(arguments));    }};
로그인 후 복사

但是由于参数不是一个数组,没有concat函数,所以需要把伪数组转成一个伪数组,可以用Array.prototype.slice

Function.prototype.curry = function() {
    let slice = Array.prototype.slice;
    let defaultArgs = slice.call(arguments);
    let that = this;
    return function() {
        return that.apply(this, 
                          defaultArgs.concat(slice.call(arguments)));    }};
로그인 후 복사

现在举一下柯里化一个有用的例子,当需要把一个数组降序排序的时候,需要这样写:

let data = [1,5,2,3,10];
data.sort((a, b) => b - a); // [10, 5, 3, 2, 1]
로그인 후 복사

sort传一个函数的参数,但是如果你的降序操作比较多,每次都写一个函数参数还是有点烦的,因此可以用柯里化把这个参数固化起来:

Array.prototype.sortDescending = 
                 Array.prototype.sort.curry((a, b) => b - a);
로그인 후 복사

这样就方便多了:

let data = [1,5,2,3,10];
data.sortDescending();
console.log(data); // [10, 5, 3, 2, 1]
로그인 후 복사

5. 防止篡改对象

有时候你可能怕你的对象被误改了,所以需要把它保护起来。

(1)Object.seal防止新增和删除属性

如下代码,当把一个对象seal之后,将不能添加和删除属性:

당신이 알아야 할 고급 JS 기술(요약)

当使用严格模式将会抛异常:

당신이 알아야 할 고급 JS 기술(요약)

(2)Object.freeze冻结对象

这个是不能改属性值,如下图所示:

당신이 알아야 할 고급 JS 기술(요약)

同时可以使用Object.isFrozenObject.isSealedObject.isExtensible判断当前对象的状态。

(3)defineProperty冻结单个属性

如下图所示,设置enumable/writablefalse,那么这个属性将不可遍历和写:

당신이 알아야 할 고급 JS 기술(요약)

6. 定时器

怎么实现一个JS版的sleep函数?因为在C/C++/Java等语言是有sleep函数,但是JS没有。sleep函数的作用是让线程进入休眠,当到了指定时间后再重新唤起。你不能写个while循环然后不断地判断当前时间和开始时间的差值是不是到了指定时间了,因为这样会占用CPU,就不是休眠了。

这个实现比较简单,我们可以使用setTimeout + 回调:

function sleep(millionSeconds, callback) {
    setTimeout(callback, millionSeconds);
}
// sleep 2秒
sleep(2000, () => console.log("sleep recover"));
로그인 후 복사

但是使用回调让我的代码不能够和平常的代码一样像瀑布流一样写下来,我得搞一个回调函数当作参数传值。于是想到了Promise,现在用Promise改写一下:

function sleep(millionSeconds) {
    return new Promise(resolve => 
                             setTimeout(resolve, millionSeconds));
}
sleep(2000).then(() => console.log("sleep recover"));
로그인 후 복사

但好像还是没有办法解决上面的问题,仍然需要传递一个函数参数。

虽然使用Promise本质上是一样的,但是它有一个resolve的参数,方便你告诉它什么时候异步结束,然后它就可以执行then了,特别是在回调比较复杂的时候,使用Promise还是会更加的方便。

ES7新增了两个新的属性async/await用于处理的异步的情况,让异步代码的写法就像同步代码一样,如下async版本的sleep

function sleep(millionSeconds) {
    return new Promise(resolve => 
                           setTimeout(resolve, millionSeconds));
}
async function init() {
    await sleep(2000);
    console.log("sleep recover");
}
init();
로그인 후 복사

相对于简单的Promise版本,sleep的实现还是没变。不过在调用sleep的前面加一个await,这样只有sleep这个异步完成了,才会接着执行下面的代码。同时需要把代码逻辑包在一个async标记的函数里面,这个函数会返回一个Promise对象,当里面的异步都执行完了就可以then了:

init().then(() => console.log("init finished"));
로그인 후 복사

ES7的新属性让我们的代码更加地简洁优雅。

关于定时器还有一个很重要的话题,那就是setTimeoutsetInterval的区别。如下图所示:

당신이 알아야 할 고급 JS 기술(요약)

setTimeout是在当前执行单元都执行完才开始计时,而setInterval是在设定完计时器后就立马计时。可以用一个实际的例子做说明,这个例子我在《JS与多线程》这篇文章里面提到过,这里用代码实际地运行一下,如下代码所示:

let scriptBegin = Date.now();
fun1();
fun2();
// 需要执行20ms的工作单元
function act(functionName) {
    console.log(functionName, Date.now() - scriptBegin);
    let begin = Date.now();
    while(Date.now() - begin < 20);
}
function fun1() {
    let fun3 = () => act("fun3");
    setTimeout(fun3, 0);
    act("fun1");
}
function fun2() {
    act("fun2 - 1");
    var fun4 = () => act("fun4");
    setInterval(fun4, 20);
    act("fun2 - 2");
}
로그인 후 복사

这个代码的执行模型是这样的:

당신이 알아야 할 고급 JS 기술(요약)

控制台输出:

당신이 알아야 할 고급 JS 기술(요약)

与上面的模型分析一致。

接着再讨论最后一个话题,函数节流

7. 函数节流throttling

节流的目的是为了不想触发执行得太快,如:

监听input触发搜索监听resize做响应式调整监听mousemove调整位置

我们先看一下,resize/mousemove事件1s种能触发多少次,于是写了以下驱动代码:

let begin = 0;
let count = 0;
window.onresize = function() {
    count++;
    let now = Date.now();
    if (!begin) {
        begin = now;
        return;
    }
    if((now - begin) % 3000 < 60) {
        console.log(now - begin,
           count / (now - begin) * 1000);
    }
};
로그인 후 복사

当把窗口拉得比较快的时候,resize事件大概是1s触发40次:

당신이 알아야 할 고급 JS 기술(요약)

需要注意的是,并不是说你拉得越快,触发得就越快。实际情况是,拉得越快触发得越慢,因为拉动的时候页面需要重绘,变化得越快,重绘的次数也就越多,所以导致触发得更少了。

mousemove事件在我的电脑的Chrome1s大概触发60次:

당신이 알아야 할 고급 JS 기술(요약)

如果你需要监听resize事件做DOM调整的话,这个调整比较费时,1s要调整40次,这样可能会响应不过来,并且不需要调整得这么频繁,所以要节流。

怎么实现一个节流呢,书里是这么实现的:

function throttle(method, context) {
    clearTimeout(method.tId);
    method.tId = setTimeout(function() {
        method.call(context);
    }, 100);
}
로그인 후 복사

每次执行都要setTimeout一下,如果触发得很快就把上一次的setTimeout清掉重新setTimeout,这样就不会执行很快了。但是这样有个问题,就是这个回调函数可能永远不会执行,因为它一直在触发,一直在清掉tId,这样就有点尴尬,上面代码的本意应该是100ms内最多触发一次,而实际情况是可能永远不会执行。这种实现应该叫防抖,不是节流。

把上面的代码稍微改造一下:

function throttle(method, context) {
    if (method.tId) {
        return;
    }
    method.tId = setTimeout(function() {
        method.call(context);
        method.tId = 0;
    }, 100);
}
로그인 후 복사

这个实现就是正确的,每100ms最多执行一次回调,原理是在setTimeout里面把tId给置成0,这样能让下一次的触发执行。实际实验一下:

당신이 알아야 할 고급 JS 기술(요약)

大概每100ms就执行一次,这样就达到我们的目的。

但是这样有一个小问题,就是每次执行都是要延迟100ms,有时候用户可能就是最大化了窗口,只触发了一次resize事件,但是这次还是得延迟100ms才能执行,假设你的时间是500ms,那就得延迟半秒,因此这个实现不太理想。

需要优化,如下代码所示:

function throttle(method, context) {
    // 如果是第一次触发,立刻执行
    if (typeof method.tId === "undefined") {
        method.call(context);
    }
    if (method.tId) {
        return;
    }
    method.tId = setTimeout(function() {
        method.call(context);
        method.tId = 0;
    }, 100);
}
로그인 후 복사

先判断是否为第一次触发,如果是的话立刻执行。这样就解决了上面提到的问题,但是这个实现还是有问题,因为它只是全局的第一次,用户最大化之后,隔了一会又取消最大化了就又有延迟了,并且第一次触发会执行两次。那怎么办呢?

笔者想到了一个方法:

function throttle(method, context) {
    if (!method.tId) {
        method.call(context);
        method.tId = 1;
        setTimeout(() => method.tId = 0, 100);
    }
}
로그인 후 복사

每次触发的时候立刻执行,然后再设定一个计时器,把tId置成0,实际的效果如下:

당신이 알아야 할 고급 JS 기술(요약)

这个实现比之前的实现还要简洁,并且能够解决延迟的问题。

所以通过节流,把执行次数降到了1s执行10次,节流时间也可以控制,但同时失去了灵敏度,如果你需要高灵敏度就不应该使用节流,例如做一个拖拽的应用。如果拖拽节流了会怎么样?用户会发现拖起来一卡一卡的。

笔者重新看了高程的《高级技巧》的章节结合自己的理解和实践总结了这么一篇文章,我的体会是如果看书看博客只是当作睡前读物看一看其实收获不是很大,没有实际地把书里的代码实践一下,没有结合自己的编码经验,就不能用自己的理解去融入这个知识点,从而转化为自己的知识。你可能会说我看了之后就会印象啊,有印象还是好的,但是你花了那么多时间看了那本书只是得到了一个印象,你自己都没有实践过的印象,这个印象又有多靠谱呢。如果别人问到了这个印象,你可能会回答出一些连不起来的碎片,就会给人一种背书的感觉。还有有时候书里可能会有一些错误或者过时的东西,只有实践了才能出真知。

推荐学习:JS视频教程

위 내용은 당신이 알아야 할 고급 JS 기술(요약)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

JS 및 Baidu Maps를 사용하여 지도 이동 기능을 구현하는 방법 JS 및 Baidu Maps를 사용하여 지도 이동 기능을 구현하는 방법 Nov 21, 2023 am 10:00 AM

JS 및 Baidu Map을 사용하여 지도 팬 기능을 구현하는 방법 Baidu Map은 지리 정보, 위치 지정 및 기타 기능을 표시하기 위해 웹 개발에 자주 사용되는 널리 사용되는 지도 서비스 플랫폼입니다. 이 글에서는 JS와 Baidu Map API를 사용하여 지도 이동 기능을 구현하는 방법을 소개하고 구체적인 코드 예제를 제공합니다. 1. 준비 바이두 맵 API를 사용하기 전에 먼저 바이두 맵 오픈 플랫폼(http://lbsyun.baidu.com/)에서 개발자 계정을 신청하고 애플리케이션을 만들어야 합니다. 생성 완료

권장 사항: 우수한 JS 오픈 소스 얼굴 감지 및 인식 프로젝트 권장 사항: 우수한 JS 오픈 소스 얼굴 감지 및 인식 프로젝트 Apr 03, 2024 am 11:55 AM

얼굴 검출 및 인식 기술은 이미 상대적으로 성숙하고 널리 사용되는 기술입니다. 현재 가장 널리 사용되는 인터넷 응용 언어는 JS입니다. 웹 프런트엔드에서 얼굴 감지 및 인식을 구현하는 것은 백엔드 얼굴 인식에 비해 장점과 단점이 있습니다. 장점에는 네트워크 상호 작용 및 실시간 인식이 줄어 사용자 대기 시간이 크게 단축되고 사용자 경험이 향상된다는 단점이 있습니다. 모델 크기에 따라 제한되고 정확도도 제한됩니다. js를 사용하여 웹에서 얼굴 인식을 구현하는 방법은 무엇입니까? 웹에서 얼굴 인식을 구현하려면 JavaScript, HTML, CSS, WebRTC 등 관련 프로그래밍 언어 및 기술에 익숙해야 합니다. 동시에 관련 컴퓨터 비전 및 인공지능 기술도 마스터해야 합니다. 웹 측면의 디자인으로 인해 주목할 가치가 있습니다.

PHP와 JS를 사용하여 주식 촛대 차트를 만드는 방법 PHP와 JS를 사용하여 주식 촛대 차트를 만드는 방법 Dec 17, 2023 am 08:08 AM

PHP와 JS를 사용하여 주식 캔들 차트를 만드는 방법 주식 캔들 차트는 주식 시장에서 흔히 사용되는 기술 분석 그래픽으로 시가, 종가, 최고가 등의 데이터를 그려서 투자자가 주식을 보다 직관적으로 이해할 수 있도록 도와줍니다. 주식의 최저 가격. 이 기사에서는 특정 코드 예제와 함께 PHP 및 JS를 사용하여 주식 캔들 차트를 만드는 방법을 설명합니다. 1. 준비 시작하기 전에 다음 환경을 준비해야 합니다. 1. PHP를 실행하는 서버 2. HTML5 및 Canvas를 지원하는 브라우저 3

주식 분석을 위한 필수 도구: PHP 및 JS를 사용하여 캔들 차트를 그리는 단계를 알아보세요. 주식 분석을 위한 필수 도구: PHP 및 JS를 사용하여 캔들 차트를 그리는 단계를 알아보세요. Dec 17, 2023 pm 06:55 PM

주식 분석을 위한 필수 도구: PHP 및 JS에서 캔들 차트를 그리는 단계를 배우십시오. 인터넷과 기술의 급속한 발전으로 주식 거래는 많은 투자자에게 중요한 방법 중 하나가 되었습니다. 주식분석은 투자자의 의사결정에 있어 중요한 부분이며 캔들차트는 기술적 분석에 널리 사용됩니다. PHP와 JS를 사용하여 캔들 차트를 그리는 방법을 배우면 투자자가 더 나은 결정을 내리는 데 도움이 되는 보다 직관적인 정보를 얻을 수 있습니다. 캔들스틱 차트는 주가를 캔들스틱 형태로 표시하는 기술 차트입니다. 주가를 보여주네요

JS와 Baidu Map을 활용하여 지도 클릭 이벤트 처리 기능을 구현하는 방법 JS와 Baidu Map을 활용하여 지도 클릭 이벤트 처리 기능을 구현하는 방법 Nov 21, 2023 am 11:11 AM

JS 및 Baidu Maps를 사용하여 지도 클릭 이벤트 처리 기능을 구현하는 방법 개요: 웹 개발에서는 지리적 위치 및 지리적 정보를 표시하기 위해 지도 기능을 사용해야 하는 경우가 많습니다. 지도에서의 클릭 이벤트 처리는 지도 기능에서 일반적으로 사용되는 중요한 부분입니다. 이 글에서는 JS와 Baidu Map API를 사용하여 지도의 클릭 이벤트 처리 기능을 구현하는 방법을 소개하고 구체적인 코드 예제를 제공합니다. 단계: Baidu Map API 파일 가져오기 먼저 다음 코드를 통해 Baidu Map API 파일을 가져올 수 있습니다.

JS 및 Baidu Maps를 사용하여 지도 히트맵 기능을 구현하는 방법 JS 및 Baidu Maps를 사용하여 지도 히트맵 기능을 구현하는 방법 Nov 21, 2023 am 09:33 AM

JS 및 Baidu Maps를 사용하여 지도 열 지도 기능을 구현하는 방법 소개: 인터넷과 모바일 장치의 급속한 발전으로 지도는 일반적인 응용 시나리오가 되었습니다. 시각적 표시 방법인 히트맵은 데이터 분포를 보다 직관적으로 이해하는 데 도움이 될 수 있습니다. 이 기사에서는 JS 및 Baidu Map API를 사용하여 지도 히트맵 기능을 구현하는 방법을 소개하고 구체적인 코드 예제를 제공합니다. 준비 작업: 시작하기 전에 Baidu 개발자 계정, 애플리케이션 생성, 해당 AP 획득 등의 항목을 준비해야 합니다.

PHP 및 JS 개발 팁: 주식 캔들 차트 그리기 방법 익히기 PHP 및 JS 개발 팁: 주식 캔들 차트 그리기 방법 익히기 Dec 18, 2023 pm 03:39 PM

인터넷 금융의 급속한 발전으로 인해 주식 투자는 점점 더 많은 사람들의 선택이 되었습니다. 주식 거래에서 캔들 차트는 주가의 변화 추세를 보여주고 투자자가 보다 정확한 결정을 내리는 데 도움이 되는 일반적으로 사용되는 기술적 분석 방법입니다. 이 기사에서는 PHP와 JS의 개발 기술을 소개하고 독자가 주식 캔들 차트를 그리는 방법을 이해하도록 유도하며 구체적인 코드 예제를 제공합니다. 1. 주식 캔들 차트의 이해 주식 캔들 차트를 그리는 방법을 소개하기 전에 먼저 캔들 차트가 무엇인지부터 이해해야 합니다. 캔들스틱 차트는 일본인이 개발했습니다.

js와 vue의 관계 js와 vue의 관계 Mar 11, 2024 pm 05:21 PM

js와 vue의 관계: 1. 웹 개발의 초석인 JS 2. 프론트엔드 프레임워크로서의 Vue.js의 등장 3. JS와 Vue의 상호 보완적인 관계 4. JS와 Vue의 실제 적용 Vue.

See all articles