>  기사  >  웹 프론트엔드  >  JavaScript 지식 요약

JavaScript 지식 요약

小云云
小云云원래의
2018-02-08 15:08:091222검색

이 글은 주로 javascript에서 유사한 키워드, 메소드, 개념을 정리하고 모두에게 도움이 되기를 바라며 공유합니다. javascript 中一些相似的关键字、方法、概念,并分享给大家,希望能帮助到大家。

1. var、function、let、const 命令的区别

  • 使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象

  • 使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升

  • 使用const声明的是常量,在后面出现的代码中不能再修改该常量的栈内存在的值和地址

  • 使用function声明的函数,其作用域为该语句所在的函数内,且存在函数提升现象

  • var

    //a. 变量提升
    console.log(a) // => undefined
    var a = 123
    
    //b. 作用域
    function f() {
        var a = 123
        console.log(a) // => 123
    }
    console.log(a) // => a is not defined
    
    for (var i = 0; i < 10; i ++) {}
    console.log(i) // => 10
  • let

    //a. 变量不提升
    console.log(a) // => a is not defined
    let a = 123
    
    //b. 作用域为所在代码块内
    for (let i = 0; i < 10; i ++) {}
    console.log(i) // => i is not defined
  • const

    //a. 不能修改的是栈内存在的值和地址
    const a = 10
        a = 20 // => Assignment to constant variable 
    
    // 但是以下的赋值确是合法的
    const  a = {
        b: 20
    }
    a.b = 30
    console.log(a.b) // => 30
  • function

    //a. 函数提升
    fn() // => 123
    function fn() {
        return 123
    }
    
    //b. 作用域
    function fn() {
        function fn1 () {
            return 123456
        }
        fn1() // => 123456
    }
    fn1() // => fn1 is not defined
  • 经典面试题

  1. var a = 1
    function fn() {
        if (!a) {
            var a = 123
        }
        console.log(a)
    }
    fn() ?
  2. // 如何依次打印出0 - 9

    for (var i = 0; i < 10; i++) {
        setTimeout(function(){
            console.log(i)
        })
    }
  3. function Foo() {
        getName = function(){
            console.log("1");
        };
        return this;
    }
    Foo.getName = function() {
        console.log("2");
    };
    
    Foo.prototype.getName = function(){
        console.log("3");
    };
    
    var getName = function() {
        console.log("4");
    }
    function getName(){
        console.log("5");
    }
    
    Foo.getName(); ?
    getName(); ?
    Foo().getName(); ?  
    getName(); ?
    new Foo.getName(); ?
    new Foo().getName(); ?
  • 答案:
    第一题

    //我们把它执行顺序整理下
    var a = 1
    function fn() {
        var a = nudefined
        if (!a) {
            var a = 123
        }
        console.log(a)
    }
    //所以 答案很明显 就是 123

    第2题

    for (var i = 0; i < 10; i++) {
        print(i)
    }
    function print(i) { // 把每个变量i值传进来,变成只可当前作用域访问的局部变量
        setTimeout(function(){
            console.log(i)
        })
    }
    
    // 或者自执行函数简写
    for (var i = 0; i < 10; i++) {
        (function(i){
            setTimeout(function(){
                console.log(i)
            })
        })(i)
    }

    第3题

    // 我们整理下它的执行顺序
    var getName = undefined
    function Foo() {
        getName = function(){
            console.log("1");
        };
        return this;
    }
    function getName(){
        console.log("5");
    }
    Foo.getName = function() {
        console.log("2");
    };
    
    Foo.prototype.getName = function(){
        console.log("3");
    };
    getName = function() {
        console.log("4");
    }
    
    Foo.getName(); // 2 
    /*
    函数也是对象, Foo.getName 相当于给 Foo这个对象添加了一个静态方法 getName,我们调用的其实是这个静态方法,并不是调用的我们实例化的 getName
     */
    
    getName(); // 4  
    /*
    按照上面的执行顺序,其实这个就很好理解了,因为 `getName = function() { console.log("4"); }` 是最后一个赋值, 执行的应该是这个函数
     */
    
    Foo().getName(); // 1  
    /*
        这里为什么是 1 而不是我们想象的 3 呢?
        问题就是出在 调用的是 Foo(); 并没有使用 new 这个关键字,所以那时候返回的 this 指向的并不是 Foo, 而是 window;
        至于为什么不用 new 返回的 this 不指向 Foo, 这个随便去哪查一下就好, 就不在这介绍了
     */
    
    getName(); // 1
    /*
        这里为什么也是1 呢?  
        其实原因就是 上面我们调用了 `Foo().getName();` 这个方法引起的, 因为我们执行了 Foo 函数, 触发了
        getName = function(){
            console.log("1");
        }
        这段代码, 而且并没有在Foo里面声明  getName 变量, 于是就一直往上查找, 找到外部的 getName 变量 并赋值给它.
        所以这里调用 getName() 方法时, 它的值已经变成
        getName = function(){
            console.log("1");
        } 了
     */
    
    new Foo.getName(); // 2
    /*这个时候还是没有实例化, 调用的还是它的静态方法*/
    
    new Foo().getName(); // 3
    /*因为实例化了,所以调的是原型上的方法*/

