JS高级_尚硅谷笔记(数据对象、数据变量内存、对象、函数、原型、执行上下文、作用域、创建对象等)

1. 数据类型

1.1 分类

基本(值)类型

  • stirng:任意字符串
  • number:任意数字(无关整数、小数等)
  • boolean:true/false
  • undefined:undefined
  • null:null

对象(引用)类型

  • Object:任意对象都是Object类型
  • Function:一种特殊的对象,可以调用执行
  • Array:特殊对象,存在数值下标,内部数据是有序的

1.2 判断

typeof

  • 返回数据类型的字符串表达
  • 可以判断数值,字符串,undefined, boolean, fucntion
  • 不可以判断null,object,array,都会返回Object

instanceof

  • 返回Boolean值,只能判断对象的具体类型,即使普通对象、函数还是基本类型
  • instance 实例

=== (全等,值和类型都相等才返回true)

  • 可以判断undefined与null,由于它们的值只有一个;

typeof和instanceof的区别

  • 在JavaScript中,判断一个变量的类型常常会用typeof运算符,在使用typeof算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都回’object’ ;
  • instanceof运算符用来测试一个对象在其原型链中是否存在一个构造函数prototype属性。语法:object instanceof constructor 参数: object(要检测的对象constructor(某个构造函数)描述:instanceof 运算符用来检测constructor.prototype是否存在于参数object的原型上。

1.2 数据类型2

实例:实例对象 - 根据类型创建的实例对象 (var p = new Person(...))
类型:类型对象 - 构造函数是类型 (function Person(...){...})

undefined和null的区别?

  • undefined 代表定义未赋值;null 代表定义并赋值了,且值为null;

什么时候给变量赋值为null?

  • 起始位置将对象b 赋值为null,表明将要赋值为对象;
  • 确定对象以后赋值 (b = [1, 2] / b = {…} 都可以);
  • 最后再“b = null”,使b指向的队形成为垃圾对象,被垃圾回收器(浏览器中)回收;

严格区别变量类型与数据类型:

  • 数据的类型
    • 基本类型
    • 对象类型
  • 变量的类型(变量内存值得类型)
    • 基本类型:保存得就是基本对象的数据
    • 引用类型:保存的是地址值

var c = function(){..};
c在栈内存中,保存的是对象在内存空间的地址值;c是引用对象;
typeof c;
是返回c指向的基本对象的类型 “function”,而不是返回"引用对象"

1.3 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01_数据类型</title>
</head>
<body>
    <script type="text/javascript">
        var a;
        console.log(a, typeof a);
        // undefined 'undefined'
        console.log(undefined === 'undefined');
        // false
        console.log(typeof a ==='undefined', a===undefined);
        // true true
        var b1 = {
            b2:[1,'abc', console.log],
            b3:function(){
                console.log('b3')
                return function(){
                    return 'kkkk'
                }
            }
        }
        console.log(b1 instanceof Object, b1 instanceof Array);
        // true false
        console.log(b1.b2 instanceof Object, b1.b2 instanceof Array);
        // true true
        console.log(b1.b3 instanceof Function, b1.b3 instanceof Object);
        // true true

        console.log(typeof b1.b3 === 'function'); //true

        console.log(typeof b1.b2[2]==='function'); //true
        b1.b2[2](4); // 4


        console.log(b1.b3()());
        /*
        * b3
        * kkk
        */
        b1.b3(); // b3
        // b1是对象,不能执行,执行的是b3函数  

        
        console.log(typeof b1.b2) //object 所以typeof不能用于判断array
    </script>
</body>
</html>

2. 数据_变量_内存

2.1 什么是数据?

存储在内存中代表特定信息的东西,本质上是0101;
特点:可传递,可运算;
内存中所有操作的目标:数据, 包括算术运算 、逻辑运算、赋值、运行函数;

2.2 什么是内存?

内存条通电以后可存储数据的空间(临时的);
内存产生和死亡:内存条(电路板)–>通电 -->产生内存空间 -->存储数据 -->处理数据 -->断电 -->内存空间和数据都消失;
一个小内存中有2个数据:内部存储的数据和地址值;
内存的分类:栈(全局变量/局部变量),堆(对象);
过程: 代码加在内存中 -->编译 -->解析执行;

    var obj = {name:'Tom'}// obj在栈空间,将对象的地址值(0x123)保存到obj中(在栈空间),对象内容(name:'Tom')在内存空间中;
    var a = obj; 
    // 是在栈空间中,a = 0x123, 指向同一个内存地址
    console.log(0bj.name); 
    // 找到obj,读值,得0x123,'.'用0x123找到对应内存地址,再根据.后的名称找对应值
    

2.3 什么是变量?

可变化的量,由变量名和变量值组成;
每个变量都对应一个小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据;

2.4 内存,数据,变量三者之间的关系:

内存是用来存储数据的临时空间, 变量是内存的标识;

2.5 相关问题:var a = xxx, a内存中保存的是什么?

  1. xxx是基本类型, 保存的是这个基本类型值;
  2. xxx是引用类型,保存的是这个引用类型的内存地址值;
  3. xxx是个变量,保存的是这个变量所存储的值(若是基本类型则就是这个值, 若是引用类型则是这个引用类型的内存地址值);

2.6 引用变量赋值问题?

  1. 多个引用变量指向同一个对象, 通过一个变量修改这个对象的值, 另一个变量只能看到修改后的值;
  2. 两个引用变量指向同一个对象,将其中一个变量赋值为新对象, 另一个引用变量仍指向原对象;

2.7 在函数调用时是值传递还是引用传递?

理解1: 无论变量类型都是值(基本/地址值)传递;
理解2: 可能是值传递, 也可能是引用传递(传递地址值);

2.8 js如何管理内存?

内存生命周期

  • 分配小内存空间, 得到它的使用权
  • 存储数据, 可以重复使用
  • 释放小内存空间
  • 释放内存

释放内存

  • 局部变量: 函数执行完自动释放
  • 对象: 成为垃圾对象后在某个时间被垃圾处理机制释放

2.9 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>02_数据_变量_内存</title>
</head>
<body>
    <script type="text/javascript">

        // 引用变量赋值问题
        // 1. 多个引用变量指向同一个对象, 通过一个变量修改这个对象的值, 另一个变量只能看到修改后的值;
        var  obj1 = {name:'Tom'};
        var obj2 = obj1;
        obj2.name = 'Amy';
        // .xxx = ... 赋值,改变对象的值
        console.log(obj1.name) // Amy
        function fn1(obj){
            obj.name = 'Fun1';
        }
        fn1(obj1);
        console.log(obj1.name); // Fun1

        // 2. 两个引用变量指向同一个对象, 将其中一个变量赋值为新对象, 另一个引用变量仍指向原对象;
        var obj3 = {age : 15};
        var obj4 = obj3;
        obj4 = {age: 16, name: 'Jack'}
        // ‘ = ’,使得obj4重新指向另一个对象,而不是更改之前对象的内容
        console.log(obj4.age, obj4.name, obj3.age); // 16 'Jack' 15
        function fn2(obj){
            // 进入函数会声明对象
            obj = {age: 18};
            // 退出后销毁,所以obj3通过形参改变的值被销毁
        }
        fn2(obj3);
        console.log(obj3.age); // 15

        // 函数传递
        var a = 3;
        function fn3(a){
            // 对于基本变量,理解成值传递,传递的是基本对象的数据
            // 调用函数时,将实参a的值传给实参a,形参a进行加减运行对外部实参a的值没有影响
            a=a+1;
        }
        fn3(a);
        console.log(a); // 3
        function fn4(obj){
            // 对于引用变量,理解成引用传递或者值传递都可以,传递的是指向对象的地址值
            // 所以调用函数时可以获得到或者修改地址指向的对象的信息
            console.log(obj.age);
            obj5.age = 28;
        }
        var obj5 = {age: 20}
        fn4(obj5); // 20
        console.log(obj5.age); //28

        // 内存释放
        function fn5(){
            var b = {}
        }
        fn5()
        // 在调用函数开始时,创建声明引用变量b 
        // 调用函数结束后,自动释放局部变量b
        // 而b所指向的对象是在后面某个时刻由垃圾回收器回收的(成为垃圾对象-->由垃圾回收器回收)
    </script>
</body>
</html>

3. 对象

3.1 什么是对象?

多个数据的封装体;
用来保存多个数据的容器;
一个对象代表现实中的一个事物, 如Person等;

3.2 为什么要用对象?

统一管理多个数据

3.3 对象的组成

  1. 属性: 属性名(字符串)与属性值(任意类型)
  2. 方法: 一种特别的属性(属性值是一个函数)

3.4 如何访问对象内部数据?

  1. .属性名 编码简单, 有时无法使用
  2. [‘属性名’] 编码复杂, 但能随意使用

3.5 什么时候必须使用[‘属性名’]?

  1. 属性名包含特殊字符, 如: - . 空格
  2. 属性名不确定时, 使用的是变量的值

3.6 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03_对象</title>
</head>
<body>
    <script type="text/javascript">
        // 对象声明
        var p={
            // 属性: 由属性名(字符串)和属性值(任意类型)组成
           name:'tom',
           age:12,
           // 方法: 由方法名(字符串)和属性值(只能是函数)组成
           setName:function(name){
               this.name=name;
           },
           setAge:function(age) {
               this.age = age;
           }
       }
       console.log(p.name, p.setAge, p.setAge(18), p.age);
        //  tom  ƒ (age) {...} undefined 18  
       p["setAge"](23)
       console.log(p.age) // 23

        // 有特殊字符
       var o ={};
        // o.content-type = "text-json";  // 语句会报错
       o['content-type '] = "text-json";

        // 属性名不确定
        var propName = 'myAge';
        var value = 18;
        o.propName = value;
        console.log(o.myAge); // undefined
        o[propName] = value;
        console.log(o.myAge); // 18

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

4. 函数

4.1 什么是函数?

实现特定功能的n条语句的封装体;
只有函数可以执行, 其他类型的数据不能执行

4.2 为什么要用函数?

  1. 提高代码复用(调用函数多次实现相同功能)
  2. 便于阅读交流(对于同一种功能只用读一边函数)

4.3 如何定义函数

  1. 函数声明 如 function fn(){....}
  2. 表达式, var fn = function(){...}

4.4 如何调用(执行)函数?

  1. 直接调用: test()
  2. 通过对象调用: obj.test()
  3. new调用: new test()
  4. test.call/apply(obj): 临时让test函数变成obj的方法进行调用

4.5 回调函数

什么函数属于回调函数?

  • 自己定义过的
  • 自己没有调用的
  • 能被执行的(某个时刻或者某种条件下)

常见的回调函数

  • dom事件回调函数–>发生事件的dom元素
  • 定时器回调函数–>window
  • ajax回调函数
  • 生命周期回调函数

4.6 IIFE

Immediately-Invoked Function Expression; 匿名函数自调用; 立即调用函数表达式;

格式 :

    (function(){
        ....(函数内容)
    })()

作用:

  • 隐藏实现
  • 不会污染外部作用域

4.7 this

this是什么?

  • 任何函数本质都是通过某个对象来调用的, 如果没有指定就是window;
  • 所有函数内部都有一个变量this;
  • 它的值是调用函数时的当前对象;
  1. 如何确定this的值?
  • test() --> window
  • p.test() --> p
  • new test() --> 新创建的对象
  • p.call(obj) --> obj

4.8 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>04_函数</title>
</head>
<body>
    <button>点击此处</button>
    <script type="text/javascript">
        // 调用函数
        var obj = {
            name:'Bob'
        }
        name = 'Win'
        function test(){
            console.log(this.name);
        }
        test.call(obj); //Bob 临时让test成为obj的方法调用
        test(); // Win 
        // obj.test(); // Uncaught TypeError: obj.test is not a function

        // 回调函数
        window.onload = function () {
            var btn = document.querySelector('button');
            // 这个成为dom事件回调函数
            btn.onclick = function () {
                alert(this.innerHTML)
            }
        }
        // 定时器回调函数
        setTimeout(function () {
            alert('到点了')
        }, 2000)


        // IIFE
        // 此部分依次输出: 6 4 1 2 
        var a = 4;
        (function(){
            var a = 3
            console.log(a+3); // 6
        })()  // 立即执行
        console.log(a); // 4

        (function(){
            var a = 1;
            console.log(a); // 1

            function test1(){
                console.log(++a);
            }
            // 像window暴露$
            window.$ = function(){
                return {
                    test1:test1
                }
            };
        })()
        $().test1(); // 2


        // this
        function Person(color){
            console.log(this+"***1");
            this.color = color;
            this.getColor = function(){
                console.log(this+"***2");
                return this.color;
            };
            this.setColor = function(color){
                console.log(this+"***3");
                this.color = color;
            };
        }
        Person("red"); // [object Window]***1
        var p1 = new Person("yellow"); // [object Object]***1 object是p1
        p1.getColor(); // [object Object]***2 object是p1

        var obj1={};
        //让obj1使用 setColor这个方法
        p1.setColor.call(obj1, "black"); // [object Object]***3 object是obj1
        var test2 = p1.setColor; 
        test2(); // [object Window]***3

        function fun1(){
            function fun2(){
                console.log(this +"***4")
            }
            fun2()
        }
        fun1(); // [object Window]***4

        var a = 1;
        a = 2;
        window.a = 3;
        function Test() {
            let a = 4;

            // 调用语句为"new Test()", 则这里的this是一个匿名对象
            // 给Test的这个匿名对象加了一个属性a
            this.a = 5;

            console.log(a);  
            // 4, 先在内部找a(找到a=4),没有的话再去外部
            console.log(this.a);  //5

            setTimeout(function () {
                console.log(a);  
                // 4, 打印的是内部的a
            })
            setTimeout(function () {
                console.log(this.a);  
                // 3,
                // 因为定时器 setInterval setTimeout是window的方法,
                // 里面回调函数的this指向window
            })
            setTimeout(() => {
                console.log(a);   
                // 4,虽然是箭头指向, 但是没有this, 所以是内部的a 
            }, 30)
            setTimeout(() => {
                console.log(this.a);  
                // 5,
                // 箭头指向的this:
                // 1. 有外层函数, 则是调用外层函数的对象
                // 2. 没有外层函数, 则是window
                // 这里的this指的是调用Test的匿名对象
            }, 40)
        }
        new Test();
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>04_函数_this面试</title>
</head>
<body>
    <script type="text/javascript">

        // 练习1
        var num = 1;
        var myObject = {
            num: 2,
            add: function() {
                this.num = 3;
                (function() {
                    console.log(this.num);
                    this.num = 4;
                })();
                console.log(this.num);
            },
            sub: function() {
                console.log(this.num)
            }
        }
        myObject.add();
        console.log(myObject.num);
        console.log(num);
        var sub = myObject.sub;
        sub();
        /*输出: 1 3 3 4 4
        * 解析:
        * 1. 执行"myObject.add()"时, add中的this指向myObject,
        *    则有myObject.num=3;
        *    接着执行自执行函数,
        *       没有任何对象调用该函数, 则this指向window, 输出1;
        *       输出this.num=4,即window.num=4;
        *       自执行函数运行完毕, 跳出;
        *    回到add函数中,此时的this是myObject,
        *    myObject.num=3,输出3;
        * 2. 执行"console.log(myObject.num);", 输出3;
        * 3. 执行"console.log(num);", 此时没有声明,则this是window,
        *    由上可知,window.num=4, 输出4;
        * 4. 由"sub = myObject.sub", 意思是给window的sub函数和myObject.sub的sub函数结构相同;
        * 5. 执行"sub();", 调用sub的函数为window;
        *    则this为window, 输出this.num即输出window.num, 输出4;
        */


        // 练习2
        var name = 'window'        
        var person1 = {
            name: 'person1',
            show1: function () {
                console.log(this.name)
            },
            show2: () => console.log(this.name),
            show3: function () {
                return function () {
                    console.log(this.name)
                }
            },
            show4: function () {
                return () => console.log(this.name)
            }
        }
        var person2 = { name: 'person2' }
        
        person1.show1() // person1
        person1.show1.call(person2) // person2
        
        person1.show2() // window
        person1.show2.call(person2) // window ** 
        
        person1.show3()()  // window **
        person1.show3().call(person2)  // person2
        person1.show3.call(person2)()  // window **
        
        person1.show4()() // person1 **
        person1.show4().call(person2) // person1 ** 
        person1.show4.call(person2)() // person2 **
        /* 解析:
        * show1略
        * show2:
        *   "person1.show2()" 执行 " () => console.log(this.name)"
        *   根据: 箭头函数的this指向外层作用域
        *   该箭头外层没有函数了, 所以指向全局作用域,this为window;
        * 
        *   "person1.show1.call(person2)" 执行 " () => console.log(this.name)"
        *   根据: 箭头函数直接应用bind/call/apply 不起作用
        *   this依旧指向外层作用域, 即指向全局, 输出window
        * 
        * show3:
        *   "person1.show3()"return 函数内容,
        *   随后接一个(),相当于在全局调用函数, 则this为window;
        *   "person1.show3()"return 函数内容,
        *   随后接".call(person2)", 让person2调用return的函数,this为person2;
        *   "person1.show3.call(person2)",相当于让person2调用show3函数, return函数内容
        *   然后再接一个()调用return的函数, this为window;
        * 
        * show4:
        *   同上, "person1.show4()" return函数内容, return "() => console.log(this.name)",
        *   1. 后面接一个(), 为寻找外层作用域,找到外层函数"person1.show4()",则this为person1;
        *   2. 接".call(person2)", 意思是让person2调用return的函数,但是函数内容箭头找外层函数,
        *      找到"person1.show4()", ,则this为person1;
        *   3. 执行"person1.show4.call(person2)()",
        *      "person1.show4.call(person2)"指定让person2执行show4,
        *       然后接着一个(),运行return的函数, 箭头找外层作用域,
        *       找到调用show4的person2, 输出person2;
        * 
        * 总结: 箭头函数没有自己的this,它的this是:
        *       1. 谁调用箭头函数的外层function,箭头函数的this就是指向谁,
        *       2. 如果箭头函数没有外层函数,则指向window。
        */


        // 练习3
        var name = 'window'
        function Person(name) {
            this.name = name;
            this.show1 = function () {
                console.log(this.name)
            }
            this.show2 = () => console.log(this.name)

            this.show3 = function () {
                return function () {
                    console.log(this.name)
                }
            }

            this.show4 = function () {
                return () => console.log(this.name)
            }
        }
        
        var personA = new Person('personA')
        var personB = new Person('personB')
        
        personA.show1() // personA
        personA.show1.call(personB) // personB
        
        personA.show2() // personA**
        personA.show2.call(personB) // personA**
        
        personA.show3()() // window
        personA.show3().call(personB) // pwrsonB
        personA.show3.call(personB)() // window
        
        personA.show4()() // personA**
        personA.show4().call(personB) // personA**
        personA.show4.call(personB)() // personB**

        /*解析:
        * 构造函数创建对象后其实多了一层构造函数的作用域;
        */

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

5. 原型与原型链

5.1 原型

原型属性: 函数的prototype属性

  • 每个函数都有一个prototype属性,它默认指向一个Object空对象(原型对象)
    console.log(typeof Date.prototype) // object
    空对象: obj = {}
  • 每个原型对象都会有一个constructor, 指向函数对象;
    也就是 func.prototype.constructor === func; //true

5.2 显式原型和隐式原型

  1. 显式原型 : 每个函数function都有一个prototype, 即显式原型;
    函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象;
  2. 隐式原型 : 每个实例对象都有一个__proto__, 即隐式原型;
    对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值;
  3. 对象的隐式原型的值为其构造函数的显示原型的值;
    func.__proto__ === Func.prototype //true
  4. 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前);

