03-JS函数事件

1.函数的分类

  • 命名函数(具名函数)

    • 具名函数和script标签同时解析,可以先调用

      foo();      //不推荐这样
      function foo(){
          console.log(1);
      }
      //输出结果:1
      
    • 具名函数,函数名的使用范围仅限于函数体内

       var foo = function poo() {
              console.log(1);
              console.log(poo);
          }
          foo();
          poo(); //此时会报错
      //输出结果:1
      ƒ poo() {
              console.log(1);
              console.log(poo);
      }
      //报错:22 Uncaught ReferenceError: poo is not defined
      
  • 匿名函数

    • 什么是匿名函数?

      function(){
          //由于没有名字,该函数无法调用
      }
      
    • 匿名函数如何运行?

      (function(){
          console.loe("匿名函数,立即调用");
          //函数在定义的同时,立即执行,也叫做函数的自运行。
      })
      
    • 匿名函数无法提前调用

      console.log(foo);   //var声明并赋值的函数无法提前调用
      foo();
      var foo = function(){}
      //输出结果
      undefined
      提示错误:75 Uncaught TypeError: foo is not a function
      
    • 函数的参数及返回值是什么 ?

      参数不做类型检查

  • 构造函数

    当需要从服务器将字符串转为函数时,需要用到构造函数。

    一般不使用,因为写入的都是字符串,所以执行时,需要将字符串转为语句块

    var fn = new Function("a", "b", "c", "console.log(a+b+c+'d')");
    //前面所有的都是函数的参数,最后一个是函数内部的语句块。
    
  • 回调函数

    将函数作为参数传入到另一个函数中,在需要执行时触发执行该函数 。

    目的:在两个不同的地方,互相不能确定时,进行调用 。

    function fn(f) {
        return f() + 1;
    }
    function fn1() {
        return 1;
    }
    console.log(fn(fn1)); // 2
    

2.函数的声明与赋值

  • 函数的声明

    function test(){
        //声明会被提前,可以在任何地方调用    
    }
    
  • 函数的赋值

    var test = function(){
        //只能在该函数以下的位置调用
    }
    
  • 两种创建方式的区别?

    JS虽然是脚本语言,但是代码也需要编译;在编译的过程中,所有的声明语句,都会被提前

    b();//当执行函数b的时候,由于声明被提前,可以执行
    a();//当执行函数a的时候,由于a仍未定义,所以执行会报错
    function b(){
        document.write("aa");
    }
    var a = function(){
        document.write("123");
    }
    
  • 函数如何与事件结合?

    事件 含义 事件 含义
    onload 页面加载完成 onfocus 获得焦点
    onclick 单击 onblur 失去焦点
    ondblclick 双击 onmouseenter 鼠标划入
    onkeydown 键盘被按下 onmouseleave 鼠标划出
    onkeyup 键盘被松开 onsubmit 表单提交事件
    onkeypress 键盘按下并松开

3.函数的参数

在函数外部定义的变量称为全局变量;内部定义的变量叫局部变量。

参数是变量,只作用于函数内部,外部不可以调用

function fn(a, b, c) {
    a++;
    b++;
    c++;
    console.log(a + b + c);
}
console.log(a);     //报错:a is not defined
fn(1,null,3);       //7 当中间的参数为空时候,不能是(1,,3),需要设置为null
console.log(fn.length);     //3,输出的是实参的个数
  • 不定参函数
function add() {
    console.log(arguments);     //伪数组,原产地是Object
    // 可以当成数组遍历,但是不能完成数组的大部分操作
    //(具有数组的数据结构,但是不具备数组配套操作方法的数据结构)
}
add();
// Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
  • argument.callee

arguments.callee等于被调用函数本身

function fn(){
    console.log(arguments.callee === fn);   //true
}

可以用于,在不知道什么函数调用了自己的情况下,再执行一遍函数(例:自调用函数)

(function(){
    var i = 0;
    if(i < 2)
        arguments.callee();
})();
  • 获取当前正在执行函数的函数名
arguments.callee.name();
  • 通过caller可以追溯到父函数的运行环境(在ES6中被抛弃)
function fn(){
    console.log(arguments.callee.caller);
}
function fn1(){
    fn(1,2,3,4);
}
fn1(5,6,7);
// [Function: fn1]
  • 通过caller可以追溯到父函数的父函数的运行环境
function fn(){
    console.log(arguments.callee.caller.arguments.callee.caller);
}
function fn2(){
fn1();
}
function fn1(){
    fn(1,2,3,4);
}
fn2(5,6,7);
//ƒ fn2(){
//fn1();
//}
  • 用arguments求若干个数中的最大值