我记得看到过几个经典的例子,找了半天没找到, 暂时就这些吧.。

2. == 与 === 的区别

  • 相同点:
     它们两个运算符都允许任意类型的的操作数,如果操作数相等,返回true,否则返回false

  • 不同点:
    ==:运算符称作相等,用来检测两个操作数是否相等,这里的相等定义的非常宽松,可以允许进行类型转换
    ===:用来检测两个操作数是否严格相等,不会进行类型转换

  • == 转换规则

  1. 首先看双等号前后有没有NaN,如果存在NaN,一律返回false。

  2. 再看双等号前后有没有布尔,有布尔就将布尔转换为数字。(false是0,true是1)

  3. 接着看双等号前后有没有字符串, 有三种情况:
     a. 对方是对象,对象使用toString()或者valueOf()进行转换;
     b. 对方是数字,字符串转数字;
     c. 对方是字符串,直接比较;
     d. 其他返回false

  4. 如果是数字,对方是对象,对象取valueOf()或者toString()进行比较, 其他一律返回false

  5. null, undefined不会进行类型转换, 但它们俩相等

// 不同类型,相同值
var a = 1
var b = &#39;1&#39;
console.log(a == b) // => true 
console.log(a === b) // => false 

// 对象和字符串
console.log([1,2,3] == &#39;1,2,3&#39;) // => true  因为 [1,2,3]调用了 toString()方法进行转换

// 对象和布尔
console.log([] == true)  // => false  []转换为字符串&#39;&#39;,然后转换为数字0, true 转换成1

// 对象和数字
console.log([&#39;1&#39;] == 1) // => true []转换为字符串&#39;1&#39;
console.log(2 == {valueOf: function(){return 2}}) // => true  调用了 valueOf()方法进行转换

// null, undefined 不会进行类型转换,  但它们俩相等
console.log(null == 1) // => false
console.log(null == 0) // => false
console.log(undefined == 1) // => false
console.log(undefined == 0) // => false
console.log(null == false) // => false
console.log(undefined == false) // => false
console.log(null == undefined) // => true 
console.log(null === undefined) // => false

// NaN 跟任何东西都不相等(包括自己)
console.log(NaN == NaN) // => false
console.log(NaN === NaN) // => false

下面几张图表示这些 == === 的关系

==
JavaScript 지식 요약

===
JavaScript 지식 요약

3. toSting 和 valueOf

所有对象继承了这两个转换方法
toString: 返回一个反映这个对象的字符串
valueOf

1. var, function, let, const 명령의 차이점
  • var를 사용하여 선언한 변수는 해당 구문이 위치한 함수 내에서 범위를 가지며, 변수 승격 현상이 있습니다
  • 변수 let을 사용하여 선언했으며 해당 범위는 문이 있는 코드 블록 내에 있습니다. 프로모션은 없습니다.
  • const를 사용하여 상수를 선언했습니다. 스택에 있는 상수의 값과 주소는 해당 코드에서 수정할 수 없습니다. 나중에 나타납니다
  1. 함수를 사용하여 선언한 함수의 범위는 해당 명령문이 위치한 함수 내이고, 함수 호이스팅 현상이 있습니다
var

var arr = [1,2,3]
var obj = {
    a: 1,
    b: 2
}
console.log(arr.toString()) // => 1,2,3
console.log(obj.toString()) // => [object Object]
// 那我们修改一下它原型上的 toString 方法呢
Array.prototype.toString = function(){ return 123 }
Object.prototype.toString = function(){ return 456 }
console.log(arr.toString()) // => 123
console.log(obj.toString()) // => 456

// 我们看下其余类型转换出来的结果, 基本都是转换成了字符串
console.log((new Date).toString()) // => Mon Feb 05 2018 17:45:47 GMT+0800 (中国标准时间)
console.log(/\d+/g.toString()) // => "/\d+/g"
console.log((new RegExp(&#39;asdad&#39;, &#39;ig&#39;)).toString()) // => "/asdad/gi"
console.log(true.toString()) // => "true"
console.log(false.toString()) // => "false"
console.log(function(){console.log(1)}.toString()) // => "function (){console.log(1)}"
console.log(Math.random().toString()) // => "0.2609205380591437"

  • let

    var arr = [1,2,3]
    var obj = {
        a: 1,
        b: 2
    }
    console.log(arr.valueOf()) // => [1, 2, 3]
    console.log(obj.valueOf()) // => {a: 1, b: 2}
    // 证明valueOf返回的是自身的原始值
    // 同样我们修改下 valueOf 方法
    
    Array.prototype.valueOf = function(){ return 123 }
    Object.prototype.valueOf = function(){ return 456 }
    console.log(arr.valueOf()) // => 123
    console.log(obj.valueOf()) // => 456
    
    // valueOf转化出来的基本都是原始值,复杂数据类型Object返回都是本身,除了Date 返回的是时间戳
    console.log((new Date).valueOf()) // => 1517824550394  //返回的并不是字符串的世界时间了,而是时间戳
    console.log(/\d+/g.valueOf()) // => 456  当我们不设置时valueOf时,正常返回的正则表式本身:/\d+/g,只是我们设置了 Object.prototype.valueOf 所以返回的时:456
    console.log(Math.valueOf()) // => 456 同上
    console.log(function(){console.log(1)}.valueOf()) // => 456 同上

    const
  • var a = {
        toString: function() {
            console.log(&#39;你调用了a的toString函数&#39;)
            return 8
        }
    }
    console.log( ++a) 
    // 你调用了a的toString函数 
    // 9  
    // 当你设置了 toString 方法, 没有设置 valueOf 方法时,会调用toString方法,无视valueOf方法
    • function

      var a = {
          num: 10,
          toString: function() {
              console.log('你调用了a的toString函数')
              return 8
          },
          valueOf: function() {
              console.log('你调用了a的valueOf函数')
              return this.num
          }
      }
      console.log( ++a) 
      // 你调用了a的valueOf函数
      // 11
      // 而当你两者都设置了的时候,会优先取valueOf方法, 不会执行toString方法
    • 전형적인 면접 질문

  • var a = true,b = false, c = true, d = false
    var str = &#39;none&#39;
    if (b || d || a) {
        str = &#39;现在是 ||&#39;
    }
    console.log(str) // => &#39;现在是 ||&#39;  ,因为其中a为true所有满足条件
    
    var str = &#39;none&#39;
    if (b || d ) {
        str = &#39;现在是 ||&#39;
    }
    console.log(str) // => &#39;none&#39; ,因为b,d都是false, 不满足条件
    
    var str = &#39;none&#39;
    if (a && c && d) {
        str = &#39;现在是 &&&#39;
    }
    console.log(str) // => &#39;none&#39; ,因为d是false, 其中有一个false就不满足条件
    
    var str = &#39;none&#39;
    if (a && c) {
        str = &#39;现在是 &&&#39;
    }
    console.log(str) // => &#39;现在是 &&&#39; ,因为b,d都是true, 满足条件

    // 0을 출력하는 방법 - 9
  • var a = true,b = false, c = true, d = false
    var str = &#39;none&#39;
    if (b || d || a) { str = &#39;现在是 ||&#39; }
    console.log(str) // => &#39;现在是 ||&#39;  ,因为其中a为true所有满足条件
    
    var str = &#39;none&#39;
    if (b || d ) { str = &#39;现在是 ||&#39; }
    console.log(str) // => &#39;none&#39; ,因为b,d都是false, 不满足条件
    
    var str = &#39;none&#39;
    if (a && c && d) { str = &#39;现在是 &&&#39; }
    console.log(str) // => &#39;none&#39; ,因为d是false, 其中有一个false就不满足条件
    
    var str = &#39;none&#39;
    if (a && c) { str = &#39;现在是 &&&#39; }
    console.log(str) // => &#39;现在是 &&&#39; ,因为b,d都是true, 满足条件
    • var a = false, b = true
      console.log(a && b) // => false  只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值
      console.log(b && a) // => false  只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值



      답변 :

      첫 번째 질문 em>
    • var name = &#39;小刚&#39;
      var person = {
          name: &#39;小明&#39;,
          fn: function() {
              console.log(this.name + &#39;撸代码&#39;)
          }
      }
      person.fn() // => 小明撸代码
      // 如何把它变成  “小刚撸代码”  呢?
      
      // 我们可以用 call/bind/apply 分别来实现
      person.fn.call(window) // => 小刚撸代码
      person.fn.apply(window) // => 小刚撸代码
      person.fn.bind(window)() // => 小刚撸代码
    • 질문 2

      obj.call(thisObj, arg1, arg2, ...)
      obj.apply(thisObj, [arg1, arg2, ...])
      // 通过上面的参数我们可以看出, 它们之间的区别是apply接受的是数组参数,call接受的是连续参数。
      // 于是我们修改上面的函数来验证它们的区别
      
      var person = {
          name: &#39;小明&#39;,
          fn: function(a,b) {
              if ({}.toString.call(a).slice(8, -1) === &#39;Array&#39;) {
                  console.log(this.name+&#39;,&#39;+a.toString()+&#39;撸代码&#39;)
              }else{
                  console.log(this.name+&#39;,&#39;+a+&#39;,&#39;+b+&#39;撸代码&#39;)
              } 
          }
      }
      
      person.fn.call(this, &#39;小红&#39;, &#39;小黑&#39; ) // => 小刚,小红,小黑撸代码
      person.fn.apply(this, [&#39;小李&#39;, &#39;小谢&#39;]) // => 小刚,小李,小谢撸代码
      질문 3
      var name = "小红"
      var obj = {
          name: &#39;小明&#39;,
          fn: function(){
              console.log(&#39;我是&#39;+this.name)
          }
      }
      setTimeout(obj.fn, 1000) // => 我是小红
      // 我们可以用bind方法打印出 "我是小明"
      setTimeout(obj.fn.bind(obj), 1000) // => 我是小明
      // 这个地方就不能用 call 或 apply 了, 不然我们把函数刚一方去就执行了
      
      // 注意: bind()函数是在 ECMA-262 第五版才被加入
      // 所以 你想兼容低版本的话 ,得需要自己实现 bind 函数
      Function.prototype.bind = function (oThis) {
          if (typeof this !== "function") {
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
          }
      
          var aArgs = Array.prototype.slice.call(arguments, 1), 
              fToBind = this, 
              fNOP = function () {},
              fBound = function () {
                return fToBind.apply(
                    this instanceof fNOP && oThis ? this : oThis || window,
                    aArgs.concat(Array.prototype.slice.call(arguments))
                );
              };
      
          fNOP.prototype = this.prototype;
          fBound.prototype = new fNOP();
      
          return fBound;
      };


    • 몇 가지 고전적인 예를 본 기억이 나지만 검색 후에도 찾을 수 없었습니다. 오랜만이므로 지금은 여기까지입니다.

2. ==와 ===의 차이점


같은 점:

두 연산자 모두 피연산자가 같으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.🎜🎜 🎜🎜🎜차이점 : 🎜🎜🎜==: 🎜 연산자는 같음이라고 하며 두 피연산자가 같은지 여부를 감지하는 데 사용됩니다. 여기서 같음의 정의는 매우 느슨하며 유형 변환을 허용합니다. 🎜🎜===: 🎜 사용하여 두 피연산자가 같은지 여부를 감지합니다. 🎜🎜🎜🎜🎜== 변환 규칙 🎜🎜🎜🎜🎜🎜먼저 이중 등호 앞뒤에 NaN이 있는지 확인하세요. NaN이 있으면 항상 false를 반환합니다. 🎜🎜🎜🎜이중 등호 앞뒤에 부울이 있는지 확인하세요. 부울이 있으면 부울을 숫자로 변환하세요. (false는 0, true는 1) 🎜🎜🎜🎜 그런 다음 이중 등호 앞뒤에 문자열이 있는지 확인하십시오. 세 가지 상황이 있습니다. 🎜 a. 상대방이 객체이고 toString을 사용하여 객체를 변환합니다. () 또는 valueOf(); b. 상대방이 숫자인 경우 문자열로 변환합니다. 🎜 c. 상대방이 문자열인 경우 🎜 d. 그렇지 않으면 false를 반환합니다. 은 숫자이고, 상대방은 객체이고, 객체는 비교를 위해 valueOf() 또는 toString()을 사용합니다. 다른 것들은 false🎜🎜🎜🎜null을 반환하고, 정의되지 않은 것은 유형 변환을 수행하지 않지만 동일합니다🎜🎜🎜🎜
var data;
$.ajax({
    ...
    success: function(data) {
        data = data
    }
})
console.log(data)
🎜다음 그림은 이들 == ===🎜🎜🎜==🎜🎜JavaScript 지식 요약🎜🎜🎜=== 🎜🎜JavaScript 지식 요약🎜 🎜3. toSting 및 valueOf🎜🎜🎜모든 객체는 이 두 가지 변환 방법을 상속합니다🎜🎜toString: 이 객체를 반영하는 문자열을 반환합니다.🎜valueOf: 해당하는 🎜🎜🎜🎜toString🎜
// 只有一个callback的时候
function fn(callback) {
    setTimeout(function(){
        callback && callback()
    }, 1000)
}
fn(function(){
    console.log(1)
})

// 一旦我们多几个呢?
function fn(a){ // 传入a  返回a1
    function fn1(a1){
        function fn2(a2){
            function fn3(a3){
                console.log(a3)
                ....
            }
        }
    }
}
// 当项目一复杂,这滋味。。。
🎜🎜🎜valueOf🎜
let promise = new Promise( (resolve, reject) => {
    setTimeout(function(){
        resolve(1)
    }, 1000)
})
promise.then( res => {
    console.log(res)// 一秒之后打印1
})
🎜🎜🎜toString 및 valueOf 인스턴스 🎜🎜🎜🎜의 원래 값을 반환합니다. 🎜
const fn = a => {
    return Promise.resolve(a)
}
const fn1 = a => {
    return Promise.resolve(a)
}
const fn2 = a => {
    // return Promise.resolve(a)
    return new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(a)
        },1000)
    })
}
const fn3 = a => {
    // return Promise.resolve(a)
    return new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(a)
        },1000)
    })
}
fn(123)
    .then(fn1)
    .then(fn2)
    .then(fn3)
    .then( res => {
        console.log(res) // => 123
    })
🎜🎜
const promise = new Promise( (resolve, reject) => {
    setTimeout(function(){
        resolve(222)
    }, 1000)
})
console.log(111)
promise.then( res => {
    console.log(res)
})
console.log(333)
🎜🎜🎜4. || "||"와 "&&"의 차이점은 🎜🎜🎜🎜🎜🎜" 중 참이면 조건이 충족됩니다🎜🎜🎜🎜"&&" 조건을 만족하려면 모든 조건이 참이어야 합니다🎜
const fn = () => {
    return new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(222)
        }, 1000)
    })
}
(async function(){
    console.log(111)
    let data = await fn()
    console.log(data)
    console.log(333)
})()
// 是不是返回 111 => 222 => 333 了呢

// 我们来试下返回别的东西, 不返回 promise
const fn = () => {
    return new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(222)
        }, 1000)
    })
}
(async function(){
    console.log(111)
    let data = await fn()
    console.log(data)
    console.log(333)
})()
// 打印结果: 111 => null => 333 => 222
// 当我们不是在await 关键字后面返回的不是 promise 对象时, 它就不会在原地等待 promise执行完再执行, 而是向正常的JS一样执行,把异步任务跳过去
🎜🎜🎜🎜🎜단락 원리: 🎜🎜🎜🎜🎜🎜🎜|| :🎜 1. 앞에 "||"가 true이면 결과는 "||" 앞에 있는 값을 반환합니다. 🎜 2. 앞에 " ||"가 false이면 결과는 "| |"를 입력하면 다음 값이 반환됩니다🎜
const fn = () => {
    let promise = new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(222)
        }, 1000)
    })
}