5.3 原型链

别名:隐式原型链
作用:查找对象属性(方法);

  • 先查找函数自身内部的方法,找到返回;
  • 如果函数自身内部没有这个方法, 就去找这个函数__proto__内的方法;
  • 仍然没有找到,继续沿着__proto__向上查找;
  • 找到返回, 如果最终没有依旧没有找到,返回undefined;
    Object的显式原型的隐式原型为null, 即原型链的尽头;

补充

  • 函数的显式原型指向的对象:默认是空Object的实例对象(但Object自身不满足);
  • Function是它自身的实例,所有函数都是Function的实例(包括Function本身);
  • Object的显式原型的隐式原型是原型链的尽头 ;

5.4 原型链属性问题

读取 对象的属性值时:会自动到原型链中查找;
设置 对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值;
方法一般定义在原型中,属性一般通过构造函数定义在对象本身上;

5.5 探索instanceof

A instanceof B: 探索对象A是否是函数B的实例

  • 如果B函数的显式原型对象在A对象的原型链上,返回true;
  • 否则返回false;

5.6 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>05_原型对象</title>
</head>
<body>
    <script type="text/javascript">
        // 原型
        function Fn(){
            console.log("函数Fn()")

        }
        console.log(Fn.prototype);   // Object test: ƒ ()  constructor: ƒ Fn().....
        console.log(typeof Fn.prototype);    // Object 
        Fn.prototype.test = function(){} 
        console.log(Fn.prototype.constructor === Fn);  // true
        var obj1 = new Fn();
        obj1.test(); // 函数Fn()

        // 显示原型和隐式原型
        function Fn2(){} // 内部语句:this.prototype={}
        console.log(Fn2.prototype) // Object{constructor:Function}

        var obj2 = new Fn2() // 内部语句:this.__proto__=fn.prototype
        console.log(obj2.__proto__) // Object{constructor:Function}
        console.log(Fn2.prototype===obj2.__proto__) // true
        Fn2.prototype.test2 = function(){
            console.log("新方法Test2()")
        }
        obj2.test2();  // 新方法Test2()



        // 原型链
        function Fn3(){
            this.test3 = function(){
                console.log("test3()")
            }
        }
        Fn3.prototype.test4 = function(){
            console.log("test4()")
        }
        console.log(Fn3.prototype)
        /*
        * {test4: ƒ, constructor: ƒ}
        *   test4: ƒ ()
        *   ...
        *   [[Prototype]]: Object
        *       ...
        *       __proto__: (...)
        *       ....
        * 
        * 意思是说, 函数的prototype属性,默认值是一个空的Object对象;
        * 而这个Object对象, 因为拥有__proto__属性, 是一个(Object的)实例对象;
        * __proto__属性值也是地址值, 默认值为其构造函数的prototype属性值;
        * 注:  每个实例对象都有一个__proto__, 即隐式原型;
        *      实例对象.__proto__ === 构造函数.prototype
        */
        console.log(Fn3.prototype.__proto__)
        /* 以下为Object的内容, 内含一些方法
        * 这些方法可以被其的实例对象继承
        * {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ
        *   ...
        *   hasOwnProperty:...
        *   toString:...
        *   valueOf:...
        *   __proto__: (...)
        *   ...
        */
        console.log(Fn3.prototype.__proto__.__proto__) // null


        var obj3 = new Fn3()
        obj3.test3() // test3()
        obj3.test4() // test4()
        console.log(obj3.toString()) // [object Object] 
        // obj3.test5() // Uncaught TypeError: obj3.test5 is not a function
        console.log(obj3.test5) // undefined

        /*
        * obj3.test3() : 
        *   获得栈空间中obj3的地址值, 去找Fn3的实例对象obj3
        *   在自身找到test3, 返回;
        * obj3.test4(): 
        *   获得栈空间中obj3的地址值, 去找Fn3的实例对象obj3
        *   在自身找没有test4, 去实例对象的__proto__指向的地址值找
        *   有实例对象.__proto__ = 其构造函数.prototype
        *   找到test4, 返回;
        * 
        * obj3.toString(): 
        *   获得栈空间中obj3的地址值, 去找Fn3的实例对象obj3
        *   在自身找没有test4, 去实例对象的__proto__指向的地址值找
        *   有实例对象.__proto__ = 其构造函数.prototype
        *   其实际上是定义函数时创建的Object对象(其构造函数.prototype),有__proto__值
        *   继续寻找, 找到Object的原型对象, 拥有toString, hasOwnProperty等多个方法, 返回;
        * 
        * obj3.test5():
        *   层层查找, 直到找到Object的原型对象, 他找到__proto__
        *   但是__proto__值是null
        *   返回
        * --> 原型链的尽头是null, 且实际上原型链是沿着__proto__向上查找
        */


        // 原型链补充
        // 函数的显式原型指向的对象:默认是空Object的实例对象(但Object自身不满足);
        console.log(Fn3.prototype instanceof Object); // true
        console.log(Object.prototype instanceof Object); // false
        console.log(Function.prototype instanceof Object); // true

        // Function是它自身的实例,所有函数都是Function的实例(包括Function本身);
        console.log(Function.prototype === Function.__proto__)  // true

        // Object的原型对象是原型链的尽头  
        console.log(Object.prototype.__proto__) // null

        
        // 原型链属性问题
        function Fn4(){

        }
        Fn4.prototype.a = 'xxx';
        var obj4 = new Fn4();
        console.log(obj4.a); // xxx
        var obj5 = new Fn4();
        obj5.a = 'yyy';
        console.log(obj4.a, obj5.a); // xxx yyy
        console.log(obj4);
        /*
        Fn4 {}
        [[Prototype]]: Object
            a: "xxx"
            ...
        */
        console.log(obj5);
        /*
        Fn4 {a: 'yyy'}
            a: "yyy"
            [[Prototype]]: Object
            a:"xxx"
            ...
        */

        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("Amy");
        console.log(p1);
        /*
        Person {name: 'Amy', age: 12}
            age: 12
            name: "Amy"
            [[Prototype]]: Object
                setName: ƒ (name)
                ...
        */
        var p2 = new Person("Jack", 15);
        console.log(p2);
        /*
        Person {name: 'Jack', age: 15}
            age: 15
            name: "Jack"
            [[Prototype]]: Object
                setName: ƒ (name)
                ...
        */
        console.log(p1.__proto__ === p2.__proto__); // true

        // 探索instanceof
        function Foo(){

        }
        var obj6 = new Foo();
        console.log(obj6 instanceof Foo); // true
        console.log(obj6 instanceof Object); // true 

        console.log(Object instanceof Function); // true
        console.log(Object instanceof Object);  // true
        console.log(Function instanceof Function);  // true
        console.log(Function instanceof Object);  // true
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>05_原型对象_面试</title>
</head>
<body>
    <script type="text/javascript">
        // 练习1
        var A = function(){};
        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);
        // 1**, undefined**, 2, 3
        console.log(b);
        /*
        [[Prototype]]: Object
            n: 1
            ....
        */
        console.log(c);
        /*
        [[Prototype]]: Object
            m: 3
            n: 2
            ....
        */

        /* 原理解析: b.n = 1, b.3 = undefined:
        * 定义A,var A = function(){}, 相当于function A(){}
        * A是一个函数, 函数的原型对象默认是一个空的Object对象
        * 此时, A.prototype = {};
        * 运行"A.prototype.n = 1;" , 有 A.prototype = {n:1}
        * 即A.prototype地址值为0x123, 指向内容为{n:1}的对象
        * 此时 "var b = new A();" b是A的实例
        * 则 b.__proto = A.prototype = 0x123
        * 
        * 由"A.prototype = {n:2, m:3}"赋值更新,
        * A.prototype地址值更新,为0x456, 指向另一个对象{n:2, m:3}
        * 此时"var c = new A();" c是A的实例
        * 则 c.__proto = A.prototype = 0x456
        * 
        */

        

        // 练习2
        function F(){}
        Object.prototype.a = function(){
            console.log("a");
        }
        Function.prototype.b = function(){
            console.log("b");
        }
        var f = new F();
        F.a(); // a
        F.b(); // b
        f.a(); // a
        f.b(); // f.b() is not a function ***
        
        /* 解析:
        * f.b() : f.b() is not a function
        *  b()在Function.prototype里
        * 而f的原型链:
        *   因为f = new F(), 则 f.__proto__ = F.prototype
        *   F.prototype, 默认是一个空的Object对象
        *   则 F.prototype.__proto__ = Object.prototype
        *       存在Object.prototype.a, 所以f.a()可以运行;
        *   再顺着__proto__往下找
        *   但是Object.prototype.__proto__ = null
        *   Object的显式原型的隐式原型是原型链的终点
        *       未找到b(), 所以f.b() is not a function ***
        * 
        * F的原型链:
        *   F由"function F(){}"声明, 相当于 var F = function(){}
        *   则F是function Function的实例对象
        *   则F.__proto__ = Function.prototype
        *       存在Function.prototype.b, 存在F.b();
        *   再往下找函数的原型(Function.prototype)默认是一个空的Object对象
        *   则 F.prototype.__proto__ = Object.prototype
        *       存在Object.prototype.a, 存在F.a();
        *  
        * 
        */
    </script>
