当前位置:   article > 正文

JavaScript高级知识汇总(高级篇)_"var a = {age:12}; var b = a; a = {name:\"mike\",a

"var a = {age:12}; var b = a; a = {name:\"mike\",age:13}; b.age = 14; console.l"

JavaScript高级知识总结(高级篇)

一、深入基础

1.1数据类型

1.分类:
-基本类型
-String:任意字符串
-Number:任意的数字
-boolean: true/false
-undefined:未定义
-null:空

-引用(对象)类型
-Object:任意对象
-Function:一种特别的对象(可以执行)
-Array:一种特别的对象(数值下标,内部数据是有序的)

2.判断:
-typeof:
可以判断:undefined、数值、字符串、布尔值
不能判断: null与object object与array
instanceof:可以判断对象的具体类型
=== 可以判断undefined、null

undefined与null的区别?
* undefined表示定义未赋值
* null表示赋值了,值为null
什么时候给变量赋值为null?
* 初始赋值,表明将要赋值为对象
* 结束前,让对象成为垃圾对象(被垃圾回收器回收)
3.严格区别变量类型与数据类型?
* 数据的类型
- 基本类型
- 对象类型
* 变量的类型(变量内存值的类型)
-基本类型:保存就是基本类型的数据
-引用类型:保存的是地址值

1.2数据变量与内存

1.什么是数据?
-存储在内存中代表特定的信息,本质上是010101…
-数据的特点:可传递、可运算
-内存中所有操作的目标:数据
逻辑运算
算数运算
赋值
运行函数

2.什么是内存?
-内存条通电后产生的可储存数据的空间(临时的)
-内存产生和死亡:内存条==>通电==>产生内存空间==>存储数据==>处理对象==>断电==>内存空间和数据消失
-一小块内存的2个数据
-内部存储的数据
-地址值

-内存分类
-栈:存储全局变量、局部变量
-堆:存储对象

3.什么是变量?
-可变化的的量,又变量名和变量值组成
-每个变量都对应的一小块内存,变量名用来查找对应的内存,变量值就是内存中保存的数据

4.内存,数据,变量三者之间的关系
-内存用来存储数据
-变量是内存的标识

5.var a = xxx; a内存中到底保存的是什么?
- xxx 是基本数据,保存的就是这个数据
- xxx 是对象,保存的是对象的地址值
- xxx 是一个变量,保存的xxx的内存内容(可能是基本数据,也可能是地址值)

6.关于引用变量的赋值问题:
2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据。

2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象。

<script>
          var obj1 = {name:"tom"};
          var obj2 = obj1;
          obj2.name = "bob";
          console.log(obj1.name);   //bob

          function fun(obj){
            obj.name = "TIMI";
          };

          fun(obj1);
          console.log(obj2.name);   //TIMI


          var a = {age:12};
          var b = a;
          a = {name:"mike",age:13};
          b.age = 14;
          console.log(b.age,a.name,a.age);  //14,mike,13

          function fun2(obj){
            obj = {age:15};
          };

          fun2(a);
          console.log(a.age);   //13

    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

7.在js调用函数时传递变量参数时,是值传递还是引用传递?
- 理解1:都是值(基本/地址值)传递
- 理解2:可能是值传递,也可能是引用传递(地址值)、

<script>
        var a = 3;
        function fun1(a){
            a = a+1;
        };
        fun1(a);
        console.log(a);//3

        function fun2(obj){
            console.log(obj.name);
        };

        var obj = {name:"tom"};
        fun2(obj);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

8.js引擎如何管理内存
1.内存生命周期
- 分配小内存空间,得到它的使用权
- 储存数据,可以反复进行操作
- 释放小内存空间

2.释放内存
-局部变量:函数执行完自动释放
对象:成为垃圾对象–>垃圾回收器回收

1.3对象

1.什么是对象?
- 多个数据的封装体
- 用来保存多个数据的容器
- 一个对象代表现实中的一个事物

2.为什么用对象?
- 统一管理多个数据

