JavaScript高级__函数高级

236 阅读12分钟
面试前喵一眼

原型、原型链、构造函数、继承

  • 所有函数都有一个特别的属性:
    • prototype : 显式原型属性
  • 所有实例对象都有一个特别的属性:
    • __proto__ : 隐式原型属性
  • 显式原型与隐式原型的关系
    • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
    • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 原型链
    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
    • 这样通过__proto__属性就形成了一个链的结构---->原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

执行上下文与执行上下文栈

  • 变量提升与函数提升
    • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
    • 函数提升: 在函数定义语句之前, 就执行该函数
    • 先有变量提升, 再有函数提升
  • 理解
    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:
    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期
    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:
    • 全局 :
      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数
      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组
  • 执行上下文创建和初始化的过程
    • 全局:
      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:
      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

作用域与作用域链

  • 理解:
    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
  • 分类:
    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用
    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文
    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

闭包

  • 理解:
    • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
    • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  • 作用:
    • 延长局部变量的生命周期
    • 让函数外部能操作内部的局部变量
  • 写一个闭包程序
    function fn1() {
      var a = 2;
      function fn2() {
        a++;
        console.log(a);
      }
      return fn2;
    }
    var f = fn1();
    f();
    f();
    
  • 闭包应用:
    • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
    • 循环遍历加监听
    • JS框架(jQuery)大量使用了闭包
  • 缺点:
    • 变量占用内存的时间可能会过长
    • 可能导致内存泄露
    • 解决:
      • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包

正式开始学习笔记

一、 原型、原型链、构造函数、继承

一图胜千言

image.png

1、原型(prototype)

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>01_原型(prototype)</title>
</head>
<body>
<!--
1. 函数的prototype属性(图)
 * 每个构造函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
 * 原型对象中有一个属性constructor, 它指向构造函数对象
2. 给原型对象添加属性(一般都是方法)
 * 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
-->
<script type="text/javascript">

 // 每个构造函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
 console.log(Date.prototype, typeof Date.prototype)
 function Fun () {//alt + shift +r(重命名rename)

 }
 console.log(Fun.prototype)  // 默认指向一个Object空对象(没有我们的属性)

 // 原型对象中有一个属性constructor, 它指向构造函数对象
 console.log(Date.prototype.constructor===Date)
 console.log(Fun.prototype.constructor===Fun)

 //给原型对象添加属性(一般是方法) ===>实例对象可以访问
 Fun.prototype.test = function () {
   console.log('test()')
 }
 var fun = new Fun()
 fun.test()
</script>
</body>
</html>

2、显式原型与隐式原型

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>02_显式原型与隐式原型</title>
</head>
<body>
<!--
1. 每个构造函数function都有一个prototype,即显式原型(属性)
2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
3. 每个实例对象的隐式原型的值为其对应构造函数的显式原型的值
4、每个实例对象都有constructor属性
5. 内存结构(图)
6. 总结:
 * 构造函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
 * 实例对象的__proto__属性: 创建实例对象时自动添加的, 默认值为构造函数的prototype属性值
 * 实例对象的constructor属性: 创建实例对象时自动添加的,继承自原型对象,指向构造函数对象
 * 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
-->
<script type="text/javascript">
 //定义构造函数
 function Fn() {   // 内部语句: this.prototype = {}

 }
 // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
 console.log(Fn.prototype)
 
 // 2. 每个实例对象都有一个__proto__,可称为隐式原型
 //创建实例对象
 var fn = new Fn()  // 内部语句: this.__proto__ = Fn.prototype
 console.log(fn.__proto__)
 
 // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
 console.log(Fn.prototype===fn.__proto__) // true
 
 //4、每个实例对象都有constructor属性,继承自原型对象,指向构造函数对象
 console.log(Fn.prototype.constructor===Fn) // true
 console.log(fn.constructor===Fn) // true
 
 //5、如果你更改了原型对象上的方法,就要修正constructor指向
 Fn.prototype = {
     aa: 11,
     bb; function() {
         console.log(666) 
     },
     constructor: Fn
 }
 
 //给原型添加方法
 Fn.prototype.test = function () {
   console.log('test()')
 }
 
 //通过实例调用原型的方法
 fn.test()
</script>
</body>
</html>

3、原型链

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>03_原型链</title>
</head>
<body>
<!--
1. 原型链(图解)
 * 访问一个对象的属性时,
   * 先在自身属性中查找,找到返回
   * 如果没有, 再沿着__proto__这条链向上查找, 找到返回
   * 如果最终没找到, 返回undefined
 * 别名: 隐式原型链
 * 作用: 查找对象的属性(方法)
