javascript 总结(那些剪不断理还乱的关系)

598
2月7日 · 2018年

前言

整理 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. 1、
    var a = 1
    function fn() {
     if (!a) {
         var a = 123
     }
     console.log(a)
    }
    fn() ?复制代码
  2. 2、// 如何依次打印出0 - 9
    for (var i = 0; i < 10; i++) {
     setTimeout(function(){
         console.log(i)
     })
    }复制代码
  3. 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 = nudefined
  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 = '1'
console.log(a == b) // => true
console.log(a === b) // => false

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

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

// 对象和数字
console.log(['1'] == 1) // => true []转换为字符串'1'
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


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

**==**
![图片描述](http://dzblog.cn/images/md/5a7e2d5497b55.jpg)

**===**
![图片描述](http://dzblog.cn/images/md/40f82997557ee.jpg)


## 3. toSting 和 valueOf
**所有对象继承了这两个转换方法**
`toString`: 返回一个反映这个对象的字符串
`valueOf`: 返回它相应的原始值


* toString

  ```
  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('asdad', 'ig')).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"
  ```

* valueOf

  ```
  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 同上 
  ```

* toString 和 valueOf 实例

1. 复制代码

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


2. 复制代码

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方法



## 4. || 和 && 的区别

* **如果以 “||” 和 “&&” 做条件判断的话**
  * “||” 只要其中有一个为 true 那么就满足条件
  * “&&” 必须要所有条件都为 true 才能满足条件

  ```
  var a = true,b = false, c = true, d = false
  var str = 'none'
  if (b || d || a) {
      str = '现在是 ||'
  }
  console.log(str) // => '现在是 ||'  ,因为其中a为true所有满足条件

  var str = 'none'
  if (b || d ) {
      str = '现在是 ||'
  }
  console.log(str) // => 'none' ,因为b,d都是false, 不满足条件

  var str = 'none'
  if (a && c && d) {
      str = '现在是 &&'
  }
  console.log(str) // => 'none' ,因为d是false, 其中有一个false就不满足条件

  var str = 'none'
  if (a && c) {
      str = '现在是 &&'
  }
  console.log(str) // => '现在是 &&' ,因为b,d都是true, 满足条件
  ```

* **短路原理:**
  * **||(或)**:
    * 只要“||”前面是true,结果会返回“||”前面的值
    * 如果“||”前面是false,结果都会“||”返回后面的值

    ```
    var a = true,b = false, c = true, d = false
    var str = 'none'
    if (b || d || a) { str = '现在是 ||' }
    console.log(str) // => '现在是 ||'  ,因为其中a为true所有满足条件

    var str = 'none'
    if (b || d ) { str = '现在是 ||' }
    console.log(str) // => 'none' ,因为b,d都是false, 不满足条件

    var str = 'none'
    if (a && c && d) { str = '现在是 &&' }
    console.log(str) // => 'none' ,因为d是false, 其中有一个false就不满足条件

    var str = 'none'
    if (a && c) { str = '现在是 &&' }
    console.log(str) // => '现在是 &&' ,因为b,d都是true, 满足条件
    ```

  * **&&(与)**:
    * 只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值
    * 只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值

    ```
    var a = false, b = true
    console.log(a && b) // => false  只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值
    console.log(b && a) // => false  只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值
    ```

## 5. call/bind/apply 的区别复制代码

var name = '小刚'
var person = {
name: '小明',
fn: function() {
console.log(this.name + '撸代码')
}
}
person.fn() // => 小明撸代码
// 如何把它变成 “小刚撸代码” 呢?

// 我们可以用 call/bind/apply 分别来实现
person.fn.call(window) // => 小刚撸代码
person.fn.apply(window) // => 小刚撸代码
person.fn.bind(window)() // => 小刚撸代码


显而易见,call 和 apply 更加类似,bind与两者形式不同
那 call 和 apply 的区别在哪呢?复制代码

obj.call(thisObj, arg1, arg2, ...)
obj.apply(thisObj, [arg1, arg2, ...])
// 通过上面的参数我们可以看出, 它们之间的区别是apply接受的是数组参数,call接受的是连续参数。
// 于是我们修改上面的函数来验证它们的区别

var person = {
name: '小明',
fn: function(a,b) {
if ({}.toString.call(a).slice(8, -1) === 'Array') {
console.log(this.name+','+a.toString()+'撸代码')
}else{
console.log(this.name+','+a+','+b+'撸代码')
}
}
}

person.fn.call(this, '小红', '小黑' ) // => 小刚,小红,小黑撸代码
person.fn.apply(this, ['小李', '小谢']) // => 小刚,小李,小谢撸代码


那么bind 与call,apply有什么区别呢 ? 
与call和apply不同的是,bind绑定后不会立即执行。它只会将该函数的 this 指向确定好,然后返回该函数

复制代码

var name = "小红"
var obj = {
name: '小明',
fn: function(){
console.log('我是'+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引擎会一直执行事件循环,直到网页关闭

  如图所示
  ![图片描述](http://dzblog.cn/images/md/61569c8b86094.png)

  由此,上面为什么会产生 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的内置对象,它有三种状态:*
    1. pending: 进行中
    2. resolved: 已完成
    3. rejected:已失败

* *Promise方法*
  1. Promise.prototype.then()  接收两个函数,一个是处理成功后的函数,一个是处理错误结果的函数。可以进行链式调用
  2. Promise.prototype.catch() 捕获异步操作时出现的异常, 一般我们用来代替.then方法的第二个参数
  3. Promise.resolve()  接受一个参数值,可以是普通的值, 会返回到对应的Promise的then方法上
  4. Promise.reject()  接受一个参数值,可以是普通的值, 会返回到对应的Promise的catch方法上或着then方法的第二个参数上
  3. Promise.all() 接收一个参数,它必须是可以迭代的,比如数组。通常用来处理一些并发的异步操作。成功调用后返回一个数组,数组的值是有序的,即按照传入参数的数组的值操作后返回的结果
  4. Promise.race() 接收一个可以迭代的参数,比如数组。但是只要其中有一个执行了,就算执行完了,不管是成功还是失败。

* 基本用法

  ```
  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
  依然不适合单线程的思维模式。所以下一个解决方案 又出现了

3. **`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)`

  * 柯里化有什么作用
    1. 参数复用;
    2. 提前返回;
    3. 延迟计算/运行

  * 例:
    
    ```
    //求和
    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 < args.length; i++) {
            total += args[i]
        }
        return total
    }
    var fn = curry(curryFn)
    fn(50)
    fn(70)
    fn(60)
    fn(100)
    fn(80)

    fn() //不传参数的时候进行计算
    ```
    这样我们只有最后的时候才进行计算。
    而且只需要修改 curryFn 里面的计算方法就行

    我们整理下上面的方法封装完整的柯里化函数

    ``` 
    var curry = function (fn, length) {
        length = length || fn.length;
        var sub_curry = function (f) {
            var args = [].slice.call(arguments, 1);
            return function () {
                return f.apply(null, args.concat([].slice.call(arguments)))
            }
        }
        return function () {
            var args = [].slice.call(arguments);
            if (length > 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')
    ```
* 反柯里化
  反柯里化的作用在与扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用.

  被任意对象使用? 是不是想到了用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方法**

## 总结
上面很多只是自己的部分理解,不一定准确。如果有不同理解,谢谢指出。
复制代码
  • 表情
  • 预览
可使用部分markdown语法
dddd

666 改版了

    学习了

      学习了

        学习了

          学习了

            大神到访数:25189