3.对象的组成
- 属性:属性名(字符串)和属性值(任意)组成
- 方法:一种特别的属性(属性值是函数)

4.如何访问对象内部的数据?
- .属性名: 编码简单,有时不能用
- [“属性名”]: 编码麻烦,能通用

5.什么时候使用 [“属性名”] 这种方式
- 属性名包含特殊字符:- 空格等
- 属性名不确定

<script>
         var p = {};
        //  1.给对象p添加一个属性:content-type:text/json
        // p.content-type = "text/json";    不能用
        p["content-type"] = "text/json";
        console.log(p["content-type"]);

        // 2.属性名不确定
        var propName = "myAge";
        var value = 18;
        // p.propName = value; 不能用
        p[propName] = value;
        console.log(p[propName]);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

1.4函数

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

如何定义函数?
函数声明
表达式

如何调用函数?
test(); 函数名(); 直接调用
obj.test(); 通过对象调用
new test(); new调用
test.call/apply(obj); 临时让test成为obj的方法进行调用

回调函数

什么是回调函数?
1.自己定义的
2.没有调用
3.最终执行了(在某个时刻或某个条件下)

常见的回调函数?
1.dom事件回调函数
2.定时器的回调函数
3.ajax请求回调函数
4.生命周期回调函数

<body>
    
    <button id="btn01">回调</button>

    <script>
        var btn01 = document.getElementById("btn01");
        btn01.onclick = function(){
            alert(this.innerHTML);
        };

        // 定时器
        setTimeout(function(){
            alert("到点了");
        },2000);
        
    </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.5 IIFE

IIFE 全称:Immediately-Invoked Function Expression (立即调用函数表达式),别名:匿名函数自调用。

作用:
- 隐藏内部实现
-不会干扰到外部(全局)命名空间
-用它来编码js模块

<script>
         (function(){   //匿名函数自调用
             var a = 3;
             console.log(a+3);
         })();

         var a = 4;
         console.log(a);

         (function(){
             var a = 1;
             function test(){
                 console.log(++a);
             }

             window.$ = function(){     //向外暴露一个全局函数
                return{
                    test:test
                }
             };
         })();

         $().test();    //$是一个函数
                        //$执行后返回的是一个对象

     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

1.6函数中的this

this是什么?
- 在任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 它的值是调用函数的当前对象

如何确定this的值?
- test(); window
- p.test(); p
- new test(); 新创建的对象
- p.call(obj); obj

<script type="text/javascript">
        function Person(color) {
            console.log(this)
            this.color = color;
            this.getColor = function () {
                console.log(this)
                return this.color;
            };
            this.setColor = function (color) {
                console.log(this)
                this.color = color;
            };
        }

        Person("red"); //this是谁? window

        var p = new Person("yello"); //this是谁? p

        p.getColor(); //this是谁? p

        var obj = {};
        p.setColor.call(obj, "black"); //this是谁? obj

        var test = p.setColor;
        test(); //this是谁? window

        function fun1() {
            function fun2() {
                console.log(this);
            }

            fun2(); //this是谁? window
        }
        fun1();
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

二、函数高级

2.1原型与原型链

2.1.1原型(prototype)

1.函数的prototype属性
- 每个函数都有一个prototype属性,它默认指向一个Object空对象(即为原型对象)
- 原型对象中有一个属性constructor,它指向函数对象

2.给原型对象添加属性(一般都是方法)
- 作用:函数的所有实例对象自动拥有原型中的属性(方法)

    <script>
        // 每个函数都有一个prototype属性,它默认指向一个Object空对象(即为原型对象)
        console.log(Date.prototype,typeof Date.prototype);
        function Fun(){

        }  
        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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