2. 构造函数/原型/实体对象的关系(图解)
3. 构造函数/原型/实体对象的关系2(图解)
-->
<script type="text/javascript">
 // console.log(Object)
 //console.log(Object.prototype)
 console.log(Object.prototype.__proto__)
 function Fn() {
   this.test1 = function () {
     console.log('test1()')
   }
 }
 console.log(Fn.prototype)
 Fn.prototype.test2 = function () {
   console.log('test2()')
 }

 var fn = new Fn()

 fn.test1()
 fn.test2()
 console.log(fn.toString())
 console.log(fn.test3)
 // fn.test3()


 /*
 1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
  */
 console.log(Fn.prototype instanceof Object) // true
 console.log(Object.prototype instanceof Object) // false
 console.log(Function.prototype instanceof Object) // true
 /*
 2. 所有函数都是Function的实例(包含Function)
 */
 console.log(Function.__proto__===Function.prototype)
 /*
 3. Object的原型对象是原型链尽头
  */
 console.log(Object.prototype.__proto__) // null

</script>
</body>
</html>

4、原型链_属性问题

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>04_原型链_属性问题</title>
</head>
<body>
<!--
1. 读取对象的属性值时: 会自动到原型链中查找
2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
-->
<script type="text/javascript">

 function Fn() {

 }
 Fn.prototype.a = 'xxx'
 var fn1 = new Fn()
 console.log(fn1.a, fn1)

 var fn2 = new Fn()
 fn2.a = 'yyy'
 console.log(fn1.a, fn2.a, fn2)

 function Person(name, age) {
   this.name = name
   this.age = age
 }
 Person.prototype.setName = function (name) {
   this.name = name
 }
 var p1 = new Person('Tom', 12)
 p1.setName('Bob')
 console.log(p1)

 var p2 = new Person('Jack', 12)
 p2.setName('Cat')
 console.log(p2)
 console.log(p1.__proto__===p2.__proto__) // true

</script>
</body>
</html>

5、探索instanceof

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>05_探索instanceof</title>
</head>
<body>
<!--
1. instanceof是如何判断的?
 * 表达式: A instanceof B
 * 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
2. Function是通过new自己产生的实例
-->
<script type="text/javascript">
   /*
   案例1
   */
   function Foo() {  }
   var f1 = new Foo()
   console.log(f1 instanceof Foo) // true
   console.log(f1 instanceof Object) // true

   console.log(f1.__proto__ === Foo.prototype) // true
   console.log(Object.prototype === f1.__proto__.__proto__) // true
   console.log(Object.getPrototypeOf(f1) === Foo.prototype) // true

   console.log(Foo.__proto__ === Function.prototype) // true
   console.log(Object.__proto__ === Function.prototype) // true
   console.log(Function.__proto__ === Function.prototype) // true
   /*
   案例2
   */
   console.log(Object instanceof Function) // true
   console.log(Object instanceof Object) // true
   console.log(Function instanceof Function) // true
   console.log(Function instanceof Object) // true

   function Foo() {}
   console.log(Object instanceof  Foo) // false
</script>
</body>
</html>

6、对象的继承

6.1 原型链继承

此继承有什么问题:引用值共享问题
    function Super() { this.b = "呱呱呱"; this.temp = [1,2,3,4] };
    Super.prototype.say = function() {
        console.log(222)
    };
    function Sub() { this.a = "嘎嘎嘎"; };
    //这句是关键,这样sub1不仅可以用Sub上的方法,还可以用Super
    Sub.prototype = new Super();
    
    var sub1 = new Sub();
    var sub2 = new Sub();
    
    console.log(sub1.b);//可以拿到 
    
    sub1.b = "噢噢噢";
    console.log(sub1.b);//噢噢噢
    console.log(sub2.b);//呱呱呱
    
    sub1.temp.push(5);
    console.log(sub1.temp);//[1,2,3,4,5]
    console.log(sub2.temp);//[1,2,3,4,5]
    //这就奇了怪了,这就是【引用值共享问题】,需要通过构造函数继承来解决

6.2 构造函数继承

此继承有什么问题:没办法拿到原型上的方法
    function Super() { this.b = "呱呱呱"; this.temp = [1,2,3,4] };
    Super.prototype.say = function() {
        console.log(222)
    };
    function Sub() { 
        this.a = "嘎嘎嘎"; 
        //强绑定,调用Super,但是有局限,无法调用/拿到Super原型上的say方法
        Super.call(this);
    };

    var sub1 = new Sub();
    var sub2 = new Sub();

    sub1.b = "噢噢噢";
    console.log(sub1.b);//噢噢噢
    console.log(sub2.b);//呱呱呱
        
    sub1.temp.push(5);
    console.log(sub1.temp);//[1,2,3,4,5]
    console.log(sub2.temp);//[1,2,3,4]
    //这样就正常了

