【重学】尾递归,数组查找模拟实现

大纲
尾调用
尾递归
数组查找指定元素模拟实现:
find
findIndex
target, currentTarget,addEventListener监听函数中的this指向
函数复习
函数名,变量名,形参提升优先级

尾调用

尾调用: 函数执行的最后一个步骤,是返回另一个函数的调用,叫尾调用
优点:   
1. 尾调用,当里层函数被调用时,外层函数已经执行完,出栈了,不会造成内存泄漏
2. 在递归中,尾调用使得栈中只有一个函数在运行,不会造成性能问题


f(x) {
  return g(x)
}
// 尾调用,因为返回g(x)调用的时候,f(x)已经执行完


f(x) {
  return g(x) + 1
}
// 非尾调用,因为返回 g(x) 调用时,f(x)并未执行完,当f(x)执行完后,还有执行 g(x)+1,f(x)才执行完
// 函数只有执行完后才会出栈(执行上下文调用栈)


const a = x => x ? f() : g();
// f()和g()都是尾调用

const a = () => f() || g()
// f()非尾调用,还要接着判断

const a = () => f() && g();
// f()非尾调用

递归

递归  -- 尾递归和尾调用

1. 构成递归的条件
  - 边界条件
  - 递归前进段
  - 递归返回段
  - 当边界条件不满足时,递归前进
  - 当边界条件满足时,递归返回

2. 
Recursive:递归
factorial:阶乘

3. 尾调用和非尾调用
  - 尾调用和非尾调用的区别是 执行上下文栈不一样
  - 为调用:调用在函数结尾处
  - 尾调用的执行上下文栈,外层函数执行完就出栈,不会一层一层嵌套,不造成内存溢出
  - 尾调用自身就叫尾递归
// 尾调用
// 因为调用g(x)时,f(x)已经执行完了,就会出栈,不会压栈,不会造成内存溢出
function f(x){
    return g(x);
}
// 非尾调用
// 因为调用g(x)时,f(x)并未执行完,g(x)+1需要g(x)函数执行完,才会相加,返回后f(x)才会执行完
function f(x){
    return g(x) + 1;
}



------------------------------------------------------------------------------------

+++(例1)阶乘
     // recursive递归
    function factorial (n) {
      if (n < 2) return n
      return n * factorial(n-1)
    }
    const res = factorial(3)
    // 1. 3 => 3 * factorial(2) => 3 * 2 * factorial(1) => 3 * 2 * 1  
(分析)
    1. 每次返回一个递归的函数,都会创建一个闭包
    2. 所以维护这么多执行上下文栈,开销大,用以造成内存泄漏
    3. 优化方法:尾调用


+++(例1升级)阶乘优化
    function factorial(n, res) {
      if (n === 1) {
        return res
      }
      return factorial(n-1, n * res)
    }
(分析)
    第一次:factorial(3, 4* 1)
    第二次:factorial(2, 3* 4)
    第三次:factorial(1, 2* 12)
    第四次:24


+++(例1再升级)阶乘优化,多传了一个参数,可以用函数柯里化或者偏函数来实现

    function factorial(res, n) {
      if (n === 1) return res;
      return factorial(n * res, n-1)
    }

    function curring (fn) {
      let par_arr = Array.prototype.slice.call(arguments, 1)
      const closure = function () {
        par_arr = par_arr.concat(Array.prototype.slice.call(arguments))
        console.log(par_arr, 'par_arr')
        if (par_arr.length < fn.length) {
          return closure
        }
        return fn.apply(null, par_arr)
      }
      return closure
    }
    const curringFactorial =  curring(factorial, 1)
    const res = curringFactorial(4)
    console.log(res)

数组查找指定元素模拟实现

(1)findIndex

1. find(fn, bindThisObj)
  - 数组实列的find方法,用于找出第一个符合条件的数组成员,返回值是该成员
  - 参数:是一个回调函数
  - 过程:所有数组成员作为参数,依次执行改回到函数,直到找出第一个返回值为true的成员,并返回该成员
  - 返回值
    - 如果找到该成员,返回该成员 ( 注意:是第一个返回值为true的成员 )
    - 如果未找到,返回undefined ( 注意:未找到是返回undefined )
  - find 和 findIndex可以发现NaN,弥补了indexOf 的不足
  - find 和 findIndex都可以接收第二个参数,用来绑定回调函数的this对象
const arr = [1, 2, 3, -4, -5, 6]
function fn (value, index, arr) { // 注意:回调函数的参数有三个:分别是 value, index, arr
  return value < 0
}
const res = arr.find(fn)
console.log(res)





2. findIndex(fn, bindThisObj)
  - findIndex返回第一个满足条件的成员的位置
  - 返回值
    - 第一个满足条件的成员的位置 (注意: 是返回成员在数组中的位置 - 下标 )
    - 如果未找到,返回 -1 ------(注意:findIndex()未找到是返回 -1 ,find()未找到是返回undefined )





3. findIndex的模拟实现
  - predicate function 判定函数
const arr = [1, 2, 3, -4, -5, 6, NaN]