2.1.2显示原型与隐式原型

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

     <script>
        //  定义构造函数
        function Fn(){
            // 内部语句:this.prototype = {}

        }

        // 1.每个函数function都有一个prototype,即为显示原型属性,默认指向一个空的Object对象
        console.log(Fn.prototype);

        // 2.每个实例对象都有一个__protp__,可称为隐式原型
        // 创建实例对象
        var fn = new Fn();
        // 内部语句:this.__proto__ = Fn.prototype
        console.log(fn.__proto__);   

        // 3.对象的隐式原型的值为其对应构造函数的显示原型的值
        console.log(Fn.prototype === fn.__proto__); //true

        // 给原型对象添加方法
        Fn.prototype.test = function(){
            console.log("test()");
        }

        // 通过实例调用原型的方法
        fn.test(); 

     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

显示原型与隐式原型的内存结构图
在这里插入图片描述

2.1.3原型链

原型链(别名:隐式原型链)
访问一个对象属性时,
先在自身属性中查找,找到返回
如果没有,再沿着__proto__这条链向上查找,找到返回
如果最终没有找到,则返回undefined

作用:
1.查找对象的属性(方法)
2.构造函数/原型/实体对象的关系(图解)
3.构造函数/原型/实体对象的关系2(图解)

     <script>

        //  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);

        /* Object的原型对象是原型链的尽头 */
        console.log(Object.prototype.__proto__); //null
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

原型链图解
在这里插入图片描述

2.1.4原型链属性问题

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

     <script>
         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("libai");

         var p2 = new Person("jack",12);
         p2.setName("nice");

         console.log(p1);
         console.log(p2);
         console.log(p1.__proto__ === p2.__proto__);  //true
     </script>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
2.1.5探索instanceof

1.instanceof是如何判断的?
表达式:A instanceof B
如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false

2.Function是通过new自己产生的实例

     <script>
         /* 案例1 */

         function Foo(){}

         var f1 = new Foo();
         console.log(f1 instanceof Foo);    //true
         console.log(f1 instanceof Object); //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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
2.1.6原型面试题
<script>
        /* 测试题1*/
        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); //1  undefined  2  3

        /* 测试题2 */
        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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

测试一图解
在这里插入图片描述

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

2.2.1变量提升与函数提升

1.变量的声明提升

  • 通过var定义(声明)的变量,在定义语句之前就可以访问到,值为:undefined

2.函数声明提升

  • 通过function声明的函数,在之前就可以直接调用,值为:函数定义(对象)

3.问题:变量提升和函数提升是如何产生的?

<script>
         /* 面试题 */
        //  输出a的值
        var a = 3;
        function fn(){
            console.log(a);
            var a = 4;
        }

        fn(); //a=undefined


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

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

        var fn3 = function(){
            console.log("fn3()");
        };
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
2.2.2执行上下文

1.代码分类(位置)
- 全局代码
- 函数(局部)代码

2.全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
* var 定义的全局变量–>undefined,添加为window的属性
* function声明的全局函数–>赋值(fun),添加为window的方法
* this–>赋值(window)
- 开始执行全局代码

3.函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
- 对局部数据进行预处理
* 形参变量–>赋值(实参)–>添加为执行上下文的属性
* arguments–>赋值(实参列表),添加为执行上下文属性
* var 定义的局部变量–>undefined,添加为执行上下文的属性
* function声明的函数–>赋值(fun),添加为执行上下文的方法
* this–>赋值(调用函数的对象)
- 开始执行函数体代码

<script>
        /* 全局执行上下文 */
        console.log(a1,window.a1);  //undefined,undefined
        a2();   //a2()
        console.log(this);  //window

        var a1 = 3;
        function a2(){
            console.log("a2()");
        }
        console.log(a1);    //3

        console.log("=========");

        /* 函数执行上下文 */
        function fn(a1){
            console.log(a1);
            console.log(a2);
            a3();
            console.log(this);
            console.log(arguments); //伪数组(2,3)

            var a2 = 3;
            function a3(){
                console.log("a3()");
            }
        }
        fn(2,3);    //2,undefined,a3(),window
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
2.2.3执行上下文栈

1.在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完之后,栈中只剩下window

<script>
        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); //30
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
2.2.4执行上下文2
 <script>
        /* 
            1.以下代码依次输出什么?
                gb:undefined
                fb:1
                fb:2
                fb:3
                fe:3
                fe:2
                fe:1
                ge:1
           
            2.整个过程中产生了几次执行上下文?
                5次
        */

        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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