6.3 组合继承(伪经典继承)

此继承有什么问题:构造函数复用问题,调用了两次Super,所以"伪"
    function Super() { this.b = "呱呱呱"; this.temp = [1,2,3,4] };
    Super.prototype.say = function() {
        console.log(222)
    };
    function Sub() { 
        this.a = "嘎嘎嘎"; 
        //强绑定,调用Super,但是有局限,无法调用/拿到Super原型上的say方法
        Super.call(this);
    };
    
    Sub.prototype = new Super();//加上这两句就完事了
    Sub.prototype.constructor = Sub;

    var sub1 = new Sub();
    var sub2 = new Sub();

    sub1.b = "噢噢噢";
    console.log(sub1.b);//噢噢噢
    console.log(sub2.b);//呱呱呱
        
    sub1.temp.push(5);
    console.log(sub1.temp);//[1,2,3,4,5]
    console.log(sub2.temp);//[1,2,3,4]
    
    sub1.say();//222
    sub2.say();//222

6.4 寄生组合继承(经典继承)

此继承有什么问题:Sub原型上的方法被覆盖了,用【圣杯模式】来解决
扩展知识:js如何实现多重继承
    function Super() { this.b = "呱呱呱"; this.temp = [1,2,3,4] };
    Super.prototype.say = function() {
        console.log(222)
    };
    function Lol() {
        this.game = "Lol"
    };
    Lol.prototype.play = function() {
        console.log(333)
    };
    function Sub() { 
        this.a = "嘎嘎嘎"; 
        //强绑定,调用Super,但是有局限,无法调用/拿到Super原型上的say方法
        Super.call(this);
        Lol.call(this);
    };
    Sub.prototype.subSay = function() {
        console.log("subSay 666")
    };
    
    //避免不支持Object.create的情况
    if(!Object.create) {
         Object.create = function(proto) {
            function() {};
            F.prototype = proto;
            return new F();
        }
    }
   
    //Sub的原型Sub.prototype = Super.prototype原型的实例对象,
    //通过Object.create得到对应的实例对象
    Sub.prototype = Object.create(Super.prototype);
    //加上下面这句可以实现多重继承
    Object.create(Super.prototype, Lol.prototype);
    Sub.prototype.constructor = Sub;

    var sub1 = new Sub();
    var sub2 = new Sub();

    sub1.b = "噢噢噢";
    console.log(sub1.b);//噢噢噢
    console.log(sub2.b);//呱呱呱

    sub1.temp.push(5);
    console.log(sub1.temp);//[1,2,3,4,5]
    console.log(sub2.temp);//[1,2,3,4]

    sub1.say();//222
    sub2.say();//222
    
    sub1.subSay();//报错,is not a function

6.5 圣杯模式(企业级方案)

    function Teacher() { 
        this.name = "猫咪老师"; 
        this.tSkill = "JAVA"; 
    };
    Teacher.prototype = {
        this.pSkill = "JAVA"; 
    };
    var t = new Teacher();
    
    function Student() { 
        this.name = "夏目"; 
    };
    
    function Buffer() { 
        this.name = "夏目"; 
    };
    Buffer.prototype = Teacher.prototype;
    var buffer = new Buffer();//buffer就是圣杯
    Student.prototype = buffer;
    Student.prototype.age = 18;
    var s = new Student();
       
    console.log(s);

封装一下

    function Teacher() {this.name = "猫咪老师"; this.tSkill = "JAVA"};
    function Student() {this.name = "夏目"};
    Teacher.prototype = {this.pSkill = "JAVA"};
    Student.prototype = buffer;
    Student.prototype.age = 18;
    inherit(Student, Teacher);
    var t = new Teacher();
    var s = new Student();
    console.log(t);
    console.log(s);
    
    function inherit(Target, Origin) {
        function Buffer() {this.name = "夏目"};
        Buffer.prototype = Origin.prototype;
        Target.prototype = new Buffer();//顺序不能乱
        
        Target.prototype.constructor = Target;//记得加这两句
        Target.prototype.super_class = Origin;//设置继承源,
        用来找到真正继承的那个构造函数对象
    }