</body>
</html>

6. 执行上下文和指向上下文栈

6.1 变量提升和函数提升

变量声明提升

  • 通过var定义的变量在这行定义语句前就能访问到;
  • 值: undefined;

函数声明提升

  • 通过function声明的函数在声明之前就能访问到;
  • 值: 通过function定义的函数本身;

先变量提升再函数提升;
在函数中使用未声明的变量会自动声明成全局变量;

6.2 执行上下文

代码分类

  • 全局代码
  • 函数代码(局部)

全局执行上下文

  • 在执行全局代码前将window确定为全局执行上下文;
  • 对全局数据进行预处理
    • var定义的全局变量–>undefined, 添加为window的属性
    • function声明的全局函数–>赋值为这个函数, 将全局函数添加为window的方法
    • this–>赋值为window

函数执行上下文

  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
  • 对局部数据进行预处理
    • 声明形参变量–>赋值为实参的值–>添加为执行上下文的属性
    • arguments–>赋值为实参列表, 添加为执行上下文属性
    • var定义的局部变量–>赋值为undefined, 添加为执行上下文属性
    • function声明的函数–>赋值为这个函数本身, 添加为执行上下文方法
    • this–>赋值为调用这个函数的对象
  • 开始执行函数上下文

6.3 执行上下文栈