// 这样是不行的,会报错,因为的await关键字的父函数不是 async 函数
const grand = async () => {
    return function parent() {
        let data = await fn()
    }
}

// 这样才行,因为await 的父函数 是一个 async 函数
const grand = () => {
    return async function parent() {
        let data = await fn()
    }
}
🎜🎜🎜🎜&& (with)🎜:🎜 1. "&&"가 false 앞에만 있으면 "&&" 뒤에 true 또는 false가 오든 관계없이, 결과는 "&&" 이전의 값을 반환합니다🎜 2. "&&" 이전의 값이 true인 한, "&&" 이후의 값이 true이든 false이든 관계없이 결과는 "&&" 이후의 값을 반환합니다🎜
//求和
function add (a, b, c) {
    return a + b + c
}
add(1,2,3)
🎜🎜🎜🎜5. call /bind/apply의 차이점🎜
function add (a, b) {
    return function (c) {
        return a + b + c
    }
}
var sum = add(1, 2)
sum(3)
sum(4)
🎜물론 call과 Apply가 더 유사하고, Bind는 형태가 다릅니다🎜 그럼 call과 Apply의 차이점은 무엇인가요?🎜
function add (a) {
    return function (b) {
        return function (c) {
            return a + b + c
        }
    }
}
🎜그렇다면 바인딩과 호출의 차이점, 적용? 🎜 호출과 적용의 차이점은 바인딩 직후에 바인딩이 실행되지 않는다는 것입니다. 함수의 이 지점만 결정한 다음 함수를 반환합니다🎜
var name = "小红"
var obj = {
    name: &#39;小明&#39;,
    fn: function(){
        console.log(&#39;我是&#39;+this.name)
    }
}
setTimeout(obj.fn, 1000) // => 我是小红
// 我们可以用bind方法打印出 "我是小明"
setTimeout(obj.fn.bind(obj), 1000) // => 我是小明
// 这个地方就不能用 call 或 apply 了, 不然我们把函数刚一方去就执行了

// 注意: bind()函数是在 ECMA-262 第五版才被加入
// 所以 你想兼容低版本的话 ,得需要自己实现 bind 函数
Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(
              this instanceof fNOP && oThis ? this : oThis || window,
              aArgs.concat(Array.prototype.slice.call(arguments))
          );
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
};