封装一下,用【闭包+自执行】的方式,企业级写法,也是模块化开发

    var inherit = (function() {
        var Buffer = function() {this.name = "夏目"}
        return function(Target, Origin) {
            Buffer.prototype = Origin.prototype;
            Target.prototype = new Buffer();
            Target.prototype.constructor = Target;
            Target.prototype.super_class = Origin;
        }
    })()
    
    function Teacher() {this.name = "猫咪老师"; this.tSkill = "JAVA"};
    function Student() {this.name = "夏目"};
    Teacher.prototype = {this.pSkill = "JAVA"};
    Student.prototype = buffer;
    Student.prototype.age = 18;
    inherit(Student, Teacher);
    var t = new Teacher();
    var s = new Student();
    console.log(t);
    console.log(s);
    
    //下面的代码与圣杯模式封装无关,只是顺便感受一下在此场景下的模块化开发,
    //代码是一体的
    var initProgrammer = (function() {
        var Programmer = function() {};
        Programmer.prototype = {
            name: "程序员",
            tool: "计算机",
            work: "编写应用重新程序,
            duration: "10个小时",

            say: function() {
                console.log(
                "我是一名" + this.myName + this.name + ",我的工作是用" +
                this.tool + this.work + ",我每天工作" + this.duration + "我
                的工作需要用到" + this.lang.toString() + ""
                )
            }
        };
        function FrontEnd() {};
        function BackEnd() {};
        
        inherit(FrontEnd, Programmer);
        inherit(BackEnd, Programmer);
        
        FrontEnd.prototype.lang = ["HTML", "CSS", "JavaScript"];
        FrontEnd.prototype.myName = "前端";
        BackEnd.prototype.lang = ["Node", "Java", "SQL"];
        BackEnd.prototype.myName = "后端";
        
        return {
            FrontEnd: FrontEnd,
            BackEnd: BackEnd
        }
    })();
    
    var frontEnd = new initProgrammer.FrontEnd();
    var backEnd = new initProgrammer.BackEnd();
    
    frontEnd.say();
    backEnd.say();

继续感受,什么是模块化开发

    window.onload = function() {
        init();
    };
    
    function init() {
        initCompute();
        initFunctions();
    };
    
    var initCompute = (function() {
        var a = 1, b = 2;
        
        function add() {
            console.log(a + b);
        };
        function minus() {
            console.log(a - b);
        };
        function mul() {
            console.log(a * b);
        };
        function div() {
            console.log(a / b);
        };
        
        return function() {
            add();
            minus();
            mul();
            div();
        }
    })()
模块化开发,Person借用Car的属性和方法
    function Car(brand, color, displacement) {
        this.brand = brand;
        this.color = color;
        this.displacement = displacement;
        this.info = function() {
            return "排量为" + this.displacement + ""的 + this.color + this.brand
        };
    };
    function Person(opt) {
        Car.apply(this, [opt.brand, opt.color, opt.displacement]);
        this.name = opt.name;
        this.age = opt.age;
        this.say = function() {
            console.log("年龄" + this.age + "岁姓名为" + this.name + "买了一辆" 
            + this.info());
        };
    };
    var p = new Person({
        brand: "奔驰", 
        color: "红色",
        displacement: "3.0",
        color: "张三",
        color: "25",      
    });
    p.say();//年龄25岁姓名为张三买了一辆排量为3.0的红色奔驰

模块化开发,链式调用,链式操作

    var sche = {
        wakeup: function() {
            console.log("wakeup");
            return this;
        },
        morning: function() {
            console.log("morning");
            return this;
        },
        noon: function() {
            console.log("noon");
            return this;
        },
        afternoon: function() {
            console.log("afternoon");
            return this;
        },
        evening: function() {
            console.log("evening");
            return this;
        },
        night: function() {
            console.log("night");
            return this;
        },
    };
    sche.wakeup().morning().noon().afternoon().evening().night();

6.5.1 CSS圣杯布局 双飞翼

这是一个插入的知识点

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-
      2.0.2.min.js"></script>
    </head>
    <style>
      body {
        min-width: 550px;  /* 2x leftContent width + rightContent width */
        font-weight: bold;
        font-size: 20px;
      }

      #header, #footer {
        background: rgba(29, 27, 27, 0.726);
        text-align: center;
        height: 60px;
        line-height: 60px;
      }
      #footer {
        clear: both;
      }

      #container {
        padding-left: 200px;   /* leftContent width */
        padding-right: 150px;  /* rightContent width */
        overflow: hidden;
      }

      #container .column {
        position: relative;
        float: left;
        text-align: center;
        height: 300px;
        line-height: 300px;
      }

      #center {
        width: 100%;
        background: rgb(206, 201, 201);
      }

      #left {
        width: 200px;           /* leftContent width */
        right: 200px;           /* leftContent width */
        margin-left: -100%;
        background: rgba(95, 179, 235, 0.972);
      }

      #right {
        width: 150px;           /* rightContent width */
        margin-left: -150px;   /* rightContent width */
        right: -150px;
        background: rgb(231, 105, 2);
      }
    </style>

    <body>
      <div id="header">#header</div>
      <div id="container">
        <div id="center" class="column">#center</div>
        <div id="left" class="column">#left</div>
        <div id="right" class="column">#right</div>
      </div>
      <div id="footer">#footer</div>
    </body>
    </html>