2.2.5面试题
 <script>
        /* 测试题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); //报错,c不是函数
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.3作用域与作用域链

2.3.1作用域

作用域
1.理解:
- 指一块空间,代码所在的区域
- 它是静态的(相对于上下文对象),在编写代码时就确定了

2.分类:
- 全局作用域
- 函数作用域
- 没有块作用域(ES6开始有了)

3.作用:
- 隔离变量,不同作用域下同名变量不会有冲突

<script>
        // 没有块作用域
        /* 
            if(true){
                var c = 3;
            }
            console.log(c);
        */

        var a = 10;
        var b = 20;
        function fn(x){
            var a = 100;
            c = 300;
            console.log("fn()",a,b,c,x);

            function bar(x){
                var a = 1000;
                var d = 400;
                console.log("bar()",a,b,c,d,x);
            }

            bar(100);
            bar(200);
        }

        fn(10);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
2.3.2作用域与执行上下文

作用域与执行上下文的区别:
区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时,
- 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
- 函数执行上下文是在调用函数时,函数体代码执行之前创建

区别2
- 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
- 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放

联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境–>全局作用域
- 函数上下文环境–>对应的函数作用域

<script>
        
        var a = 10;
        var b = 20;
        function fn(x){
            var a = 100;
            c = 300;
            console.log("fn()",a,b,c,x);

            function bar(x){
                var a = 1000;
                var d = 400;
                console.log("bar()",a,b,c,d,x);
            }

            bar(100);
            bar(200);
        }

        fn(10);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
2.3.3作用域链

1.理解
- 多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的

2.查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
- 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
- 再次执行2的相同操作,知道全局作用域,如果还找不到就抛出找不到的异常

<script>
        /* 作用域链 */
        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);  //d未定义,会报错
            }
            fn2();
        }
        fn1();
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
2.3.4作用域面试题
<script>
        var x = 10;
        function fn(){
            console.log(x);
        }

        function show(f){
            var x = 20;
            f();
        }
        show(fn); //10
        // fn的作用域没有x,只能从外部的作用域去找

        console.log("=========");

        var fn = function(){
            console.log(fn); 
        };

        fn();

        var obj = {
            fn2:function(){
                console.log(fn2); //会报错,fn2未被定义
                // console.log(this.fn2);  想要输出该作用域的fn2,须在前面加this.
            }
        };
        obj.fn2();
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

2.4闭包

2.4.1理解闭包

1.如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包。

2.闭包到底是什么?
理解一:闭包是嵌套的内部函数
理解二:包含被引用变量(函数)的对象
注:闭包存在于嵌套的内部函数中

3.产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)

<script>
        function fn1(){
            var a = 2;
            var b = "abc";
            function fn2(){     //执行函数定义就会产生闭包(不用调用内部函数)
                console.log(a);
            }
            fn2();
        }
        fn1();
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
2.4.2常见的闭包

1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用

<script>
        
        // 1.将函数作为另一个函数的返回值
        function fn1(){
            var a = 2;
            function fn2(){
                a++;
                console.log(a);
            }
            return fn2;
        }
        
        var f = fn1();
        f();    //3
        f();    //4

        // 2.将函数作为实参传递给另一个函数调用
        function showDelay(msg,time){
            setTimeout(function(){
                alert(msg);
            },time);
        }
        showDelay("学 习",2000);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
2.4.3闭包的作用

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

问题:
1.函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在的,存在于闭包的变量才可能存在

2.在函数外部能直接访问函数内部的局部变量么?
不能,但我们可以通过闭包让外部操作它

<script>
        function fn1(){
            var a = 2;
            function fn2(){
                a++;
                console.log(a);
            }

            function fn3(){
                a--;
                console.log(a);
            }
            return fn3;
        }
        
        var f = fn1();
        f();    //1
        f();    //0
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
2.4.4闭包的生命周期

1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2.死亡:在嵌套的内部函数成为垃圾对象时