function max() {
    // 如果参数为0,直接返回
    if (!arguments.length) return;
    //如果参数为1,返回第一个,无需比较
    var max = arguments[0];
    if (arguments.length === 1) {
        return max;
    }
    for (var i = 1; i < arguments.length; i++) {
        max = max > arguments[i] ? max : arguments[i];
    }
    return max;
}
console.log(max(3, 1, 4, 2, 5));
  • 对象作为函数参数

    • 改变返回值
    function fn(obj){
        obj.a++;
        obj.b++;
        console.log(obj.a + obj.b);   //5
    }
    
    var obj1={
        a:1,
        b:2
    }
    fn(obj1);
    console.log(obj1.a,obj1.b);       //2,3
    
    • 不改变返回值
    function fn(obj1) {
        // 这句之前obj和外面的obj引用地址相同
        obj1 = {
            a: 2,
            b: 3
        }; //重新将obj的引用地址改变,这时候就与外面的obj没有任何关系了
        console.log(obj1.a + obj1.b);
    }
    
    var obj = {
        a: 1,
        b: 2
    }
    fn(obj);
    console.log(obj);
    //{a: 1, b: 2}
    

4.函数的作用域

  • 全局中调用this,指向window
var a = 3;
function fn(){
    var a = 5;
    console.log(this.a + a);    //8 ,只适用于ES5
}
  • 点击事件的函数中,this是被点击对象
var btns = document.getElementsByClassName("block");

for(var i = 0; i < bns.length; i++){
    (function(n){
        bns[n].onclick = function(){
        //  这里打印的n变量就是自执行函数执行瞬间传入的参数值
            console.log(n);
        }
    })(i)
}
  • 对象中的方法,this是该对象
var fn = function(){
    console.log(fn);    //打印fn这个函数
}
fn();
var obj = {
        fn2:function(){
        //console.log(fn2);     //想要输出fn2属性的值,但是会报错
        console.log(this.fn2);  //输出fn2这个函数
    }
}
obj.fn2();