6.6 class + extends

ES6当中,通过 class + extends 关键字的方式,直接实现继承

    class Person {
        constructor(name) {
            this.name = name;
        };
        drink() {
            console.log("喝水");
        }
    }
    class Student extends Person {
        constructor(name, score) {
            this.score = score;
        };
        introduce() {
            console.log(`我是${this.name},考了${this.score}分`);
        }
    }
    class Teacher extends Person {
        constructor(name, subject) {
            this.subject = subject;
        };
        teach() {
            console.log(`我是${this.name},教${this.subject}`);
        }
    }
    
    const student = new Student("葡葡", 100);
    student.introduce();
    student.drink();
    const teacher = new Teacher("猫咪老师", "计算机");
    teacher.teach();
    teacher.drink();

6.7 拷贝继承

function Person() { } 
Person.prototype.head = 1;
Person.prototype.foot = 2; 
Person.prototype.eat = function () { 
    console.log('我会吃饭');
} 
function Student() { } 
for (let key in Person.prototype) { 
    Student.prototype[key] = Person.prototype[key]; 
} 
let stu1 = new Student(); 
stu1.eat();

7、利用自执行函数、构造函数、原型、window写一个插件,注意开头的;不可缺

    ;(function() {
        var Compute = function() {}
        
        Compute.prototype = {
            plus: function(a, b) {
                return a + b;
            },
             minus: function(a, b) {
                return a - b;
            },
             mul: function(a, b) {
                return a * b;
            },
             div: function(a, b) {
                return a / b;
            },
        }
        
        window.Compute = Compute;
    })();
    
    var compute = new Compute(1, 6);

8、函数预编译

前置知识---函数预编译会走的五步

  1. 产生AO对象:AO对象全称为:activation object (活跃对象/执行期上下文), 在函数执行前执行函数预编译,此时会产生一个AO对象,AO对象保存该函数的参数变量;
  2. 寻找var变量声明:将函数的参数以及函数里面声明的变量当做AO对象的属性名, 值全部为undefined;
  3. 形参和实参赋值:将实参的值赋值给形参 ;
  4. 寻找函数声明:在函数里面声明的函数,函数名作为AO对象的属性名,值赋值给函数 体(若参数名和函数名重叠,则函数体值会覆盖参数值);
  5. 执行。

有了前置知识才能开启面试题

9、原型面试题

    //面试题1
    function Foo() {
        getName = function() {
            console.log(1);
        };
        return this;
    };
    Foo.getName = function() {
        console.log(2);
    };
    Foo.prototype.getName = function() {
        console.log(2);
    };
    var getName = function() {
        console.log(4);
    };
    function getName() {
        console.log(5);
    };
    
    console.log(Foo());//window。此行先不注释掉,然后此行注释掉,
    //对比其它打印结果会完全不同。
    Foo.getName();//2 //2
    getName();//1 //4
    Foo().getName();//1 //1
    getName();//1 //1
    
    new Foo.getName();//2 //2
    new Foo().getName();//3 //3
    new new Foo.getName();//3 //3
#
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>面试题</title>
    </head>
    <body>
    <script type="text/javascript">
        /*
        面试题2
        */
        function A () {

        }
        A.prototype.n = 1

        var b = new A()

        A.prototype = {
            n: 2,
            m: 3
        }

        var c = new A()
        console.log(b.n, b.m, c.n, c.m)


        /*
        面试题3
        */
        function F (){}
        Object.prototype.a = function(){
            console.log('a()')
        }
        Function.prototype.b = function(){
            console.log('b()')
        }

        var f = new F()
        f.a()
        // f.b()
        F.a()
        F.b()
        console.log(f)
        console.log(Object.prototype)
        console.log(Function.prototype)
    </script>
    </body>
    </html>

二、 执行上下文与执行上下文栈

1、变量提升与函数提升

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>01_变量提升与函数提升</title>
</head>
<body>
<!--
1. 变量声明提升
 * 通过var定义(声明)的变量, 在定义语句之前就可以访问到
 * 值: undefined
2. 函数声明提升
 * 通过function声明的函数, 在之前就可以直接调用
 * 值: 函数定义(对象)