全局代码执行前, JS引擎会创建一个上下文栈来存储管理执行上下文
在全局执行上下文(window)确定后, 将其添加到栈中
在函数执行上下文创建后, 将其添加到栈中
在当前函数执行完后, 将栈顶对象移除
当所有代码执行完毕后栈中只剩下window

6.4 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>06_执行上下文</title>
</head>
<body>
    <script type="text/javascript">
        // 变量提升
        var a = 3;
        function Fn1(){
            console.log(a);
            var a = 4;
        }
        Fn1(); // undefined

        // 函数提升
        console.log(b); // undefined 变量提升
        Fn2(); // Fn2() 可调用 函数提升
        // Fn3(); // Uncaught TypeError: Fn3 is not a function 不可调用, 变量提升
        var b = 3;
        function Fn2(){
            console.log("Fn2()");
        }
        var Fn3 = function(){
            console.log("Fn3()");
        }

        // 全局执行上下文
        console.log(a1, window.a1); // undefined undefined
        a2(); // a2()
        console.log(this); // Window {window: Window, self: Window....
        var a1=4;
        function a2(){
            console.log('a2()');
        }
        console.log(a1); // 4

        // 函数执行上下文
        function Fn4(a1){
            console.log(a1); // 2
            console.log(a2); // undefined
            a3(); // a3()
            console.log(this); //Window {window: Window, self: Window....
            console.log(arguments);
            /*
            * Arguments(2) [2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            *    0: 2
            *    1: 3
            *    callee: ƒ Fn4(a1)
            *    ...
            */

            var a2=3;
            function a3(){
                console.log('a3()');
            }
        }
        Fn4(2, 3)

        // 执行上下文栈
            // 1. 进入全局执行上下文
        console.log("***** 以下是执行上下文栈 *****")
        var c = 10 ;
        var bar = function(x){
            var b = 5;
            foo(x+b); // 3. 进入foo执行上下文
        }
        var foo =function(y){
            var c = 5;
            console.log(a+c+y);
        }
        bar(10); // 2. 进入bar函数执行上下文

        // 输出: 23 <-- 不会报错,因为在产生
        // 产生了3个执行上下文栈, 1. window, 2. bar(调用时产生), 3 foo
        // 栈的意思是:后进先出 
        //     全局代码执行前, JS引擎会创建一个上下文栈来存储管理执行上下文   
        //     在全局执行上下文(window)确定后, 将其添加到栈中 
        //     函数执行上下文创建后, 将其添加到栈中
        //     在当前函数执行完后, 将栈顶对象移除  
        //     当所有代码执行完毕后栈中只剩下window

        console.log('global begin: ' + i);
        var i = 1;
        foo2(1);
        function foo2(i){
            if(i==4){
                return;
            }
            console.log('foo2() begin: ' + i);
            foo2(i+1)
            console.log('foo2() end: ' + i);
        }
        console.log('global end: ' + i);
        /* 依次输出:
        * global begin: undefined
        * foo2() begin: 1
        * foo2() begin: 2
        * foo2() begin: 3
        * foo2() end: 3 (递归,函数堆栈,执行完栈顶函数后弹出,依次执行完直到window)
        * foo2() end: 2
        * foo2() end: 1
        * global end: 1 (形参在函数执行结束后被释放, 此时的i是window.i = 1)
        * 
        * 调用了4次foo
        * 产生了5个执行上下文
        */

    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>06_执行上下文_面试</title>