<script>
        function fn1(){
            // 此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
            var a = 2;
            function fn2(){
                a++;
                console.log(a);
            }
            return fn2;
        }
        
        var f = fn1();
        f();    //3
        f();    //4
        f = null;  //闭包死亡(包含闭包的函数对象成为垃圾对象)
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
2.4.5闭包的应用_自定义JS模块

闭包的应用:自定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包,执行n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

<script src="myModule2.js"></script>
    <script>
       myModule2.doSomething();
       myModule2.doOtherthing();
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5

myModule2.js

(function(){
    // 私有数据
    var msg = "My Class";

    // 操作数据的函数
    function doSomething(){
        console.log("doSomething()"+msg.toUpperCase());
    }

    function doOtherthing(){
        console.log("doOtherthing()"+msg.toLowerCase());
    }

    // 向外暴露对象(给外部使用的方法)
    window.myModule2 = {
        doSomething:doSomething,
        doOtherthing:doOtherthing
    }
})()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
2.4.6闭包的缺点及解决

1.缺点:
- 函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄漏

2.解决
- 能不用闭包就不用
- 及时释放

<script>
        function fn1(){
            var arr = new Array[100000];
            function fn2(){
                console.log(arr.length);
            }
            return fn2;
        }
        var f = fn1();
        f();
        f = null;  //让内部函数成为垃圾对象-->回收闭包
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
2.4.7内存溢出与内存泄漏

1.内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余内存时,就会抛出内存溢出的错误

2.内存泄漏
- 占用的内存没有及视释放
- 内存泄漏积累的多了就容易导致内存溢出

常见的内存泄漏:
* 意外的全局变量
* 没有及时清理的计时器或回调函数
* 闭包

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

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

        fn();

        // 没有及时清理的计时器或回调函数
        var start = setInterval(function(){  //启动定时器后不清理
            console.log("---");
        },2000);

        // 清理定时器
        clearInterval(start);

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

        var f = fn1();
        f();

        // f = null; 
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
2.4.8闭包面试题
<script>
        /* 以下代码输出的是什么 */

        // 代码片段一
        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
        // 函数嵌套,内部函数调用了外部函数的that,是闭包
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
<script>
        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 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

三、对象高级

3.1对象的创建模式

3.1.1Object构造函数模式

方式一:Object构造函数模式
- 套路:先创建空Object对象,再动态添加属性/方法
- 使用场景:起始时不确定对象内部数据
- 问题:代码量多

<script>
        var p = new Object();
        p.name = "tom";
        p.age = 12;
        p.setName = function(name){
            this.name = name;
        };
 
        // 测试
        p.setName("李白");
        console.log(p.name,p.age);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
3.1.2对象字面量模式

方式二:对象字面量模式
- 套路:使用{}创建对象,同时指定属性和方法
- 适用场景:起始时对象内部数据时确定的
- 问题:如果创建多个对象,有重复代码

<script>
        var p = {
            name:"tom",
            age:16,
            setName:function(name){
                this.name = name;
            }
        };

        // 测试
        console.log(p.name,p.age);
        p.setName("Jack");
        console.log(p.name,p.age);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
3.1.3工厂模式

方式三:
工厂模式
- 套路:通过工厂函数动态创建对象并返回
- 适用场景:需要创建多个对象
- 问题:对象没有一个具体的类型,都是Object类型

<script>
        function createPerson(age,name){    //返回一个对象的函数-->工厂函数  
            var obj = {
                age:age,
                name:name,
                setName:function(){
                    this.name = name;
                }
            };
            return obj;
        }

         var p1 = createPerson(16,"张三");
         var p2 = createPerson(16,"李三");

        console.log(p1);
        console.log(p2);

        function createCat(age,name){
            var obj = {
                age:age,
                name:name,
                setName:function(){
                    this.name = name;
                }
            };
            return obj;
        }

        var c1 = createCat(15,"波斯猫")
        var c2 = createCat(15,"野猫")

        console.log(c1);
        console.log(c2);
        
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
3.1.4自定义构造函数模式