3. 问题: 变量提升和函数提升是如何产生的?
-->
<script type="text/javascript">
 console.log('-----')
 /*
 面试题 : 输出 undefined
  */
 var a = 3
 function fn () {
   console.log(a)
   var a = 4
 }
 fn()

 console.log(b) //undefined  变量提升
 fn2() //可调用  函数提升
 // fn3() //不能  变量提升

 var b = 3
 function fn2() {
   console.log('fn2()')
 }

 var fn3 = function () {
   console.log('fn3()')
 }
</script>
</body>
</html>

2、执行上下文

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>02_执行上下文</title>
</head>
<body>
<!--
1. 代码分类(位置)
 * 全局代码
 * 函数(局部)代码
2. 全局执行上下文
 * 在执行全局代码前将window确定为全局执行上下文
 * 对全局数据进行预处理
   * var定义的全局变量==>undefined, 添加为window的属性
   * function声明的全局函数==>赋值(fun), 添加为window的方法
   * this==>赋值(window)
 * 开始执行全局代码
3. 函数执行上下文
 * 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
 * 对局部数据进行预处理
   * 形参变量==>赋值(实参)==>添加为执行上下文的属性
   * arguments==>赋值(实参列表), 添加为执行上下文的属性
   * var定义的局部变量==>undefined, 添加为执行上下文的属性
   * function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
   * this==>赋值(调用函数的对象)
 * 开始执行函数体代码
-->
<script type="text/javascript">
 console.log(a1, window.a1)
 window.a2()
 console.log(this)

 var a1 = 3
 function a2() {
   console.log('a2()')
 }
 console.log(a1)

</script>
</body>
</html>
 

3、执行上下文栈1

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>03_执行上下文栈1/title>
</head>
<body>
<!--
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window
-->
<script type="text/javascript">
 var a = 10
 var bar = function (x) {
   var b = 5
   foo(x + b)
 }
 var foo = function (y) {
   var c = 5
   console.log(a + c + y)
 }
 bar(10)
 // bar(10)
</script>

</body>
</html>

4、执行上下文栈2

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>04_执行上下文栈2</title>
</head>
<body>
<!--
1. 依次输出什么?
 gb: undefined
 fb: 1
 fb: 2
 fb: 3
 fe: 3
 fe: 2
 fe: 1
 ge: 1
2. 整个过程中产生了几个执行上下文?  5
-->
<script type="text/javascript">
 console.log('gb: '+ i)
 var i = 1
 foo(1)
 function foo(i) {
   if (i == 4) {
     return
   }
   console.log('fb:' + i)
   foo(i + 1) //递归调用: 在函数内部调用自己
   console.log('fe:' + i)
 }
 console.log('ge: ' + i)
</script>
</body>
</html>

5、面试题

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>05_面试题</title>
 <link rel="stylesheet" href="xxx.css">
 <style>

 </style>
</head>
<body>
<div style=""></div>
<script type="text/javascript">

 /*
  测试题1:  先执行变量提升, 再执行函数提升
  */
 function a() {}
 var a
 console.log(typeof a) // 'function'


 /*
  测试题2:
  */
 if (!(b in window)) {
   var b = 1
 }
 console.log(b) // undefined

 /*
  测试题3:
  */
 var c = 1
 function c(c) {
   console.log(c)
   var c = 3
 }
 c(2) // 报错

</script>
</body>
</html>

三、 作用域与作用域链

1、作用域

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>01_作用域</title>
</head>
<body>
<!--
1. 理解
 * 就是一块"地盘", 一个代码段所在的区域
 * 它是静态的(相对于上下文对象), 在编写代码时就确定了
2. 分类
 * 全局作用域
 * 函数作用域
 * 没有块作用域(ES6有了)
3. 作用
 * 隔离变量,不同作用域下同名变量不会有冲突
-->
<script type="text/javascript">
/*  //没块作用域
 if(true) {
   var c = 3
 }
 console.log(c)*/

 var a = 10,
   b = 20
 function fn(x) {
   var a = 100,
     c = 300;
   console.log('fn()', a, b, c, x)
   function bar(x) {
     var a = 1000,
       d = 400
     console.log('bar()', a, b, c, d, x)
   }

   bar(100)
   bar(200)
 }
 fn(10)
</script>
</body>
</html>

2、作用域与执行上下文

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>02_作用域与执行上下文</title>
</head>
<body>
<!--
1. 区别1
 * 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
 * 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
 * 函数执行上下文是在调用函数时, 函数体代码执行之前创建
2. 区别2
 * 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
 * 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
3. 联系
 * 执行上下文(对象)是从属于所在的作用域
 * 全局上下文环境==>全局作用域
 * 函数上下文环境==>对应的函数使用域
-->
<script type="text/javascript">
 var a = 10,
   b = 20
 function fn(x) {
   var a = 100,
     c = 300;
   console.log('fn()', a, b, c, x)
   function bar(x) {
     var a = 1000,
       d = 400
     console.log('bar()', a, b, c, d, x)
   }

   bar(100)
   bar(200)
 }
 fn(10)