</head>
<body>
    <script type="text/javascript">
        // 练习1
        function a(){}
        var a;
        console.log(typeof a) // function
        /* 解析:
        * 存在函数提升和变量提升,
        * 先执行变量提升, 再执行函数提升,
        * 后面的会覆盖前面的;
        */


        // 练习2
        if(! (b in window)){  // window中是否有b属性
            var b = 1;
        }
        console.log(b); // undefined
        /* 解析:
        * b in window: window中有没有b属性
        *   1. 如果window中存在b, 取反为假, b输出为undefined
        *   2. 如果window中不存在b, 取反为真, 执行b=1, 输出为1
        * 结果可知, window中会存在b;
        */

        
        // 练习3
        var c = 1;
        function c(c){
            console.log(c);
        }
        c(2); // Uncaught TypeError: c is not a function
        /* 解析:
        * 执行顺序: 
        *   var c --> function c --> c=1 -->c(2), 报错, c不是函数
        * 
        */
        

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

7. 作用域与作用域链

7.1 作用域

理解:

  • 相当于一块地盘, 一个代码段所在的区域, 相当于内部封闭;
  • 静态的(相对于上下文对象), 在编写代码时就已经确定

分类:

  • 全局作用域;
  • 函数作用域
  • 没有块作用域 ('{}'内的内容)