function findIndex(arr, predicate, context) {
  // 参数:数组,判定函数,执行上下文对象
  for(let i = 0; i < arr.length; i++) {
    if (predicate.call(context, arr[i], i, arr)) {
    // 如果判断条件成立,就返回该数组成员在数组中的位置
    // 判断函数的参数:item, index, arr
    // 判定函数所绑定的this指向:context 
      return i
    }
  }
  return -1 // 如果执行到这里,说明for循环中没有返回值,即没有满足条件的数组成员,则返回 -1
}
function predicate (item, index, arr) {
  return item  === -5
}
const res = findIndex(arr, predicate, window)
console.log(res) // 4





4. findLastIndex
  - findexLashIndex 倒序查找
function findLastIndex(arr, predicate, context) {
  // 从尾部开始循环
  for(let i = arr.length - 1; i >= 0; i--) {
  // 判断条件:最后一项的位置arr.length -1
    if (predicate.call(context, arr[i], i, arr)) {
      return  i
    }
  }
  return -1
}
function predicate(item, index, arr) {
  return item === 3
}
const res = findLastIndex(arr, predicate, window)
console.log(res)





5. 需求
  - 因为 findIndex 和 findLastIndex存在很多重复的部分
  - 可以通过传参的不同,返回不同的函数
  - 重点?如何通过参数的不同,实现正序和倒序?

const arr = [1, 2, 3, 4, - 5, 6]

function createFinderIndex (dir) { // direction 方向
  return function (arr, predicate, context) {
    const length = arr.length
    let index = dir > 0 ? 0 : length -1 // 正序还是倒序的index

    for(; index >= 0 && index < length; index += dir) {
      // 第三个条件:正序+1,倒序-1
      if (predicate.call(context, arr[index], index, arr)) {
        return index
      }
    }
    return -1
  }
}

function predicate (item, index, arr) {
  return item === 4
}

const findIndex = createFinderIndex(1) // 利用闭包,返回一个函数
const resFindIndex = findIndex(arr, predicate, window)
console.log(resFindIndex)

const findLastIndex = createFinderIndex(-1)
const resFindLastIndex = findIndex(arr, predicate, window)
console.log(resFindLastIndex)










target 和 currentTarget

1. e.currentTarget --------------------------- 正在执行的监听函数所绑定的那个节点,不会变
2. e.target ---------------------------------- 事件最初触发的节点

3. addEventListener() ------------------------ 监听函数中的this,指向事件的监听函数所绑定对象,和 currentTarget一样

函数复习

1. 函数声明
  - function 关键词
  - 变量赋值,这个匿名函数就是 函数表达式
  - const fn = function go () {};
    - 函数表达式,需要在语句结尾加上分号,表示语句结束!!!!!!!!!!!!!!!!!
    - 一般使用函数表达式都不会加具体的函数名称
    - 加了名称go主要有两个作用:
      - 可以在函数内部调用自身
      - 方便排错
      - 注意:go只在函数内部有效,在函数外部无效

2. 函数重复声明
  - 如果函数重复声明,后面的声明会覆盖前面的声明

3. 第一等公民
  - 函数就是一个可执行的值
  - 函数可以作为一种值,凡是使用值的地方,都可以使用函数
  - 因为函数与其他数据类型平等,所以又称函数为 第一等公民

4. 函数名的提升
  - js把函数名视同为变量,所以函数名也存在函数名提升
------------------
f();
var f = function (){}; // TypeError: undefined is not a function 报错

以上代码相当于:

var f = undefined
f(); // 所以会报错 f不是函数
f = function() {}
------------------

5. 不能在条件语句中声明函数
  - 因为存在函数名提升和变量名提升
  - 如 if    try 

6. 函数的name属性: 返回函数的名字

7. 函数的length属性: 返回函数(形参)的长度

8. 函数的toString()返回函数的源码

9. 函数的作用域
  - 作用域:指变量存在的范围
  - es5中存在两种作用域
    - 全局作用域:变量在整个程序中一直存在,所以地方都能读取
    - 函数作用域:变量在函数内部有效,函数外部无法访问
  - 局部变量:函数内部声明的变量是局部变量,外部无法访问
  - 全局变量:函数内部可以读取
  - 注意:对于 var 命令来说,局部变量只在函数内部有效,在其他代码块中,是全局变量,因为存在变量提升 
var x = 10
function a () {
  var x = 20
  console.log (x, 'in') // 20
}
a()
console.log (x, 'out') // 10
---------------
if (true) {
  var x = 5;
}
console.log(x);  // ------------------------------------ 5
---------------
if (false) {
  var x = 5;
}
console.log(x); // -------------------------------------- undefined,变量提升
---------------

10. 函数本身的作用域
  - 函数本身也是一个值,也自己的作用域
  - 函数的作用域是其声明时所在的作用域,与其运行时所在的作用域无关
  - 总之,函数执行时所在的作用域,就是函数定义时的作用域,与函数调用时的作用域无关
  - 函数内部声明的函数,作用域绑定在函数内部