</script>
</body>
</html>

3、作用域链

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>03_作用域链</title>
</head>
<body>
<!--
1. 理解
 * 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
 * 查找变量时就是沿着作用域链来查找的
2. 查找一个变量的查找规则
 * 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
 * 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
 * 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
-->
<script type="text/javascript">
 var a = 1
 function fn1() {
   var b = 2
   function fn2() {
     var c = 3
     console.log(c)
     console.log(b)
     console.log(a)
     console.log(d)
   }
   fn2()
 }
 fn1()  //3 2 1 undefined
</script>

</body>
</html>

4、作用域_面试题1

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>04_作用域_面试题1/title>
</head>
<body>
<script type="text/javascript">
 var x = 10;
 function fn() {
   console.log(x);
 }
 function show(f) {
   var x = 20;
   f();
 }
 show(fn); //结果为:10
 
</script>
</body>
</html>

4、作用域_面试题2

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>04_作用域_面试题2</title>
</head>
<body>
<script type="text/javascript">
 var fn = function () {
   console.log(fn)
 }
 fn()

 var obj = {
   fn2: function () {
    console.log(fn2) //报错
    //console.log(this.fn2)
   }
 }
 obj.fn2()
</script>
</body>
</html>

四、 闭包 Closure

0、引入

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>00_引入</title>
</head>
<body>

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  /*
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    btn.onclick = function () {
      alert('第'+(i+1)+'个') //此法不行
    }
  }*/
  /*
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    //将btn所对应的下标保存在btn上
    btn.index = i
    btn.onclick = function () {
      alert('第'+(this.index+1)+'个')  //此法可以
    }
  }*/

  //利用闭包
  for (var i = 0,length=btns.length; i < length; i++) {
    (function (j) {
      var btn = btns[j]
      btn.onclick = function () {
        alert('第'+(j+1)+'个')  //此法更好
      }
    })(i)
  }

</script>
</body>
</html>

1、理解闭包

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>01_理解闭包</title>
</head>
<body>
<!--
1. 如何产生闭包?
  * 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
2. 闭包到底是什么?
  * 使用chrome调试查看,Closure
  * 理解一: 闭包是嵌套的内部函数(绝大部分人)
  * 理解二: 包含被引用变量(函数)的对象(极少数人)
  * 注意: 闭包存在于嵌套的内部函数中
  * 注意:与--递归--区分开
3. 产生闭包的条件?  两个条件就足以
  * 函数嵌套
  * 内部函数引用了外部函数的数据(变量/函数)
  注意,这里没要求内部函数被调用,这不是条件之一
  -->
<script type="text/javascript">
  function fn1 () {
    var a = 2
    var b = 'abc'
    function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数fn2也行,也算是产生了闭包,但是记得,要调用外部函数fn1
      console.log(a)
    }
    // fn2()
  }
  fn1()

  function fun1() {
    var a = 3
    var fun2 = function () { //这种写法,fun2为undefined,没有闭包
      console.log(a)
    }
  }
  fun1()
</script>
</body>
</html>

2、常见的闭包

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>02_常见的闭包</title>

</head>
<body>
<!--
1. 将函数作为另一个函数的返回值
2. 将函数作为实参传递给另一个函数调用
-->
<script type="text/javascript">
    // 1. 将函数作为另一个函数的返回值
    function fn1() {
        var a = 2
        function fn2() {
            a++
            console.log(a)
        }
        return fn2
    }
    var f = fn1()
    f() // 3
    f() // 4
// 注意: 以上整个过程,就算你调用了2次f(),也只是产生了一个闭包。
看外部函数执行几次,就产生多少个闭包,本题调用了一次fn1,所以只有一个闭包,
调用f()相当于调用fn2

    // 2. 将函数作为实参传递给另一个函数调用
    function showDelay(msg, time) {
        setTimeout(function () {
        alert(msg)
        }, time)
    }
  showDelay('atguigu', 2000)

    //3. 对象式闭包
    function test() {
        var num = 0;
        
        var compute = {
            add: function fn2() {
              num++
              console.log(num)
            };
            minus: function fn2() {
              num--
              console.log(num)
            };
        },
        
        return compute
    }
    
    var compute = test();
    compute.add();//1
    compute.add();//2
    compute.add();//3
    compute.minus();//2
    
    //4. 构造函数式闭包
    function Compute() {
        var num = 0;
        
        this.add = function fn2() {
          num++
          console.log(num)
        };
        this.minus = function fn2() {
          num--
          console.log(num)
        };
        //return this;这一句不用写,因为在new的时候自动有这句
        //而且,就算写了return 引用类型,也不会报错;
        //但是,return 123基本类型,就会报错
    }
    
    var compute = new Compute();//因为new的时候隐式的return this;
    compute.add();//1
    compute.add();//2
    compute.add();//3
    compute.minus();//2
    