方式四:自定义构造函数模式
套路:自定义构造函数,通过new创建对象
适用场景:需要创建多个类型确定的对象
问题:每个对象都有相同的数据,浪费内存

<script>
        // 定义类型
        function Person(name,age){
            this.name = name;
            this.age = age;
            this.setName = function(){
                this.name = name;
            };
        }

        var p1 = new Person("lisa",16);
        p1.setName("bob");
        console.log(p1.name,p1.age);
        console.log(p1 instanceof Person);

        function Student(name,price){
            this.name = name;
            this.price = price;
            this.setName = function(){
                this.name = name;
            };
        }

        var s = new Student("熊大",1000);
        console.log(s instanceof Student);

        var p2 = new Person("Jack",16);
        console.log(p1,p2)
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
3.1.5构造函数+原型组合模式

方式五:构造函数+原型组合
套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
适用场景:需要创建多个类型确定的对象

<script>
        function Person(name,age){
            this.name = name;
            this.age = age;
        }
        Person.prototype.setName = function(name){
            this.name = name;
        };

        var p1 = new Person("tom",16);
        var p2 = new Person("bob",19);
        console.log(p1,p2);
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.2继承模式

3.2.1原型链继承

原型链继承:方式一
套路:
1.定义父类型的构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.创建父类型的对象赋值给子类型的原型
5.将子类型原型的构造属性设置为子类型
6.给子类型原型添加方法
7.创建子类型的对象:可以调用父类型的方法

关键
- 子类型的原型为父类型的一个实例对象

<script>
        //  父类型
        function Supper(){
            this.supProp = "Supper property";
        }
        Supper.prototype.showSupperProp = function(){
            console.log(this.supProp);
        }

        // 子类型
        function Sub(){
            this.subProp = "Sub property";
        }

        // 子类型的原型为父类型的一个实例对象
        Sub.prototype = new Supper();
        // 让子类型的原型的constructor指向子类型
        Sub.prototype.constructor = Sub;
        Sub.prototype.showSubProp = function(){
            console.log(this.subProp);
        }

        var sub = new Sub();
        sub.showSupperProp();
        sub.showSubProp();

        console.log(sub); //Sub
     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

原型链继承结构图:
在这里插入图片描述

3.2.2借用构造函数继承

方式二:借用构造函数继承(假的)
1.套路:
- 定义父类型的构造函数
- 定义子类型的构造函数
- 在子类型构造函数中调用父类型构造

2.关键:
- 在子类型构造函数中通用call()调用父类型构造函数

<script>
         function Person(name,age){
             this.name = name;
             this.age = age;
         }

         function Student(name,age,price){
            Person.call(this,name,age);     //相当于:this.Person(name,age)
            
            /* 
            this.name = name;
            this.age = age; 
            */

            this.price = price;
         }

         var s = new Student("tom",20,14000);
         console.log(s.name,s.age,s.price);
     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
3.2.3组合继承

方式三:原型链+借用构造函数的组合继承
1.利用原型链实现对父类型对象的方法继承
2.利用call借用父类型构造函数初始化相同属性

<script>
         function Person(name,age){
             this.name = name;
             this.age = age;
         }

         Person.prototype.setName = function(name){
             this.name = name;
         };

         function Student(name,age,price){
            Person.call(this,name,age);     //为了得到属性
            this.price = price;
         }

         Student.prototype = new Person();  //为了能看到父类型的方法
         Student.prototype.constructor = Student;  //修正constructor属性
         Student.prototype.setPrice = function(price  ){
             this.price = price;
         };

         var s = new Student("tom",24,15000);
         s.setName("bob");
         s.setPrice(16000);
         console.log(s.name,s.age,s.price); 
     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

四、线程机制与事件机制

4.1进程与线程

进程:程序的一次执行,它占有一片独有的内存空间,可以通过windows任务管理器查看进程
- 多进程运行:一应用程序可以同时启动多个实例运行

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

多线程:在一个进程内,同时有多个线程运行
优点:
1.有效提升CPU的利用率
缺点:
1.创建多线程开销
2.线程间切换开销
3.死锁与状态同步问题