6. callback 、 promise 、 async/await

这三个东西牵涉到的可能就是我们最常见到的 “同步”、“异步”、“任务队列”、“事件循环”  这几个概念了


    • 例:

      var data;
      $.ajax({
          ...
          success: function(data) {
              data = data
          }
      })
      console.log(data)

      当我们从服务器获取到数据的时候,为什么打印出来的是undefined ?
      解决这个问题之前我们先来了解javascript的运行环境

      JavaScript是单线程语言,JS中所有的任务可以分为两种:同步任务和异步任务。

    • 同步任务:
      意思是我必须做完第一件事,才能做第二件事,按照顺序一件一件往下执行(在主线程上)

    • 异步任务:
      假如我第一件事需要花费 10s, 但是我第二件事急着要做, 于是我们就把第一件事告诉主线程,然后主线程暂停先放到某个地方, 等把第二件事完成之后,再去那个地方执行第一件事,第一件事也就可以理解为异步任务

    • 任务队列(task queue):
      任务队列是干嘛的呢; 上面我们说了异步任务的情况,  我们把第一件放到某个地方, 那某个地方是什么地方呢,就是 “任务队列” 这个东西。里面乘放的是所有异步任务。

    • Event Loop(事件循环)
      当主线程上面所有同步任务执行完之后,主线程就会向任务队列中读取异步任务(队列方法:先进先出)
      而且是一直重复向任务队列中,即使没有任务。它也会一直去轮询。
      只不过在任务列表里面没有任务的时候, 主线程只需要稍微过一遍就行, 一旦遇到任务队列里面有任务的时候,就会去执行它
      也就是说在我们打开网页的时候,JS引擎会一直执行事件循环,直到网页关闭

      如图所示
      JavaScript 지식 요약

      由此,上面为什么会产生 undefined的原因了, 因为ajax 是异步任务,而我们console.log(data)是同步任务,所以先执行的同步任务,才会去执行 ajax

      说了这么多,我们来看下 为什么我们很需要 从 callback => promise => async/await

      因为很多时候我们需要把一个异步任务的返回值,传递给下一个函数,而且有时候是连续的n个

  1. callback

    // 只有一个callback的时候
    function fn(callback) {
        setTimeout(function(){
            callback && callback()
        }, 1000)
    }
    fn(function(){
        console.log(1)
    })
    
    // 一旦我们多几个呢?
    function fn(a){ // 传入a  返回a1
        function fn1(a1){
            function fn2(a2){
                function fn3(a3){
                    console.log(a3)
                    ....
                }
            }
        }
    }
    // 当项目一复杂,这滋味。。。
  2. Promise

  • 什么是promise?
    Promise是异步编程的一种解决方案,同时也是ES6的内置对象,它有三种状态:

  • Promise方法

  • 基本用法

    let promise = new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(1)
        }, 1000)
    })
    promise.then( res => {
        console.log(res)// 一秒之后打印1
    })
  • 我们把上面的回调地狱转换下

    const fn = a => {
        return Promise.resolve(a)
    }
    const fn1 = a => {
        return Promise.resolve(a)
    }
    const fn2 = a => {
        // return Promise.resolve(a)
        return new Promise( (resolve, reject) => {
            setTimeout(function(){
                resolve(a)
            },1000)
        })
    }
    const fn3 = a => {
        // return Promise.resolve(a)
        return new Promise( (resolve, reject) => {
            setTimeout(function(){
                resolve(a)
            },1000)
        })
    }
    fn(123)
        .then(fn1)
        .then(fn2)
        .then(fn3)
        .then( res => {
            console.log(res) // => 123
        })

    这样就简单明了多了, 我们就不需要一层一层嵌套callback了,可以通过链式调用来解决callback的问题

    然而,仅仅这样还是觉得不够好
    因为这种面条式调用还是让人很不爽,而且 then 方法里面虽然是按先后顺序来的,但是其本身还是异步的
    看下面这段代码

    const promise = new Promise( (resolve, reject) => {
        setTimeout(function(){
            resolve(222)
        }, 1000)
    })
    console.log(111)
    promise.then( res => {
        console.log(res)
    })
    console.log(333)

    打印结果依然还是 111 => 333 => 222, 并不是我们想象的 111 => 222 => 333
    依然不适合单线程的思维模式。所以下一个解决方案 又出现了

  1. Promise.prototype.then()  接收两个函数,一个是处理成功后的函数,一个是处理错误结果的函数。可以进行链式调用

  2. Promise.prototype.catch() 捕获异步操作时出现的异常, 一般我们用来代替.then方法的第二个参数

  3. Promise.resolve()  接受一个参数值,可以是普通的值, 会返回到对应的Promise的then方法上

  4. Promise.reject()  接受一个参数值,可以是普通的值, 会返回到对应的Promise的catch方法上或着then方法的第二个参数上

  5. Promise.all() 接收一个参数,它必须是可以迭代的,比如数组。通常用来处理一些并发的异步操作。成功调用后返回一个数组,数组的值是有序的,即按照传入参数的数组的值操作后返回的结果

  6. Promise.race() 接收一个可以迭代的参数,比如数组。但是只要其中有一个执行了,就算执行完了,不管是成功还是失败。

  7. pending: 进行中

  8. resolved: 已完成

  9. rejected:已失败

  • async/await
    这是ES7的语法,当然,在现在这种工程化的时代,基本babel编译之后也都是能在项目中引用的

    • 基本用法跟规则
      async 表示这是一个async函数,
      await只能用在这个函数里面。后面应该跟着是 Promise 对象, 不跟的话也没关系, 但是await就不会在这里等待了
      await 表示在这里等待promise返回结果

      例:

      const fn = () => {
          return new Promise( (resolve, reject) => {
              setTimeout(function(){
                  resolve(222)
              }, 1000)
          })
      }
      (async function(){
          console.log(111)
          let data = await fn()
          console.log(data)
          console.log(333)
      })()
      // 是不是返回 111 => 222 => 333 了呢
      
      // 我们来试下返回别的东西, 不返回 promise
      const fn = () => {
          return new Promise( (resolve, reject) => {
              setTimeout(function(){
                  resolve(222)
              }, 1000)
          })
      }
      (async function(){
          console.log(111)
          let data = await fn()
          console.log(data)
          console.log(333)
      })()
      // 打印结果: 111 => null => 333 => 222
      // 当我们不是在await 关键字后面返回的不是 promise 对象时, 它就不会在原地等待 promise执行完再执行, 而是向正常的JS一样执行,把异步任务跳过去
    • await 关键字必须包裹在 async 函数里面,而且async 函数必须是它的父函数

      const fn = () => {
          let promise = new Promise( (resolve, reject) => {
              setTimeout(function(){
                  resolve(222)
              }, 1000)
          })
      }
      
      // 这样是不行的,会报错,因为的await关键字的父函数不是 async 函数
      const grand = async () => {
          return function parent() {
              let data = await fn()
          }
      }
      
      // 这样才行,因为await 的父函数 是一个 async 函数
      const grand = () => {
          return async function parent() {
              let data = await fn()
          }
      }

    7. 柯里化 与 反柯里化

    • 柯里化
       函数柯里化就是对高阶函数的降阶处理。
       柯里化简单的说,就是把 n 个参数的函数,变成只接受一个参数的 n 个函数
      function(arg1,arg2)变成function(arg1)(arg2)
      function(arg1,arg2,arg3)变成function(arg1)(arg2)(arg3)
      function(arg1,arg2,arg3,arg4)变成function(arg1)(arg2)(arg3)(arg4)

      • 柯里化有什么作用

      • 例:

        //求和
        function add (a, b, c) {
            return a + b + c
        }
        add(1,2,3)

        如果我只改变 c 的值,在求和
        add(1,2,4) 是不是得多出重新计算  a + b 的部分
         我们是不是可以提前返回a+b的值, 然后只传入 c 的值进行计算就行了
         修改一下方法

        function add (a, b) {
            return function (c) {
                return a + b + c
            }
        }
        var sum = add(1, 2)
        sum(3)
        sum(4)

        在此基础上我们在做下修改

        function add (a) {
            return function (b) {
                return function (c) {
                    return a + b + c
                }
            }
        }

        这样我们是不是可以随时复用某个参数,并且控制在某个阶段提前返回

        还有一个经典的例子

        var addEvent = function(el, type, fn, capture) {
            if (window.addEventListener) {
                el.addEventListener(type, function(e) {
                    fn.call(el, e);
                }, capture);
            } else if (window.attachEvent) {
                el.attachEvent("on" + type, function(e) {
                    fn.call(el, e);
                });
            } 
        };

        我们每次调用事件时,都需要判断兼容问题, 但我们运用柯里化的方式就只要判断一次就行了

        var addEvent = (function(){
            if (window.addEventListener) {
                return function(el, sType, fn, capture) {
                    el.addEventListener(sType, function(e) {
                        fn.call(el, e);
                    }, (capture));
                };
            } else if (window.attachEvent) {
                return function(el, sType, fn, capture) {
                    el.attachEvent("on" + sType, function(e) {
                        fn.call(el, e);
                    });
                };
            }
        })();

        还有一个作用就是延迟计算

        小明每天都会花一部分钱吃饭
         小明想知道它5天之后总共会花费多少钱

        var total = 0
        var fn = function(num) {
            total += num
        }
        fn(50)
        fn(70)
        fn(60)
        fn(100)
        fn(80)

        这样我们便能算出它总共花了都少钱

        但是小明又突然想知道 如果他每天花费的的钱翻一倍 会产生多少钱
         于是我们是不是得改下 上面的 函数

        var fn = function(num) {
            total += num*2
        }
        fn(50)
        fn(70)
        fn(60)
        fn(100)
        fn(80)

        那我们是不是有什么办法,先把这些数 存起来,到最后在进行计算
         我们接着来封装

        var curry = function(fn) {
            var args = []
            return function() {
                if (arguments.length === 0) {
                    return fn.apply(null, args)
                }else{
                    args = args.concat([].slice.call(arguments))
                    return curry.call(null, fn, args)
                }
            }
        }
        
        var curryFn = function() {
            var args = [].slice.call(arguments),
                total = 0
            for (var i = 0; i 9dbef05f8660b044d4e176bb951d5597 args.length) {
                    var newArgs = [fn].concat(args);
                    return curry(sub_curry.apply(null,newArgs), length - args.length)
                }else{
                    fn.apply(null,arguments)
                }
            }
        }
        // 1.
        var fn  = curry( function(a,b,c){
            console.log(a, b, c)
        })
        fn('a')('b')('c')
        
        // 2.
        fn1 = curry(function(){
            console.log(arguments)
        }, 3)
        fn1('a')('b')('c')
    1. 参数复用;

    2. 提前返回;

    3. 延迟计算/运行

  • 反柯里化
     反柯里化的作用在与扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用.

    被任意对象使用? 是不是想到了用call, apply 设置this指向

    • 通过 call/apply  被任意对象所用

      var obj = {
          a: 1,
          fn: function (b) {
              return this.a + b
          }
      }
      obj.fn(2) // 3
      var obj1 = {a:4}
      obj.fn.call(obj1, 2) // 6
    • 反柯里化版本

      var uncurrying= function (fn) {
          return function () {
              var context=[].shift.call(arguments);
              return fn.apply(context,arguments);
          }
      }
      // const uncurrying = fn => (...args) => Function.prototype.call.apply(fn,args) // 简洁版
      var f = function (b) {
          return this.a + b
      }
      var uncurry = uncurrying(f)
      var obj = {a:1},
          obj1 = {a:4}
      uncurry(obj, 2) // 3
      uncurry(obj1, 2) // 3

    相信大家已经看出区别了,这丫的就相当于一个外部的call方法

    总结

    上面很多只是自己的部分理解,不一定准确。如果有不同理解,谢谢指出。

    相关推荐:

    最全JavaScript知识点总结

    一些容易犯错的JavaScript知识点整理

    JavaScript知识点系统总结

    위 내용은 JavaScript 지식 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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