作用:

  • 隔离变量, 不同作用域中同名变量不会有冲突

7.2 作用域和执行上下文

区别1

  • 函数作用域在函数定义时创建而非函数调用时
  • 全局执行上下文在全局作用域创建之后, JS代码执行之前创建
  • 函数执行上下文在调用函数时, 函数体代码未执行前创建

区别2

  • 作用域是 静态 的, 只要函数定义好后就一直存在且不会变化
  • 执行上下文是 动态 的, 调用函数时创建, 函数执行完成后自动销毁

联系

  • 作用域从属于所在的执行上下文
  • 全局作用域–>全局执行上下文
  • 函数作用域–>函数执行上下文

7.3 作用域链

理解

  • 多个上下级关系的作用域形成的链的方向是 从内向外
  • 查找变量时是 沿着作用域链 来查找的

查找上一个变量的查找规则

  • 在当前作用域下的上下文中查找对应属性, 有则返回没有进入2
  • 在上一级作用下的上下文中查找对应属性, 有则返回没有进入3
  • 依次执行2直到处在全局作用域中, 在全局上下文中查找对应属性, 有则返回没有报错

7.4 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>07_作用域</title>
</head>
<body>
    <script type="text/javascript">
        // 作用域

        // 没有块作用域
        if(true){
            var z=10;
        }
        console.log(z); // 可以访问x 10

        // 全局作用域
        var a = 10;
        var b = 20;

        function Fn1(x){ // Fn1 作用域
            var a = 100;
            var c = 200;
            console.log('fn()', a, b, c, x); // fn(), 100, 20, 200, 10

            function bar(x){ // bar作用域
                var a =  1000,
                    d=400;
                console.log('bar()', a, b, c, d, x); 
                
            }
            bar(100); // bar(), 1000, 20, 200, 400, 100
            bar(200); // bar(), 1000, 20, 200, 400, 200
        }
        Fn1(10);
        
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>07_作用域_面试</title>
</head>
<body>
    <script type="text/javascript">
        // 练习1
        var x = 10;
        function Fn1(){
            console.log(x);
        }
        function Show(f){
            var x = 20;
            f();
        }
        Show(Fn1);  // 10**

        /*解析:
        * 因为函数作用域在一开始(调用前)就创建好了的, 是静态的
        * 所以不会在后面调用的时候动态去寻找
        * 所以作用域的关系是:
        * 全局(Fn1, Show); 
        * 所以输出x时, 先在Fn1查找,未找到,去到上一级全局作用域中查找
        * 输出window.x = 10
        */

        // 练习2
        var Fn2 = function(){
            console.log(Fn2);
        }
        Fn2(); // ƒ (){ console.log(Fn2); }

        var obj = {
            yy: 10,

            Fn3 : function (){
                console.log(Fn3);
            },
            
            Fn4 : function Fn4(){
                console.log(Fn4); 
            },

            Fn5 : function (){
                console.log(this.Fn5);
            },

            Fn6 : function (){
                console.log(obj.Fn6);
            },

            Fn7 : function (){
                console.log(yy);
            },

            Fn8 : function (){
                console.log(this.yy);
            },
        }
        obj.Fn7(); // Uncaught ReferenceError: yy is not defined
        obj.Fn8(); // 10
        obj.Fn6(); // ƒ (){ console.log(obj.Fn5); }
        obj.Fn5(); // ƒ (){ console.log(this.Fn5); }
        obj.Fn4(); // ƒ Fn4(){ console.log(Fn4); }
        obj.Fn3(); // Uncaught ReferenceError: Fn3 is not defined **
        
        /*解析:
        * 调用Fn3
        * 先在函数作用域找, 未找到
        * 然后跳出, 到上级作用域找,仍然没有找到
        * 
        * 如果想要输出函数内容, 代码应该是:
        *   console.log(this.Fn3)
        */
    </script>
</body>
</html>

8. 闭包

8.1 闭包的基本概念

如何产生闭包

  • 产生闭包:当一个嵌套在外部函数中的内部函数,引入并使用了外部函数的变量就产生了闭包;
    与外部函数的变量的值无关;

闭包是什么

  • 使用Chrome调试时,可以看到的包含了被引用的(外部函数的)变量的Closure对象;

产生闭包的条件

  • 存在函数嵌套(函数(外部函数)内存在函数(内部函数));
  • 内部函数引入了外部函数的数据,且内部函数已经别定义;

    根据内部函数定义的方式不同,产生闭包的位置可能不同,
    闭包在嵌套的内部函数 完成定义 时产生:

    • 使用 函数表达式 定义函数:
      var Fn = function() ..., 要执行到这一段代码才产生闭包;
    • 使用 函数声明 定义函数:
      function Fn()..., 在进入内部函数时就完成了产生闭包;
  • 外部函数被调用执行

常见的两种闭包

  • 将内部函数作为外部函数的返回值进行返回;
  • 将函数作为一个实参传给另一个函数;

闭包的作用

  • 在执行完外部函数后,它的内部变量(存在在闭包中的)依然存留在内存中,即延长了变量的生命周期,(一般的外部函数调用完成后,内部变量会被释放);
  • 一般的(未在闭包中的)外部函数的变量,依旧会在外部函数调用完成后被释放;
  • 在函数的外部可以操作(读写)到函数内部的数据,但不是将变量直接暴露给外部;

闭包的生命周期

  • 在嵌套的内部函数完成定义时产生;
  • 在嵌套的内部函数成为垃圾对象时死亡(例如: f = null);

8.2 闭包的应用

自定义js模块(流程)

  • 创建具有特点功能的JS文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外部暴露一个有n个方法的函数或者对象
  • 模块的使用者,只需要通过模块暴露的函数或者对象来使用该模块的功能

8.3 闭包的缺点及解决方法

缺点

  • 外部函数执行完以后,函数内部的局部变量没有被释放,占用内存的时间会变长
  • 容易造成内存泄漏(内存被占用,但是没有使用)

解决办法

  • 尽量不使用闭包
  • 记得尽早释放

8.4 内存溢出和内存泄漏

内存溢出

  • 程序运行时出现的错误(死循环, 堆栈)
  • 当程序运行需要的内存超过了所有的内存,就抛出内存溢出的错误

内存泄漏

  • 被占用的内存没有被及时释放
  • 内存泄漏过多容易造成内存溢出
  • 常见的内存泄漏
    • 意外的全局变量(意外的意思是,原本是只想要它是局部变量,但是定义成了全局)
    • 没有及时处理的计时器或者回调函数
    • 闭包