5.函数的调用

  • 变量和函数重名的问题

    var a = 10;
    function a(){}
    alert(typeof a);  //number
    //以上写法   等价于   以下写法:
    var a;
    function a(){}
    a = 10;
    // 因此a最终被10覆盖,则为number类型
    
  • 什么是变量声明提升?

    var n = 2;
    (function(){
         console.log(n);  //undefined   
         var n = 10;
         console.log(n);  //10
    })()
    //以上写法   等价于   以下写法:
    var n = 2;
    (function(){
         var n; //变量的声明不管哪里写在,都会被提升到该作用域的最前面
         console.log(n); //这里应该是undefined
         n = 10; //这里只是一个赋值动作
         console.log(n);  //毫无疑问等于10
    })()
    
  • 作用域链(向上找寻机制)

    从当前作用域向外向上寻找,如果找到了返回结果并终止查找;没有找到则报错。

    var a = 10;
    function foo(){
        //var a = 10;     //局部变量
        console.log(a)     //访问就近原则
    }
    foo();
    a = 20;
    //输出结果: 10
    
  • 局部提升优先于整体

    var的优先级高于function,所以var提升后会被function覆盖

    var a = 20;
      function foo() {
        console.log(a);       //undefined,如果是整体优先于局部提升,此处为20
        var a = 10;
        function a() {        
        }
        console.log(a);   /10
    }
    foo();
    //输出结果:
    //[Function: a]
    //10  
    
    var fn1 = 10;
    function fn1() {
    
    }
    console.log(typeof fn1)//number
    

    只定义没有赋值,意味着原有栈中fn1的结果没有改变

    console.log(fn1)
    var fn1 = 10;
    function fn1() {
    
    }
    console.log(typeof fn1) //function
    
  • 回调函数

    当外部函数不清楚内部函数要调用的函数是谁,需要用到回调函数

    function fn(){
        f();  //由外部告知函数f()要调用谁
    }
    function fn1(){
    
    }
    fn(fn1);//调用者告诉fn()要调用谁;fn()只负责调用,不关心调用谁
    
    function first(b, c, a) {
        setTimeout(function () {
            console.log("aaa");
            b(c, a, b);
        }, 1000)
    }
    
    function second(c, a, b) {
        setTimeout(function () {
            console.log("bbb");
            c(a, b, c);
        }, 1000)
    }
    
    function third(a, b, c) {
        setTimeout(function () {
            console.log("ccc");
            a(b, c, a);
        }, 1000)
    }
    
    first(second, third, first);//循环打印函数
    
  • 递归调用

    先递推、再回归

    递归调用——深度优先;循环——广度优先;

    • 求斐波纳契数列
    function fib(n) {
        if (n === 1 || n === 2) {
            return 1;
        }
        return fib(n - 1) + fib(n - 2);
    }
    console.log(fib(3));//8
    
    • 递归调用二叉树
    //工厂模式
    function createObj(_value) {
        var o = {};
        o.left = null;
        o.right = null;
        o.value = _value;
        return o;
    }
    var obj = createObj(1);;
    obj.left = createObj(2);
    obj.right = createObj(3);
    obj.left.left = createObj(4);
    obj.left.right = createObj(5);
    obj.right.left = createObj(6);
    obj.right.right = createObj(7);
    
    • 先序遍历
    function leftObj(o) {
        console.log(o.value);
        if (o.left)
            leftObj(o.left);
        if (o.right)
            leftObj(o.right);
    }
    //1245367
    
    • 中序遍历
    function centerObj(o) {
        if (o.left)
            centerObj(o.left);
        console.log(o.value);
        if (o.right)
            centerObj(o.right);
    }
    //4251637
    
    • 后序遍历
    function rightObj(o) {
        if (o.left)
            rightObj(o.left);
        if (o.right)
            rightObj(o.right);
        console.log(o.value);
    }
    //4526731
    
    • 求最大公约数

      1. 方法一
      image.png
          function reduce(m, n) {
              //先算法
              var r = m % n;
              m = n;
              n = r;
              if (r === 0) {
                  return m;
              } else {
                  return reduce(m, n);
              }
              //终止条件
          }
          console.log(reduce(30, 5));
      
      1. 方法二:更相减损法

      更相减损法原本是为了约分而设计的:可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。

      1:任意给定两个正整数;判断它们是否都是偶数。

      若是,则用2约简;若不是则执行第二步。

      2:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。

      继续这个操作,直到所得的减数和差相等为止。
      第一步中约掉的若干个2,与第二步中等数的乘积就是所求的最大公约数。

      image.png
       function reduce(x, y) {
              if (x % 2 === 0 && y % 2 === 0) {
                  x = x / 2;
                  y = y / 2;
                  return reduce(x, y) * 2;    //注意此处需要*2
              }
              var red = Math.max(x, y) - Math.min(x, y);
              if (red === Math.min(x, y)) {
                  return red;
              } else {
                  return reduce(red, Math.min(x, y));
              }
          }
       console.log(reduce(12, 20));
      

6.函数执行环境

为了储存局部变量,JS创建出了一个临时对象;我们称之为 AO (Active Object) 活动对象;

内存:垃圾回收机制: 1.IE 计数法; 2.现在浏览器 标记法

闭包:  
    function outerFn() {    // = > AO{a : 10, fn : fn}
        var a = 10;
        return function innerFn() {
            a++;
            console.log(a);
        }
    }
    var inner = outerFn();
    inner();        //11
    inner();        //12
    inner();        //13
    var inner2 = outerFn();
    inner2();       //11
    inner2();       //12
    inner2();       //13
 //执行环境(innerFN)(引用了AO{outerFn .a)
 //当内部有函数引用了外部函数定义的变量,外部函数的AO不会被删除;
 //闭包的目的 : 把局部变量生命周期变成永久; 数据保密;
 //闭包的问题:内存泄漏 (内存之中的数据没有可用引用)

结论:循环次数非常大的程序之中使用闭包;  

7.面试题

var a = 3;
function a(a){
    console.log(a);
    var a = 5;
}
a(0);
console.log(a); //报错,a is not a function
var a = {n:1};
var b = a;
a.x = a = {n:2};    //相当于:a.x = {n:2};a = {n: 2}
//a对象的地址被更改了,而b对象添加了一个属性
console.log(a,b);   // {n: 2} {n: 1, x: {n: 2}}
var x  = 10;
function fn(){
    console.log(x);     //10
}
function show(f){
    var x = 20;
    f();
}
show(fn);   
var fn1;
//只定义没有赋值,意味着原有栈中fn1的结果没有改变
function fn1(){
}
console.log(typeof fn1);    //打印函数function

在函数内部一旦使用var定义变量名或者参数名与全局变量名相同,在该函数中不再使用这个全局变量名,该函数中这个变量名是局部变量

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

推荐阅读更多精彩内容