</script>
</body>
</html>

3、闭包的作用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>03_闭包的作用</title>

</head>
<body>
<!--
1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:
  1. 函数执行完后, 函数内部声明的局部变量是否还存在?  一般是不存在, 存在于闭中的变量才可能存在
  2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它
-->
<script type="text/javascript">
  function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
      // return a
    }
    function fn3() {
      a--
      console.log(a)
    }
    return fn3
  }
  var f = fn1()  //注意:此处用f接住了,所以fn3被执行完就会被释放,但是a还在
  f() // 1
  f() // 0
</script>

</body>
</html>

4、闭包的生命周期

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>04_闭包的生命周期</title>

</head>
<body>
<!--
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时
-->
<script type="text/javascript">
  function fn1() {
    //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
    var a = 2
    function fn2 () {
    //var fn2 = function () { //用这种方式定义的,闭包要在以下四行代码(包含本行代码)才算产生,记住,区分--函数定义执行---和---函数执行---
      a++
      console.log(a)
    }
    return fn2
  }
  var f = fn1()
  f() // 3
  f() // 4
  f = null //闭包死亡(包含闭包的函数对象fn2成为垃圾对象)
</script>
</body>
</html>

5、闭包的应用_自定义JS模块1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_闭包的应用_自定义JS模块</title>
</head>
<body>
<!--
闭包的应用1: 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包含n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>
</body>
</html>

myModule.js

//myModule.js
function myModule() {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

5、闭包的应用_自定义JS模块2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_闭包的应用_自定义JS模块2</title>
</head>
<body>
<!--
闭包的应用2 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包含n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>
</body>
</html>

myModule2.js

//myModule2.js
(function () {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

6、闭包的缺点及解决

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>06_闭包的缺点及解决</title>
</head>
<body>
<!--
1. 缺点
  * 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  * 容易造成内存泄露
2. 解决
  * 能不用闭包就不用
  * 及时释放
-->
<script type="text/javascript">
  function fn1() {
    var arr = new Array[100000]
    function fn2() {
      console.log(arr.length)
    }
    return fn2
  }
  var f = fn1()
  f()

  f = null //让内部函数成为垃圾对象-->回收闭包
</script>
</body>
</html>

7、内存溢出与内存泄露

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>02_内存溢出与内存泄露</title>
</head>
<body>

<!--
1. 内存溢出
  * 一种程序运行出现的错误
  * 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
2. 内存泄露
  * 占用的内存没有及时释放
  * 内存泄露积累多了就容易导致内存溢出
  * 常见的内存泄露:
    * 意外的全局变量
    * 没有及时清理的计时器或回调函数
    * 闭包
-->

<script type="text/javascript">
  // 1. 内存溢出
  var obj = {}
  for (var i = 0; i < 10000; i++) {
    obj[i] = new Array(10000000)
    console.log('-----')
  }

  // 2. 内存泄露
    // 意外的全局变量
  function fn() {
    a = new Array(10000000)
    console.log(a)
  }
  fn()

   // 没有及时清理的计时器或回调函数
  var intervalId = setInterval(function () { //启动循环定时器后不清理
    console.log('----')
  }, 1000)

  // clearInterval(intervalId)

    // 闭包
  function fn1() {
    var a = 4
    function fn2() {
      console.log(++a)
    }
    return fn2
  }
  var f = fn1()
  f()

  // f = null

</script>
</body>
</html>

8、面试题1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>07_面试题1</title>
</head>
<body>

<script type="text/javascript">
  //代码片段一
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());  //?  the window


  //代码片段二
  var name2 = "The Window";
  var object2 = {
    name2 : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name2;
      };
    }
  };
  alert(object2.getNameFunc()()); //?  my object

</script>
</body>
</html>

8、面试题2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>07_面试题2</title>
</head>
<body>

<script type="text/javascript">
  function fun(n,o) {
    console.log(o)
    return {
      fun:function(m){
        return fun(m,n)
      }
    }
  }
  var a = fun(0)
  a.fun(1)
  a.fun(2)
  a.fun(3)//undefined,0,0,0 因为每一次调用完就释放了,这里有三个闭包
  //var a1 = a.fun(1)  这才叫接住

  var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2

  var c = fun(0).fun(1)
  c.fun(2)
  c.fun(3)//undefined,0,1,1
</script>
</body>
</html>