8.5 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01_引入</title>
</head>
<body>
    <button>测试1</button>
    <button>测试2</button>
    <button>测试3</button>
    <script type="text/javascript">
        var btns = document.getElementsByTagName('button');
        for(var i=0;i<btns.length;i++){

            // 任意点击, 都是第4个
            //  当函数执行时, 循环已经结束
            // var btn = btns[i];
            // btn.onclick = function(){
            //     alert('第' + (i+1) + '个');
            //     
            // }

            // 方法1 : 以下代码功能正常
            // btns[i].index=i
            // btns[i].οnclick=function(){
            //     alert("第"+(this.index+1)+"个button")
            // }

            // 方法2  使用闭包
            (function(i){
                var btn = btns[i];
                btn.onclick = function(){
                    alert('第' + (i+1) + '个');
                }
            })(i)

        }
        
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>02_常见的闭包</title>
</head>
<body>
    <script type="text/javascript">
        // 1. 将内部函数作为外部函数的返回值进行返回;
        function fn1(){
            var a = 2;
            function fn2(){
                // 此处为内部函数,使用了外部函数中的a变量
                a++;
                console.log(a);
            }
            return fn2;
        }
        var f = fn1();
        f(); // 3
        f(); // 4
        var f2 = fn1();
        f2(); // 3
        f2(); // 4

        // 2. 将函数作为一个实参传给另一个函数;
        function showDelay(msg, time){
            // 外部函数
            setTimeout(
                // setTimeout() 是属于 window 的方法
                // 该方法用于在指定的毫秒数后调用函数或计算表达式。
                function(){
                    // 回调函数   
                    // 此处为内部函数,使用了外部函数中的msg变量
                    alert(msg);
            }, time)
        }
        showDelay('tt', 2000);


    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03_闭包的生命周期</title>
</head>
<body>
    <script type="text/javascript">
        function fn1(){
            // 此时闭包fn2已产生(函数提升,内部函数对象已经创建)
            var a = 2;
            function fn2(){
                a++;
                console.log(a);
            }
            var fn3 = function(){  //闭包fn3在本行才产生
                a--;
                console.log(a);
            }
            return fn2;
        }
        var f = fn1();
        f(); // 3
        f(); // 4
        f = null; 
        // 闭包fn2死亡
        // 原理:是包含闭包的函数对象(object)成为垃圾对象,即没有被引用
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>04_闭包的应用</title>
</head>
<body>
	<script type="text/javascript" src="module1.js"></script>
	<script type="text/javascript" src="module2.js"></script>

    <script type="text/javascript">
        // 方法1
        // js文件中是函数return的情况
        // 这种情况必须先执行函数
        // var fn = MyModule();
        // fn.doSomeThing(); // doSomeThing() MODULE1
        // fn.doOtherThing(); // doOtherThing() module1

        // 方法2
        // js文件中是匿名函数自调用
        // 这种情况只要引入js文件就可以得到想要的对象
        console.log(window.myModule2); //{doSomeThing: ƒ, doOtherThing: ƒ}....
        myModule2.doSomeThing() // doSomeThing() MODULE2
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>05_闭包_面试</title>
</head>
<body>
    <script type="text/javascript">

        // 练习1
        var name='The Window'
        var object={
            name:"My Object",
            getNameFunc:function(){
                return function(){
                    // 内部函数
                    return this.name
                }
            }
        }
        console.log(object.getNameFunc()()) //The Window
        /*解析:
        * object.getNameFunc() 返回内部函数
        * 接下来执行的是:内部函数()
        * this为window
        * 
        * 存在嵌套函数,但是没有构成闭包
        * 因为内部函数没有引入外部函数的变量
        */

        // 练习2
        var name2="The Window2"
        var object2={
            name2:"My Object2",
            getNameFunc:function(){
                var that=this
                return function (){
                    return that.name2
                }
            }
        }
        console.log(object2.getNameFunc()()) //My Object2
        /*解析:
        * 执行object2.getNameFunc()
        * 执行'that = this'语句,此时的that = this = object2
        * 返回 function 内部函数,执行内部函数()
        * 输出that.name2, that = object2
        * 所以输出 My Object2
        * 
        * 存在嵌套函数,且内部函数使用了外部函数的变量that
        * 构成闭包
        */

        // 练习3 *******
        function fun(n, o){
            console.log("fun: fun(n, o)....", n)

            console.log(o);

            return {
                fun: function(m){
                    console.log("fun: function(m)", m, n)
                    return fun(m, n); 
                }
            }
        }
        var a = fun(0);
        a.fun(1);
        a.fun(2);
        a.fun(3);
        // undefined, 0, 0, 0

        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>

9. 对象创建模式

9.1 对象创建模式

方法1:Object构造函数模式

  • 套路: 先new来创建空对象, 再动态添加新的属性/方法
  • 使用场景: 起始时不知道对象内部的数据
  • 问题: 语句太多

方法2: 对象字面量模式

  • 套路: 使用{}创建空对象, 再动态添加新的属性/方法
  • 使用场景: 起始时已知对象内部的数据
  • 问题: 如果创建多个对象, 代码会重复

方法3: 工厂模式

  • 套路: 使用工厂函数动态创建对象并返回
  • 使用场景: 需要创建多个对象
  • 问题: 对象没有具体类型, 都是Object

方法4: 自定义构造函数

  • 套路: 自定义构造函数, 使用new创建
  • 使用场景: 需要创建多个类型确定的对象
  • 问题: 如果实例化多次会使同样的方法重复占用内存空间

方法5: 自定义构造函数+原型对象组合使用

  • 套路: 自定义构造函数, 使用new创建, 添加方法时使用原型对象来添加
  • 使用场景: 需要创建多个类型确定的对象

9.2 继承模式

原型继承

  • 缺点是如果父函数有一个变量为引用类型, 任意一个实例修改这个变量会导致所有实例的相关属性被修改
  • 套路:
    • 构造父函数
    • 给父函数的原型添加新方法
    • 构造子函数
    • 使子函数的原型对象成为父函数的实例(关键一步, 此处使原型链能够继承)
    • 给子函数的原型对象添加新方法

借用构造函数继承(假继承, 实际上并没有继承父类型方法)

  • 缺点是父类有方法时会被创建多次
  • 套路:
    • 创建父类型构造函数
    • 创建子类型构造函数
    • 在子类型构造函数中使用call/apply调用父类型构造函数

寄生式继承

  • 缺点是方法没有放到原型中无法复用
  • 套路:
    • 创建父类型构造函数
    • 创建子类型构造函数
    • 在子类型构造函数中使用call/apply调用父类型构造函数

组合继承(借用构造函数继承+原型继承)

  • 缺点是构造函数执行了两次
  • 套路:
    • 创建父类型构造函数
    • 创建子类型构造函数
    • 在子类型构造函数中使用call/apply调用父类型构造函数