var x = 10
const a = function () {
  console.log (x) ------------- 10,函数的作用域是函数声明时所在的作用域,与函数调用时的作用域无关
}
const b = function () {
  var x = 20
  a ()
}
b ()
----------------------
function a () {
  var x = 10
  return function () { ----------- 函数内部声明的函数,作用域绑定在函数内部
    console.log (x)
  }
}
var x = 20
const b = a ()
b () ------------------------------ 函数的作用域,是函数定义时的作用域,与函数调用时的作用域无关

11. 参数
  - length: 表示函数形参的长度
  - arguments: 函数内部的arguments对象,表示实参的具体情况
  - 函数的实参,不能省略前面的参数,会报错,如果一定要省略,要显示的传递undefined

12. 参数的传递方式
  - 传值传递:
    - 函数的参数,如果是原始类型的值,(number, string, boolean),传递方式是传值传递
    - 在函数内部修改参数,不会影响原始值
  - 传址传递
    - 函数的参数,如果是引用类型的值,(object,arrar, function ...),传递方式是传址传递
    - 在函数内部修改参数,会影响原始值,(传递的是地址)
  - 注意:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    - 如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
-----------------------------
var p = 2;
function f(p) {
  p = 3;
}
f(p);
p // 2
相当于

var p = 2;
function f(p) { // ------ 函数参数是原始类型的值,传递方式是传值传递,函数内部修改参数,不影响原始值
  var p = 2 // --------------------------------- 局部变量,不影响函数外部
  p = 3;
}
f(p);
p // 2
-----------------------------

var obj = { p: 1 };
function f(o) { // ------ 函数的参数是引用类型的值,传递方式是传址传递,内部修改参数,会影响到原始值,修改的是地址
  o.p = 2;
}
f(obj);
obj.p // 2
-----------------------------

var obj = [1, 2, 3];
function f(o) {
  o = [2, 3, 4]; // ------ 替换掉了整个参数,而不是修改参数对象的某个属性,o指向了新地址,对源地址的值无影响
}
f(obj);
obj // [1, 2, 3]
-----------------------------

13. 同名参数
  - 如果有同名参数,取最后出现的那个值
  - 即使后面的参数没有值,或是被省略,也是以其为准
function f(a, a) {
  console.log(a);
}
f(1, 2) // 2
-----
function f(a, a) {
  console.log(a); // --------- 第二个参数实际传参时,被省略了,但是还是以其为准
}
f(1) // undefined

函数名,变量名,形参提升优先级

  • 优先级:
    函数形参 > 函数声明 > 变量声明
  • 函数名已经存在,新的覆盖旧的
    (如果函数名和已经存在的形参名,函数名相同的话,新的函数名将覆盖形参名或者覆盖旧的函数名)
  • 变量名已经存在,直接跳过变量声明
    (如果变量名和已经声明的形参名,函数名相同的话,则会直接跳过变量声明,不会影响已经存在的属性)
例一

function a (name, age) {
  console.log (name) // ------------------- name
  console.log (age) // -------------------- undefined
  var name = 'new name'
  console.log (name) // ------------------- new name
}
a ('name')


实际执行的代码如下:


function a (name, age) {
  var name = 'name'
  var age = undefined
  // var name = undefined ------------- 变量名name已经存在(形参),所以会直接跳过变量name的声明
  console.log (name) // 'name'
  console.log (age) // undefined
  name = 'new name'
  console.log(name) // 'new name'
}
a ('name')

例二

function a (name) {
  console.log (name)
  var name = '10'
  function name () {
    console.log (20)
  }
  console.log(name)
}
a ('woow-wu')


实际执行的代码如下:


function a (name) {
  var name = 'woow-wu' // ----------- 函数形参声明并赋值
  function name () { // ------------- 函数名存在,覆盖掉旧的形参
    console.log (20)
  }
  // var name = undefined // -------- 变量名存在,直接跳过变量的声明
  console.log(name) // ----------------------------------------------- 所以name 是 函数
  name = '10' // -------------------- name被重新赋值
  console.log(name) //------------------------------------------------ 所以打印是 '10'
}
a ('woow-wu')
例三

function a(name) {
    var name = 10;
    function name() {
        console.log('20')
    };
    console.log(name); // 10
}
a('wang')


实际执行的代码如下:


function a(name) {
    var name = 'wang';
    function name() { // -------------- 函数名已经存在,覆盖上面的
        console.log('20')
    };
  // var name = undefined  ---------- 变量已经存在跳过
    name = 10; // --------------------- 执行阶从新赋值
    console.log(name); // ------------- 一定要注意打印的位置是创建阶段还是执行阶段!!!!!
}
a('wang')
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,521评论 0 5
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,505评论 1 51
  • #1.函数基础1.1 局部对象1.2 函数声明1.3 分离式编译 #2.参数传递2.1 传值参数2.2 传引用参数...
    MrDecoder阅读 585评论 0 1
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,733评论 0 38
  • 02店破百分享22,13,5,5。 1:分解任务,每组40度。 2:整合客资做到有效邀约,认真率自己的客资。3:整...
    黄睿凯涵阅读 84评论 0 0