单线程
优点:顺序编程简单易懂
缺点:效率低

相关知识
- 应用程序必须运行在某个进程的某个路线上
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个进程之间的数据是不能直接共享的
- 线程池:保存多个线程对象的容器,实现线程对象的反复利用

4.2定时器引发的思考

1.定时器真的是定时执行的么?
- 定时器并不能保证真正定时执行
- 一般会延迟一点,也有可能延迟很长时间

2.定时器回调函数是在分线程执行的吗?
- 在主线程执行的,js是单线程的

3.定时器是如何实现的?
事件循环模型

 <button id="btn">启动定时器</button>
     <script>
         document.getElementById("btn").onclick = function(){
             var start = Date.now();
             console.log("启动定时器前");
             setTimeout(function(){
                 console.log("定时器执行了",Date.now()-start);
             },200);
         }

         console.log("定时器启动后");

        //  做一个长时间的工作
        for(var i = 0;i<1000000000;i++){

        }
     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

4.3JS是单线程的

1.如何证明js执行是单线程的?
- setTimeout()的回调函数是在主线程执行的
- 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

2.为什么js要用单线程模式,而不是用多线程模式?
- JavaScript的单线程,与它的用途有关
- 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM
- 这决定了它只能是单线程,否则会带来很复杂的同步问题

3.代码的分类:
- 初始化代码
- 回调代码

4.js引擎执行代码的基本流程
- 先执行初始化代码:包含一些特别的代码 回调函数(异步执行)
- 设置定时器
- 绑定事件监听
- 发送ajax请求
- 后面在某个时刻才会执行回调代码

4.4事件循环模型

1.所有代码分类
- 初始化执行代码(同步代码):包含绑定dom事件监听,设置定时器,发送ajax请求的代码
- 回调执行代码(异步代码):处理回调逻辑

2.js引擎执行代码的基本流程:初始化代码==>回调代码
3.模型的2个重要组成部分:
- 事件(定时器/DOM事件/Ajax)管理模块
- 回调队列

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

<button id="btn">测试</button>

     <script>
         function fn1(){
            console.log("fn1()");
         }
         fn1();

         document.getElementById("btn").onclick = function(){
             console.log("点击了btn");
         };

         setTimeout(function(){
             console.log("定时器执行了");
         },2000);

         function fn2(){
             console.log("fn2()");
         }

         fn2();
     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

4.5Web Workers

1.h5规范提供了js分线程的实现,取名为:Web Workers
2.相关API
- Worker:构造函数,加载分线程执行的js文件
- Worker.prototype.onmessage: 用于接收另一个线程的回调函数
- Worker.prototype.postMessage: 向另一个线程发送消息

3.不足:
- worker内代码不能操作DOM
- 不能跨域加载js
- 不是每个浏览器都支持这个新特性

 <input type="text" placeholder="数值" id="number">
     <button id="btn">计算</button>
     <script>
         var input = document.getElementById("number");
         document.getElementById("btn").onclick = function(){
             var number = input.value;

            //  创建一个Worker对象
            var worker = new Worker("worker.js");
            // 绑定接收消息的监听
            worker.onmessage = function(event){
                console.log("主线程接收分线程返回");
                alert(event.data);
            };

            // 向分线程发送信息
            worker.postMessage(number);
            console.log("主线程向分线程发送数据"+number);

         };
     </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

worker.js

function fibonacci(n){
    return n<=2? 1:fibonacci(n-1) + fibonacci(n-2); //递归调用
}

console.log(this);
var onmessage = function(event){
    var number = event.data;
    console.log("分线程接收到主线程发送的数据:"+number);

    // 计算
    var result = fibonacci(number);
    postMessage(result);
    console.log("分线程向主线程返回数据:"+result);

    // alert(result) alert是window的方法,在分线程不能调用
    // 分线程中的全局对象不再是window,所以在分线程中不可能跟新界面
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/108852
推荐阅读
相关标签
  

闽ICP备14008679号