9.3 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>09_创建对象</title>
</head>
<body>
    <script type="text/javascript">
        // 1.Object构造函数模式
        var p1 = new Object()
        p1.name = 'TT'
        p1.age = 12
        p1.setName = function(){
            this.name = name;
        }
        p1.setName('Jack')

        // 2.对象字面量模式
        var p2 = {
            name:'Tom',
            age:12,
            setName : function(name){
                this.name = name;
            }
        }

        // 3.工厂模式(*)
        function createPerson(name, age){
            var obj = {
                name:name,
                age:age,
                setName : function(name){
                    this.name = name;
                }
            }
        }
        var p3 = createPerson('Amy', 15)

        // 4.自定义构造函数模式
        function Person(name, age){
            this.name = name,
            this.age = age,
            this.setName = function (name){
                this.name = name
            }
        }
        var p4 = new Person('Bob', 15)

        // 5.构造函数+原型模式
        function Person2(name, age){
            this.name = name,
            this.age = age
        }
        Person2.prototype.setName = function (name){
            this.name = name
        }
        var p4 = new Person2('cat', 15)

    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>09_创建对象_继承</title>
</head>
<body>
    <script type="text/javascript">
        // 父类型
        function Supper(){
            this.supProp = 'supper';
        }
        Supper.prototype.showSupper = function(){
            console.log(this.supProp);
        }

        // 子类型
        function Sub(){
            this.subProp = 'sub';
        }
        
        // 1. 原型链继承
        Sub.prototype = new Supper() // 完成继承
        
        Sub.prototype.showSub = function(){
            console.log(this.subProp);
        }
        var sub1 = new Sub();
        sub1.showSub(); // sub
        sub1.showSupper(); // supper
        
        console.log(sub1.constructor) 
        console.log(Sub.prototype.constructor) 
        // ƒ Supper(){this.supProp = 'supper';}

        Sub.prototype.constructor = Sub 
        console.log(Sub.prototype.constructor) 
        console.log(sub1.constructor) 

        // ƒ Sub(){this.supProp = 'supper';}


        // 2. 借用构造函数继承(假的)
        function Person(name, age){
            this.name = name
            this.age = age
        }
        function Student(name, age, price){
            Person.call(this, name, age)
            // 只是调用方法,没有真的继承
            this.price = price
        }
        var p1 = new Student('XXX', 12, 1200)
        console.log(p1.name, p1.age, p1.price)
        // XXX 12 1200

        // 3. 组合继承
        Person.prototype.setName = function(name){
            this.name = name;
        }
        function Teacher(name, age, price){
            Person.call(this, name, age)
            this.price = price
        }
        Teacher.prototype = new Person();
        Teacher.prototype.constructor = Teacher;
        Teacher.prototype.setPrice = function(price){
            this.price = price;
        }
        var t1 = new Teacher('Kim', 25, 15000);
        t1.setName('Kim1')
        t1.setPrice(16000)
        console.log(t1.name, t1.age, t1.price)
        // Kim1 25 16000

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

10. 进程与线程

10.1 基本概念

进程:程序的一次执行,在内存中占用一片独立的内存空间;

线程:是进程里的一个独立执行单元,是程序执行的一个完整的流程,是CPU的最小调度

应用程序必须运行在某个进程的某个线程上;
一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建;
一个进程中也可以同时运行多个线程;
一个进程内的数据可以让其中多个线程共享;
多个进程之间的数据是相互独立的;
线程池: 保存多个线程对象的容器, 实现线程对象重复利用;

比较单线程与多线程

  • 多线程优点
    • CPU利用效率高
  • 多线程缺点
    • 创建多线程开销
    • 线程间切换开销
    • 死锁与状态同步问题
  • 单线程优点
    • 顺序编程简单易懂
  • 单线程缺点
    • 效率低

JS的单线程与多线程

  • js单线程运行
  • 使用H5的Web Workers可以多线程运行

浏览器运行是单线程还是多线程?

  • 多线程

浏览器运行是单进程还是多进程?

  • 有单进程如火狐与老版IE 也有多进程如chrome与新版IE

10.2 浏览器内核

支撑浏览器运行的最核心的程序

不同浏览器可能不一样

  • chrome, safari: webket
  • firefox: Gecko
  • IE: Trident

内核由多个模块组成

  • 主线程模块
    • js引擎模块, 负责js的编译与运行
    • html, css文档解析模块, 负责页面文本的解析
    • DOM/CSS模块, 负责DOM/CSS在内存中的相关处理
    • 布局和渲染模块, 负责页面的布局与效果的绘制
  • 分线程模块
    • 定时器模块, 负责定时器管理
    • 事件响应模块, 负责事件的管理
    • 网络请求模块, 负责ajax请求

10.3 定时器引起的思考

定时器真的是定时执行吗

  • 无法保证真正定时执行
  • 一般会延迟一点, 也可能延迟很长时间

定时器的回调函数是在分线程执行的吗

  • 不是, js是单线程的

定时器如何实现的

  • 事件循环模型

10.4 JS是单线程执行的

如何证明js执行是单线程的

  • setTimeout()函数是在主线程执行的
  • 定时器回调代码只有在运行栈中的代码全部执行完后才执行

为什么js要用单线程模式而不是多线程模式

  • 作为浏览器脚本语言主要用途在于与用户互动及操作DOM, 这决定必须为单线程执行, 否则有严重的同步问题

代码分类

  • 初始化代码
  • 回调代码

js引擎执行代码的基本流程

  • 先执行初始化代码, 包含一些特殊代码
    • 设置定时器
    • 绑定监听
    • 发送ajax请求
  • 某个时刻后再执行回调代码 使用alert()能暂停主线程执行与定时器计时

10.5 事件循环模型

  • 执行栈(execution stack)
  • 浏览器内核(browser core)
  • 回调队列(callback queue)
    • 消息队列(task queue)
    • 任务队列( event queue)
    • 事件队列( event queue)
  • 事件轮询(主线程队列与回调队列的事件执行顺序)( event loop)
  • 事件驱动模型(同步与异步的执行)(event-driven interaction model)
  • 请求响应模型(request-response model)

模型的运转流程(面试时问道可以画图说明)

  • 执行初始化代码,将事件回调函数交给对应模块管理
  • 当事件发生时,管理模块会将回调函数及其数据添加到回调列队中
  • 只有当初始化代码执行完后(可能要一定时间), 才会一个一个遍历读取回调队列中的回调函数执行。

10.6 H5 Web Workers多线程

介绍

  • Web Workers是HTML5的多线程解决方案
  • 可以将一些大计算量的代码交由Web Workers运行而不冻结用户界面
  • 但是子线程完全受主线程控制且不能操作DOM, 因此没有改变JS是单线程的属性

使用

  • 创建在分线程执行的js函数
  • 向主线程的js中发消息并执行回调

不足

  • 不能跨域加载js
  • worker内代码不能操作dom
  • 不是每个浏览器都支持
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值