JavaScript高级

基础总结深入

数据类型

1.分类

基本(值)类型

​ String:任意字符

​ Number:任意的数字

​ Boolean:true、false

​ undefind:undefind

​ null:null

对象(引用)类型

​ Object:任意对象

​ Function:一种特别的对象(可以执行)

​ Array:一种特别的对象(数值下标属性,内部数据是有序的)

2.判断

typeof:

​ 可以判断:undefined / 数值 / 字符串 / 布尔值 / function

​ 不能判断:null 与 object / object和array

​ **instanceof:**判断对象的具体类型

== / === :

​ 一个会做数据转换,一个不会

​ 可以判断undefined / null

<!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>Document</title>
</head>
<body>
</body>
<script> 
    //分类
    var obj = {
        name: 'Top',
        age: 12
    }; //对象用来存数据,一般对象内部数据都是无序的
    function fun(){ //函数用来存代码
        var a = 3;
    }
    var arr = [3,'abc']; 
    console.log(arr[1]);

    //判断
    //1.基本
    //typeof返回数据类型的字符串表达式
    var a;
    console.log(a); //undefined
    console.log(a, typeof a); //undefined 'undefined'
    console.log(undefined == 'undefined'); //false
    console.log(undefined === 'undefined'); //false
    
    //判断一个a是否为undefined
    console.log(typeof a===undefined);  //false
    console.log(typeof a==='undefined'); //true
    console.log(a===undefined); //true

    a = 3;
    console.log(typeof a); //number
    console.log(typeof a==='number'); //true

    a = "123";
    console.log(typeof a==='string'); //true

    a = true;
    console.log(typeof a==="boolean"); //true

    a = null;
    console.log(typeof a); //object
    console.log(typeof a==="null"); //false
    console.log(typeof a==="object"); //true
    console.log(a===null); //true 

    console.log("----------------");
    //2.对象
    var b1 = {
        b2: [1,'abc',console.log],
        b3: function(){
            console.log("b3");
            return function(){
                return "套中套";
            }
        }
    }
    //判断b1是不是Object类型的实例
    console.log(b1 instanceof Object); //true
    console.log(b1 instanceof Array);  //false
    
    // console.log(b2 instanceof Array);  //报错
    console.log(b1.b2 instanceof Array);  //true
    console.log(b1.b2 instanceof Object);  //true
    console.log(typeof b1.b2); //object 不能用typeof来区别对象和数组


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

    console.log(typeof b1.b2[2]); //function
    b1.b2[2]("hello"); //hello

   console.log(b1.b3()());; //b3,套中套


</script>
</html>

相关问题

1.undefined与null的区别?

​ undefined代表定义未赋值

​ null定义并赋值了,只是值为null

2.什么时候给变量赋值null呢?

​ 初始赋值,表明将要赋值为对象

​ 结束前赋值,让obj指向的对象成为垃圾对象(被垃圾回收机制回收)

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>Document</title>
</head>
<body>
</body>
<script>
    //实例:实例对象
    //类型:类型对象
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    var p = new Person();

    //1.undefined与null的区别?
    var a;
    console.log(a); //undefined
    a=null;
    console.log(a); //null

    //2.什么时候给变量赋值null呢?
    //计划创建一个对象,但是数据还没产生,所以就赋值一个null,来表明是一个对象类型
    var obj = null; //初始赋值为null,表明将要赋值为对象
    obj = ['abc',12]; //确定对象就赋值
    obj = null; //最后,释放这个对象(数组)所占用的内存

    //3.严格区别变量类型与数据类型? 
    var c = {} 

</script>
</html>

数据_变量__内存

1.什么是数据?

​ 存储在内存中代表特定信息的 ‘东东’,本质上是0101…

​ 数据的特点:可传递,可运算

​ 一切皆数据

​ 内存中所有操作的目标:数据

​ 算数运算

​ 逻辑运算

​ 赋值

2.什么是内存?

​ 内存条通电后产生的可存储数据的空间(临时的)

​ 内存产生和死亡:内存条(电路板)>通电>产生内存空间==>存储数据>断电==>内存空间和数据都消失

​ 一块小内存的2个数据

​ 内部存储的数据

​ 地址值

​ 内存的分类

​ 栈:局部变量 / 全局变量

​ 堆:对象

3.什么是变量?

​ 可变化的量,有变量名和变量值组成

​ 每个变量都对应的一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据

4.内存,数据,变量三者之间的关系?

​ 内存是用来存储数据的空间(临时的)

​ 变量是内存的标识

<script>
    var age = 18;
	console.log(age); //通过变量名去找到内存中所在的位置,并读取里面的数据

	var a = 3;
	var b= a + 2;
</script>

变量在内存中到底保存什么

问题:var a = xxx,a内存中到底保存什么?

xxx是基本数据,a保存的就是这个数据

xxx是对象,保存的是对象的地址值

xxx是一个变量,保存的是xxx的内存内容(可能是基本数据,也可能是地址值)
<script>
    var a = 3;
    //xxx是对象
    var a = {} //a保存的是地址值
    var f = function(){
        console.log("hello");
    };
    //  f也是保存的地址值
    f(); //hello
    console.log(f); 

    //xxx是一个变量
    var b = 'abc'
    a = b;
    console.log(a); //abc
    b = {};
    a = b;
    console.log(a); //{}

</script>

关于引用变量赋值问题

2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据

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

<!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>Document</title>
</head>
<body>
<script>
    //2个引用变量指向同一个对象,通过一个变量修改对象内部数据,其他变量看到的是修改之后的数据
    var obj1 = {name:"Tom"}
    var obj2 = obj1;
    obj1.name = "Mi";
    console.log(obj2.name); //Mi
    fn(obj2);
    console.log(obj1.name); //fn
    function fn(obj){
        obj.name = "fn";
    }

    //2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个变量
    var a = {age:12}
    var b = a;
    a = {name:"Tom",age:13}
    console.log(b); //{age: 12}
    console.log(a); //{name: 'Tom', age: 13}

    function fn2(obj){
        obj = {age:15} //不会改变a的值
        // obj.age = 15; //这样才会改变a的值

    }
    //这个函数不会改变a的值
    //因为33行的a和obj指向同一个地址,然后到34行的时候,obj指向了另一个地址,而a还是指向原来的地址
    fn2(a); 
    console.log(a.age); //13


</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>Document</title>
</head>
<body>
    <script>
        function fun(obj){
            console.log(obj.name);
        }
        var obj = {name:'Tom'};
        fun(obj);
    </script>
</body>
</html>

JS引擎如何管理内存

1.内存生命周期

​ 分配小内存空间,得到它的使用权

​ 存储数据,可以反复进行操作

​ 释放小内存空间

2.释放内存

​ 局部变量:函数执行完自动释放

​ 对象:成为垃圾对象==>垃圾回收器回收

<!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>Document</title>
</head>
<body>
 <script>
    //这里占用了3个内存空间,a、obj和对象"{}",其中,对象占的内存空间最大
    var a = 3;
    var obj = {};

    //此时还有两个空间,只有对象"{}",被释放了,obj还在
    obj = null;

    function fun(){
        var b = {};
    }
    //29行的b只有到函数执行的时候(32行)才会产生空间,并且在函数结束的时候b会"自动释放"
    fun(); //注意:b是自动释放的,但是,b所指向的空间是在后面某一时刻,被垃圾回收器回收的


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

对象

1.什么是对象?

​ 多个数据的封装体

​ 用来保存多个数据的容器

​ 一个对象代表现实中的一个事务

2.为什么要用对象?

​ 统一管理多个数据

3.对象的组成

​ 属性:属性名(字符串)和属性值(任意类型)组成

​ 方法:一种特别的属性(属性值为函数)

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>Document</title>
</head>
<body>
 <script>
    var obj = {
        name: 'Tom',
        age: 12,
        setName: function(name){
            this.name = name
        },
        setAge: function(age){
            this.age = age;
        }
    }
    obj.setName();
    obj['setAge'](13);
    console.log(obj.name, obj['age']); //undefined,13
 </script>
</body>
</html>

什么时候用[‘属性名’]

​ 1.属性名包含特殊字符:- 空格

​ 2.属性名不确定

<!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>Document</title>
</head>
<body>
<script>
    var p = {};
    //给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;
    //此时的属性名就不是myAge,而是propName
//    p.propName = value; 
//    console.log(p.myAge); //undefined
    p[propName] = value; 
    console.log(p['myAge']); //18
</script>
</body>
</html>

函数

1.什么是函数?

​ 实现特定功能的n条语句的封装体

​ 只有函数是可以执行的,其他类型的数据不能执行

2.为什么要用函数?

​ 提高代码复用

​ 便于阅读交流

3.如何定义函数?

​ 函数声明

​ 表达式

4.如何调用(执行)函数?

​ test():直接调用

​ obj.test():通过对象调用

​ new test():通过new 调用

​ test.call/apply(obj):相当于obj.test()

​ 临时让test成为obj的方法进行调用(obj里面是没有test方法的)

<!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>Document</title>
</head>
<body>
 <script>
    /*
    编写程序实现以下功能需求:
        1. 根据年龄输出对应的信息
        2. 如果小于18,输出:未成年,再等等
        3. 如果大于60,输出:算了吧!
        4. 其他,输出:刚好!
    */
   function showInfo(age){
        if(age<18){
            console.log("未成年,再等等");
        }else if(age > 60){
            console.log("算了吧!");
        }else{
            console.log("刚好!");
        }
   }
   showInfo(18); //刚好!
   showInfo(16); //未成年,再等等
   showInfo(61); //算了吧!

   //3. 如何定义函数?
   function fn1(){ //函数声明
    console.log('fun1()');
   }

   var fn2 = function(){ //表达式
    console.log('fun2()');
   }

   //4. 如何调用(执行)函数?
   var obj = {};
   function test(){
    this.xxx = 'zhangzh'
   }
//   obj.test(); //报错,不能直接调用,因为obj里面没有test方法
   test.call(obj); //相当于obj.test()
   console.log(obj.xxx); //zhangzh
 </script>
</body>
</html>

回调函数

1.什么函数才是回调函数?

​ 1). 你定义的

​ 2). 你没有调用

​ 3). 但最终它执行 了(在某个时刻或某个条件下)

2.常见的回调函数?

​ dom事件回调函数–>this为:发生事件的回调函数(此处为btn按钮)

​ 定时器回调函数 -->this为:window

​ ajax请求回调函数

​ 生命周期回调函数

<!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>Document</title>
</head>
<body>
    <button id="btn">点击测试事件</button>
 <script>
    document.getElementById("btn").onclick = function(){ //dom事件回调函数
        alert(this.innerHTML);
    };

    //定时器
        //超时定时器
        //循环定时器
    setTimeout(function(){ //定时器回调函数
        alert("到点了");
    },2000);
 </script>
</body>
</html>

IIFE

1.理解

​ 全称:Immediately - Invoked Function Expression

​ 立即 调用 函数 表达式

2.作用

​ 隐藏实现

​ 不会污染外部(全局)命名空间

​ 用它来编码JS模块

<!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_IIFE</title>
</head>
<body>
 <script>
    (function (){ //匿名函数自调用 === IIFE
         //这个a如果写道外面就会产生一个全局变量,
         //但是用匿名函数的方法就可以将作用域限制到这个函数里面(局部变量)
        var a = 3;
        console.log(a + 3);
    })(); 

    (function(){
        var a = 1;
        function test(){
            console.log(++a);
        }
        window.$ = function(){ //向外暴露一个全局函数
            return {
                test:test
            }
        }
    })()
    //windwo可以省略
    window.$().test(); //2
 </script>
</body>
</html>

函数中的this

1.this是什么?

​ 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是Window

​ 所有函数内部都有一个变量this

​ 它的值是调用函数的当前对象

2.如何确定this的值?

​ test():window

​ p.this():p

​ new test():新创建的对象

​ p.call(obj):obj

<!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>Document</title>
</head>
<body>
<!-- 
    1.this是什么?
        任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是Window
        所有函数内部都有一个变量this
        它的值是调用函数的当前对象
    2.如何确定this的值?
    test():window
    p.this():p
    new test():新创建的对象
    p.call(obj):obj
 -->
 <script>
    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是:windwo

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

    console.log(p.getColor); //this是:p

    var obj = {};
    p.setColor.call(obj,"balck"); //this是:obj

    var test = p.setColor; //得到一个函数
    test(); //this是:window(直接调用)

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

        fun2(); //this是:window
    }
    fun1();
 </script>
</body>
</html>

函数高级

原型和原型链

原型

1.函数的prototype属性(图)

​ 每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)

​ 原型对象中有一个属性constructor,它指向函数对象

2.给原型对象添加属性(一般都是方法)

​ 作用:函数的所有实例对象自动拥有原型中的属性(方法)

在这里插入图片描述

<!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>Document</title>
</head>
<body>
 <script>

    // 每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
    console.log(Date.prototype,typeof Date.prototype); //Object 'object'
    console.log(Date.prototype.prototype); //undefined

    function Fun(){

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

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

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

显式原型与隐式原型

1.每个函数function都有一个prototype,即显式原型

2.每个实例对象都有一个__ proto __,可称为隐式原型

3.对象的隐式原型的值为其对应构造函数的显示原型的值

4.内存结构图

5.总结:

实例对象的隐式原型等于构造函数的显示原型

​ 函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象

​ 对象的__ proto __属性:创建对象时自动添加的,默认值为构造函数的prototype属性值

​ 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)

内存结构图

在这里插入图片描述

<!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>Document</title>
</head>
<body>
 <script>
    function Fun(){ //内部语句:this.prototype = {}

    }
    var fun = new Fun(); //内部代码:this.__proto__ = Fun.prototype
    //1.每个函数function都有一个prototype,即显式原型
    console.log(Fun.prototype); //object

    //2.每个实例对象都有一个__proto__,可称为隐式原型
    console.log(fun.__proto__); //object

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

    //给原型添加方法
    Fun.prototype.test = function(){
        console.log('test()');
    }
    //因为__proto__和prototype指向同一个对象,所以修改prototype的话__proto__也能看得到
    fun.__proto__.test(); //test()
    
    //fun.test没有找到的话就会去__proto__里面一层一层找
    fun.test(); //test()
 </script>
</body>
</html>

原型链

1.原型链(图解)

​ 访问一个对象的属性时:

  • ​ 现在自身属性中找,找到返回

    • ​ 如果没有,再沿着__ proto __这条链向上查找,找到返回
  • ​ 如果最终没有找到,返回undefined

    ​ 别名:隐式原型链

    ​ 作用:查找对象的属性(方法)

2.构造函数/原型/实体对象的关系(图解)

3.构造函数/原型/实体对象的关系2(图解)

原型链(图解)

在这里插入图片描述

构造函数/原型/实体对象的关系(图解)

在这里插入图片描述

构造函数/原型/实体对象的关系2(图解)

在这里插入图片描述

创建定义函数相当于var Foo = new Function()

所以Foo是Function的实例对象

所以Foo也有隐式原型__ proto __

所有函数的隐式原型都等于Function的显式原型!!!

<!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>Document</title>
</head>
<body>
<script>
    function Fun(){
        this.test1 = function(){
            console.log('test1()');
        }
    }
    Fun.prototype.test2 = function(){
        console.log('test2()');
    }
    var fun = new Fun(); 
    fun.test1(); //test1()
    fun.test2(); //test2()
    console.log(fun.toString()); //[object Object] 

    //相当于undefined(),undefined不是一个函数
    // fun.test3(); //Uncaught TypeError: fun.test3 is not a function
    console.log(fun.test3); //undefined

    //实例对象隐式原型等于构造函数显式原型,系统默认会有一个object的对象
    console.log(typeof Fun.prototype.__proto__); //object
    
    //默认有一个Object类型的对象,并且是全局的
    console.log(Object); //ƒ Object() { [native code] }
    console.log(Object.prototype.toString); //ƒ toString() { [native code] }


    //构造函数/原型/实体对象的关系2(图解)
    //这里相当于var Foo = new Function()
    function test(){
    }
    console.log(test.__proto__ === Function.prototype); //true
    //所有函数的隐式原型都等于Function的显示原型!!!
    console.log(Function.prototype === Object.__proto__); //true
    console.log(test.__proto__ === Object.__proto__); //true

//    ************** 
    //Function的隐式原型等于显式原型
    //实例对象的隐式原型等于构造函数的显示原型
    //相当于 Function = new Function(),用自己来创建的
    console.log(Function.__proto__ === Function.prototype); //true

</script>
</body>
</html>
补充

1.函数的显示原型指向的对象默认是空的Object实例对象(但Object不满足)
2.所有函数都是Function的实例(包括Function本身)
3.Object的原型对象,是原型链的尽头

<!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>Document</title>
</head>
<body>
    <script>
        function Fun(){};

        //测试
        console.log(Object.prototype.__proto__); //null
        console.log(Fun.__proto__.__proto__.__proto__); //null
        console.log(Fun instanceof Object);   //true
        console.log(Fun instanceof Function); //true

        //1.函数的显示原型指向的对象默认是空的Object实例对象
        console.log(Fun.prototype instanceof Object); //true
        console.log(Function.prototype instanceof Object); //true
        //但Object不满足
        console.log(Object.prototype instanceof Object); //false  

        // 2.Function是它自身的实例
        console.log(Fun instanceof Function); //true
        console.log(Function instanceof Function); //true
        console.log(Function.__proto__ === Function.prototype); //true
        console.log(Fun.__proto__ === Function.__proto__); //true

        // Object的原型对象,是原型链的尽头
        console.log(Object.prototype.__proto__); //null
    </script>
</body>
</html>
属性问题

1.读取对象的属性时:会自动到原型链中查找
2.设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
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>04_原型链_属性问题</title>
</head>
<body>
 <script>
    function Fun(){
    }
    Fun.prototype.a = 'xxx';
    var fun1 = new Fun();
    //1.读取对象的属性时:会自动到原型链中查找
    console.log(fun1.a); //xxx

    //2.设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
    var fun2 = new Fun();
    fun2.a = 'yyy';
    console.log(fun1.a); //xxx
    console.log(fun2.a); //yyy
    console.log(fun2.__proto__.a); //xxx
    console.log(fun2.a === fun1.__proto__.a); //false
    console.log(fun2.a === fun2.__proto__.a); //false
    console.log(fun1.a === fun2.__proto__.a);  //true

    //3.方法一般定义在原型中,属性一般通过构造函数定义在对象本身
    function Person(name, age){
        this.name = name;
        this.age = age;
    }
    var p1 = new Person('Tom',18); //此时,name和age都在p1自身里面
    Person.prototype.setName = function(name){
        this.name = name;
    }
    p1.setName('MoMo');
    console.log(p1.name); //MoMo
    console.log(p1);
 </script>
</body>
</html>
探索instanceof

1.instanceof是如何判断的?
表达式:A instanceof B
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
2.Function是通过new自己产生的实例

案例一

function Foo() {  }
var f1 = new Foo();
console.log(f1 instanceof Foo); //true
console.log(f1 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

function Foo(){ }
console.log(Object instanceof Foo); //false

在这里插入图片描述

面试题

测试题一


var A= function(){

}
A.prototype.n = 1; 

var b = new A(); //此时的prototype指向0x234

//此时prototype指向0x345
A.prototype = {
    n:2,
    m:3
}

var c = new A() //此时prototype指向0x345
console.log(b.n, b.m, c.n , c.m); //1 ,undefined ,2 ,3

//测试
console.log(A.prototype.__proto__); //相当于Object
console.log(c.__proto__ === b.__proto__); //false
console.log(c.__proto__.__proto__ === b.__proto__.__proto__); //true
console.log(c.__proto__.__proto__.__proto__);  //null
console.log(A.prototype.__proto__ === c.__proto__.__proto__); //true
console.log(A.prototype.__proto__ === b.__proto__.__proto__); //true

在这里插入图片描述

面试题二

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是在Function,实例对象去不了那里,只有函数可以去到
//此时的F相当于实例对象(new Function() ) 
F.a(); //b()
F.b(); //b()
console.log(f.__proto__ === F.prototype); //true
console.log(f.__proto__ === Function.prototype); //false

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

变量提升与函数提升

1.变量声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到
值:undefined
2.函数声明提升
通过function声明的函数,在之前就可以直接调用
值:函数定义(对象)
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>变量提升与函数提升</title>
</head>
<body>
 <script>
    //面试题
    var a = 3;
    function fn(){
        console.log(a); //undefined
        var a = 4; 
        /*
        相当于
            var a;
            console.log(a); 
            a = 4;
        自身里面已经有了a,所以就会拿自身的值,而打印的时候a是没有值的
        */
    }
    fn();

    // 通过var定义(声明)的变量,在定义语句之前就可以访问到
    console.log(b); //undefined 变量提升
    var b = 2;

//     console.log(c); //报错:colse is not defined

    // 通过function声明的函数,在之前就可以直接调用
    fn2();   //可以运行  函数提升
    fn3();   //不能运行,此时遵循的是变量提升

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

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

执行上下文

1.代码分类(位置)

​ 全局代码

​ 函数(局部)代码

2.全局执行上下文

​ 在执行全局代码前将window确定为全局执行上下文

​ 对全局数据进行预处理

​ var定义的全局变量 ==> undefined,添加为window的属性

​ function声明的全局函数 ==> 赋值(fun),添加为window的方法

​ this ==> 赋值(window)

​ 开始执行全局代码

3.函数执行上下文

在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象I(虚拟的,存在于栈中)

​ 对局部数据进行预处理

​ 形参变量 ==> 赋值(实参) ==> 添加为执行上下文的属性

​ arguments(伪数组) ==> 赋值(实参列表),添加为执行上下文的属性

<!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>Document</title>
</head>
<body>
 <script>
    //全局执行上下文
    console.log(a1,window.a1); //undefined undefined 
    a2(); //a2()
    console.log(this);  //window

    var a1 = 3;
    //调试的时候会从34直接跳到41,因为a2已经执行过了
    function a2(){
        console.log('a2()'); 
    }

    
    fn(2,3); //只有调用函数之前,才会创建空间
    //函数执行上下文
    function fn(a1){
        console.log(a1); //2
        console.log(a2); //undefined
        a3(); //a3()
        console.log(this); //window
        console.log(arguments); //伪数组(2,3)

        var a2 = 3;
        function a3(){
            console.log('a3()');
        }
    }
 </script>
</body>
</html>

执行上下文栈

1.在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象

2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)

3.在函数执行上下文创建后,将其添加到栈中(压栈)

4.在当前函数执行完后,将栈顶的对象移除(出栈)

5.当所有的代码执行完后,栈中只剩下window

流程分析

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); //10, 5, 15 = 30
}
bar(10);

在这里插入图片描述

在这里插入图片描述

1.依次输出什么?

​ global begin:undefined

​ foo() begin:1

​ foo() begin:2

​ foo() begin:3

​ foo() end:3

​ foo() end:2

​ foo() end:1

​ global end:1

2.整个过程中产生了几个执行上下文?

​ 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>03_执行上下文栈2</title>
</head>
<body>
 <script>
    console.log('global begin: ' + i); //undefined
    var i = 1;
    foo(1); 
    function foo(i){
        if(i == 4){
            return;
        }
        console.log('foo() begin:' + i);
        foo(i+1);
        console.log('foo() end:' + i);
    }
    console.log('global end:' + 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>04_面试题</title>
</head>
<body>
    <script>
        //测试一:函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖
        //当函数声明与变量名相同时,在变量赋值前,函数声明依旧是函数声明,不会被覆盖
        function a(){} //函数提升
        var a; //变量提升
        console.log(typeof a); //function

        //当变量赋值后,函数声明被同变量覆盖
        function b(){}
        var b = 1;
        console.log(typeof b); //number

        //测试题二:
        if(!(c in window)){ //判断window里面有没有属性c
            var c = 1; 
        }
        console.log(c); //undefined

        //测试题三:
        var d = 1;
        function d(d){
            console.log(d);
        }
        //当变量赋值后,函数声明被同变量覆盖
        d(2); //报错,d不是一个函数
    </script>
</body>
</html>

作用域和作用域链

作用域

1.理解

​ 就是一块"底盘",一个代码段所在的区域

​ 它就是静态的(相对于上下文对象),在编写代码时就确定了

2.分类

​ 全局作用域

​ 函数作用域

​ 没有块作用域(ES6有了)

3.作用

​ 隔离变量,不同作用域下同名的变量不会有冲突

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

在这里插入图片描述

作用域与执行上下文

1.区别1

​ 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时

​ 全局执行上下文环境是在全局作用域确定之后,JS代马上执行之前创建

​ 函数执行上下文环境实在调用函数时,函数体代码执行之前创建

2.区别2

​ 作用域是静态的,只要函数定义好了就一直存在,且不会在变化

​ 上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会被自动释放

3.联系

​ 上下文环境(对象)是从属于所在的作用域

​ 全局上下文环境 ==> 全局作用域

​ 函数上下文环境 ==> 对应的函数使用域

在这里插入图片描述

作用域链

1.理解

​ 多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)

​ 查找变量时就是沿着作用域链来查找的

2.查找一个变量的查找规则

​ 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2

​ 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3

​ 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常

<!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>
    var a = 1;
    function fn1() {
        var b = 2;
        function fn2() {
            var c = 3;
            console.log(c); //3
            console.log(b); //2
            console.log(a); //1
            console.log(d); //报错:d is not defined
        }
        fn2();
    }
    fn1();
</script>
</body>
</html>

面试题

面试题一

var  x = 10;
function fn() {
    //这里找不到,则去全局里面找,并不会去show里面
    console.log(x); //10
}

function show(f) {
    var x = 20;
    f();
}
show(fn);

面试题二

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

var obj = {
    fn2: function() {
        //这个函数没有fn2,则直接会去全局里面找,找不到则报错
        console.log(fn2); 报错:fn2 is not defined

        //假如想找obj里面的fn2的写法
        // console.log(this.fn2); //输出整个函数
    }
}
obj.fn2() 

闭包

循环遍历加监听

<!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>
    <!-- 需求:点击某个按钮,提示"点击的是第n个按钮" -->
    <script>
        var btns = document.getElementsByTagName("button");
        //遍历加监听
        //方式一:
        //length写在定义里面只会计算一次,不然每一次都会计算,也可以写在外面
        // for(var i=0,length=btns.length;i<length;i++){
        //     //添加一个属性,用来保存下标
        //     btns[i].index = i;
        //     btns[i].onclick = function(){
        //         //这样写是错误的,因为当函数执行的时候,循环已经执行完毕了,所以无论点哪个都是同一个结果
        //         // console.log('第' + (i+1) + '个'); 

        //         console.log('第' + (this.index+1) + '个'); //正常显示
        //     }
        // }
        
        //方式二:利用闭包
        for(var i=0,length=btns.length;i<length;i++){
            //立即执行函数
            (function(i){ //这里的i跟上面for里面的不一样
                btns[i].onclick = function(){
                    console.log('第' + (i+1) + '个'); //正常显示
                }
            })(i);
        }
    </script>
</body>
</html>

闭包理解

1.如何产生闭包?

​ 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包

2.闭包到底是什么?

​ 使用chrome调试查看

​ 理解一:闭包是嵌套的内部函数(绝大部分人)

​ 理解二:包含被引用的变量(函数)的对象(极少部分人)

​ 注意:闭包存在于嵌套的内部函数中

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>02_闭包理解</title>
</head>
<body>
 <script>
    function fn1() {
        var a = 2;
        var b = 'abc';
        function fn2() { 
            //[[Scopes]]: Scopes[2]
            //  0: Closure (fn1)
            //      a: undefined
            console.log(a); 
        }
        fn2();
    }
    fn1();
 </script>
</body>
</html>

常见的闭包

1.将函数作为另一个函数的返回值

2.将函数作为实参传递给另一个函数调用

<!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>
        // 1. 将函数作为另一个函数的返回值
        function fn1() {
            var a = 2

            function fn2() {
                a++
                console.log(a)
            }

            return fn2
        }
        var f = fn1()
        f() // 3
        f() // 4

        // 2. 将函数作为实参传递给另一个函数调用
        function showMsgDelay(msg, time) {
            setTimeout(function () {
                console.log(msg)
            }, time)
        }
        showMsgDelay('hello', 1000)
    </script>
</body>

</html>

闭包的作用

1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)

2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:

1.函数执行完后,函数内部声明的局部变量是否还存在?

​ 一般是不存在,存在于闭包中的变量才可能存在

2.在函数外部能直接访问函数内部的局部变量吗?

​ 不能,但是我们可以通过闭包的形式让外部去操作它

<!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>
    // 1.将函数作为另一个函数的返回值
    function fn1() {
        var a = 2; //当程序执行到这里闭包就已经产生了,因为有函数提升,函数早已经被执行完了
        function fn2() {
            a++;
            console.log('a:' + a);
        }
        function fn3() { 
            a--;
            console.log(a);
        } //当代码执行到这里的时候,fn3也已经被释放了,但是fn3被释放并不代表函数对象成为垃圾对象,因为被全局的f3所引用
        fn2();
        return fn3;
    }
    //此时return的是fn2
    //为什么a还在?
    //  因为局部变量fn2虽然被释放了,但是fn2所保存的函数对象的地址值给了全局变量f
    // var f = fn1(); //闭包产生了几个,就看外部函数调用了几次,跟内部函数执行多少次无关
    // f(); //a:3
    // f(); //a:4

    //此时return的是fn3
    //现在fn2产生的闭包已经消失了,因为没有变量去引用它
    //1.为什么会没释放,是因为函数创建时会生成GO也就是全局对象windows,然后还会产生自己的AO,也就是自己函数的局部变量
    // 2.结合老师的代码,fn1创建时会生成GO也就是windows对象,其次会产生自己的AO也就是a=2
    // 3.fn3被调用了,所以会生成全局的GO,然后会拿到父函数的AO也就是fn1的局部变量a,再生成自己的AO
    var f3 = fn1(); //用的是fn3的闭包
    f3(); //1
    f3(); //0
 </script>
</body>
</html>

闭包的生命周期

1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)

2.死亡:在嵌套的内部函数成为垃圾对象时

<!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>
    function fn1(){
        //此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
        var a = 2;
        function fn2(){
            a++;
            console.log(a);
        }
        //如果函数是这样写的话,那么闭包就不是var a时产生,而是在var fn2时产生
        //函数定义和函数执行是两码事
        var fn2 = function(){
            a++;
            console.log(a);
        }
        return fn2;
    }
    var f = fn1();
    f();
    f();

    console.log(typeof f); //function
    f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象)

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

闭包应用_自定义JS模块

闭包的应用:定义JS模块

​ 具有特定功能的js文件

​ 将所有的数据和功能都封装在一个函数内部(私有的)

​ 只向外部暴露一个包含n个方法的对象或函数

​ 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

方法一

​ 自定义JS模块1.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>自定义JS模块1</title>
    <script src="myModule1.js"></script>
</head>
<body>

 <script>
    var m = myModule();
    m.doSomething(); //doSomething:MY ZHANG
    m.doOtherthing(); //doOtherthing:my zhang
 </script>
</body>
</html>

​ myModule1.js

function myModule() {
    //私有属性
    var msg = 'My zhang';

    //操作数据的函数
    function doSomething() {
        console.log('doSomething:' + msg.toUpperCase()); //转大写
    }
    function doOtherthing() {
        console.log('doOtherthing:' + msg.toLowerCase()); //转小写
    }

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

方法二

​ 自定义JS模块1.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>自定义JS模块2</title>
    <script src="myModule2.js"></script>
</head>
<body>
    <script>
        myModule2.doSomething(); //doSomething:MY ZHANG
        myModule2.doOtherthing(); //doOtherthing:my zhang
    </script>
</body>
</html>

​ myModule2.js

(function(){
    //私有属性
    var msg = 'My zhang';

    //操作数据的函数
    function doSomething() {
        console.log('doSomething:' + msg.toUpperCase()); //转大写
    }
    function doOtherthing() {
        console.log('doOtherthing:' + msg.toLowerCase()); //转小写
    }

    window.myModule2 = {
        doSomething: doSomething,
        doOtherthing: doOtherthing
    };
})()

面试题_重难点 *

面试题一
<!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>08_面试题1</title>
</head>
<body>
    <script>
        //代码片段一:没有产生闭包
        var name = "The Window"
        var object = {
            name: "My Object",
            getNameFunc: function(){
                return function(){
                    return this.name;
                }
            }
        };
        //因为object.getNameFunc()调用完返回一个函数,然后用直接调用的方式来调用
        //相当于前面省略了window,所以this是window
        alert(object.getNameFunc()()); ///My Window

        //代码片段二:产生了闭包
        var name2 = "The Window";
        var object2 = {
            name2: "My Object",
            getNameFunc: function() {
                var that = this;
                return function(){
                    return that.name2;
                }
            }
        }
        //这里that保存的是object2的this,所以会打印My Object
        alert(object2.getNameFunc()()); //My 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>08_面试题2</title>
</head>
<body>
    <script>
        function fun(n,o) {
            console.log(o);
            return {
                fun:function(m){
                    return fun(m,n); //已经产生了闭包,n
                }
            };
        }
        //这里a存的只是第一次调用完成后的值(n),所以一直都是0
        //如果这里用a  = a.fun(1),那么结果就跟b一样
        var a = fun(0); //此时产生了一个闭包,值为0
        a.fun(1); //此时产生了一个新的闭包值为1(m),但是马上就消失了,因为return的时候并没有变量去接收它
        a.fun(2);
        a.fun(3); //undefined,0,0,0

        //这里就相当于a = fun(0); a=a.fun(1); a=a.fun(2)
        //  产生的新闭包有变量去接收 
        var b = fun(0).fun(1).fun(2).fun(3); //undefined,0,1,2

        //fun(0).fun(1)执行完之后,闭包的值(n = 1)
        // 而c.fun(2)产生的闭包并没有变量去接收它
        // 所以每次打印o都是1
        var c = fun(0).fun(1); c.fun(2); c.fun(3); //undefined,0,1,1
    </script>
</body>
</html>

详解

我们来看最开始提到的代码,先简化一下

function fun(n,o){
    return {
    }
}

我们先看这段代码,fun 调用后会怎么样?
很明显会返回一个空对象,记住,fun调用后会返回对象,这点很重要。

 function fun(n,o){
    console.log(o);
    return {
        fun:function(m){
            return fun(m,n);
        }
    };
 }

 var a = fun(0);

这里提一句,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。
console.log(o); 输出undefined
var a = fun(0); 那a是值是什么,是fun(0),返回的那个对象

{
    fun:function(m){
        return fun(m,0);
    }
}

这个对象,有一个fun的方法,方法返回的结果就是最外面 fun 调用的结果。

在这里插入图片描述

var a=fun(0),传入一个参数0,那就是说,函数fun中参数 n 的值是0了,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0,这段话是本文的重点, 明白这段,那问题就容易解决了。

说到这里,这道题基本上可以解决了,希望大家能听明白我上面说的话,下面的就简单了。我们一步一步看。

现在我们知道 a 是

{
    fun:function(m){
        return fun(m,0);
    }
}

这样的一个对象
a.fun(1); 会怎么样?看代码

{
    fun:function(1){
        return fun(1,0);
    }
}

a.fun(1); 返回的结果,就是 fun(1,0),返回的结果

 function fun(n,o){ //n的值为1,o的值为0
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n);//n的值为1
            }
        };
}
fun(1,0);  //输出0,并返回一个对象,这个对象有一个fun的方法,这个方法调用后,会返回外层fun函数调用的结果,并且外层函数的第二个参数是 n 的值,也就是1  

a.fun(2); 返回的结果,就是 fun(2,0),返回的结果

 function fun(n,o){ //n的值为2,o的值为0
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n); //n的值为2
            }
        };
}
fun(2,0); //输出0,并返回一个对象,这个对象有一个fun的方法,这个方法调用后,会返回外层fun函数调用的结果,并且外层函数的第二个参数是 n 的值,也就是2  

a.fun(3); 就不说了,一样的。


var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);

我们继续说b,b和a的不同在于,var a = fun(0); 之后一直用的是a这个对象,是同一个对象,而b每次用的都是上次返回的对象。
如果改成这样

var a = fun(0); a=a.fun(1); a=a.fun(2); a=a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);

把返回的对象,重新赋值给a,这样两行的结果就是一样的了。
var c = fun(0).fun(1); c.fun(2); c.fun(3);
c 与他们的不同,只是var c = fun(0).fun(1); 之后用的是同一个对象罢了。

文章来源

顺便推荐几篇讲解闭包的文章

对象高级

对象的创建模式

Object构造函数创建

方式一:Object构造函数模式

​ 套路:先创建空Object对象,再动态添加属性/方法

​ 适合场景:起始时不确定对象内部数据

​ 问题:语句太多

<!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_Object构造函数模式</title>
</head>
<body>
 <script>
    // 一个人:name:"Tom",age:12
    var  obj = new Object();
    obj.name = "Tom";
    obj.age = 12;
    obj.setName = function(name){
        this.name = name;
    }

    console.log(obj);
 </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>
    //这里是一条赋值语句
    var  obj = {
        name:"Tom",
        age: 12,
        setName: function(name){
            this.name = name
        }
    }
    console.log(obj);

    //如果创建多个对象代码很重复
    var  obj2 = {
        name:"Bob",
        age: 13,
        setName: function(name){
            this.name = name
        }
    }
 </script>
</body>
</html>

工厂模式

方式三:工厂模式

​ 套路:通过工厂函数动态创建对象并返回

​ 适合场景:需要创建多个对象

​ 问题:对象没有一个具体的类型,都是Object类型

<!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>
    //返回一个对象的函数 ==> 工厂函数
    function createPerson(name,age){
        var obj = {
            name:name,
            age:age,
            setName:function(name){
                this.name = name;
            }
        }
        return obj;
    }
    var obj1 = createPerson("Top",12);
    var obj2 = createPerson("Bob",13);
    
    //obj1和obj2都是Object类型
    
    function createStudent(name,price){
        var obj = {
            name:name,
            price:price,
        }
        return obj;
    }
    var s = createStudent("张三",100);
    // s也是Object类型

    //此时判定不了哪个是Person哪个是Student
    console.log(s instanceof Object);   //true
    console.log(obj1 instanceof Object); //true
    console.log(obj2 instanceof Object); //true
 </script>
</body>
</html>

自定义构造函数模式

方式四:自定义构造函数模式

​ 套路:自定义构造函数,通过new创建对象

​ 适用场景:需要创建多个类型确定的对象

​ 问题:每个对象都有相同的数据,浪费内存

<!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>
    function Person(name,age){
        this.name = name;
        this.age = age;
        setName = function(name){
            this.name = name;
        }
    }
    function Student(name,price){
        this.name = name;
        this.price = price;
    }
    var p = new Person("Tom",12);
    var s = new Student("张三",100);

    console.log(p instanceof Person);  //true
    console.log(p instanceof Student); //false
    console.log(s instanceof Student); //true

    //问题:每个对象都有相同的数据,浪费内存
    var p2 = new Person("李四",13);
    var p3 = new Person("王五",13);

    //这里每一个对象都有一个setName方法,但是这个方法在每一个对象里都是一样的,浪费内存空间
    console.log(p1,p2); 
 </script>
</body>
</html>

构造函数+原型组合模式

方式六:05_构造函数+原型组合模式

​ 套路:自定义构造函数,属性在函数中初始化,方法添加到原型上

​ 使用场景:需要创建多个类型确定的对象

<!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>
    //在构造函数中只初始化一般属性
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    Person.prototype.setName = function(name){
        this.name = name;
    }

    var p1 = new Person("张三",13);
    var p2 = new Person("李四",13);

    console.log(p1.name,p1.age); //张三 13
    console.log(p2.name,p2.age); //李四 13
    
    p1.setName("Tom");
    p2.setName("Bob");

    console.log(p1.name,p1.age); //Tom 13
    console.log(p2.name,p2.age); //Bob 14
 </script>
</body>
</html>

继承模式

原型链的继承

方式一:原型链继承

1.套路

​ 1.定义父类型构造函数

​ 2.给父类型的原型添加方法

​ 3.定义子类型的构造函数

​ 4.创建父类型的对象赋值给子类型的原型

​ 5.将子类型原型的构造属性设置为子类型

​ 6.给子类型原型添加方法

​ 7.创建子类型的对象:可以调用父类型的方法

2.关键

​ 1.子类型额度原型为父类型的一个实例对象

<!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>
    //父类型
    function Super(){
        this.supProp = "Super proterty";
    }
    Super.prototype.showSuperProp = function(){
        console.log(this.supProp);
    }

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

    //这个方法不要添加到这里,因为待会会更改Sub.prototype的指向,这里写的话这个方法将会消失
    // Sub.prototype.showSubProp = function(){
    //     console.log(this.subProp);
    // }
    
    //我们之所以能看到Object的方法是因为有以下语句
    // Sub.prototype = new Object();
    // Sub.prototype = {};
    // 这样我们就可以用Object的方法了Sub.toString();

    //我们只需要把原型保存的值更改一下就可以看到父类原型的方法
    // 子类型额度原型为父类型的一个实例对象
    Sub.prototype = new Super();
    
    //子类的方法应该写到Sub.prototype = new Super()的后面,不然会报错
    Sub.prototype.showSubProp = function(){
        console.log(this.subProp);
    }
    
    var s = new Sub();

    s.showSuperProp();  //Super proterty
    s.showSubProp(); //Sub proterty
    
    console.log(Sub.prototype);
    console.log(Super.prototype);
 </script>
</body>
</html>

在这里插入图片描述

借用构造函数继承(假的)

1.套路

​ 1.定义父类型构造函数

​ 2.定义子类型构造函数

​ 3.在子类型构造函数中调用父类型构造

2.关键:

​ 1.在子类型构造函数中通用call()调用父类型构造函数

<!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>
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    function Student(name,age,price){
        //相当于this.Person(name,age),但是不能这样写
        // 注意,此时Person的this为当前对象
        Person.call(this,name,age); 
        this.price = price;
    }

    var s = new Student("张三",18,1000);
    console.log(s.name,s.age,s.price); //张三 18 1000

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

组合继承

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

<!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>
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    Person.prototype.setName = function(name){
        this.name = name;
    }

    function Student(name,age,price){
        //相当于this.Person(name,age),但是不能这样写
        // 注意,此时Person的this为当前对象
        Person.call(this,name,age);  //为了能看到父类的属性
        this.price = price;
    }

    Student.prototype = new Person(); //为了能看到父类的方法
    Student.prototype.constructor = Student;

    Student.prototype.setPrice = function(price){
        this.price = price;
    }

    var s = new Student("Tom",18,13000);
    s.setName("张三");
    s.setPrice(150000);

    console.log(s.name,s.age,s.price); //张三 18 150000

    var p = new Person("李四",18);
    // p.__proto__.setPrice(13000); //p.setPrice is not a function
    // console.log(p.__proto__.price);
 </script>
</body>
</html>

线程机制与事件机制

进程与线程

进程(process)

  • 程序的一次执行, 它占有一片独有的内存空间
  • 可以通过windows任务管理器查看进程

线程(thread)

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

相关知识

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

相关问题
何为多进程与多线程?
多进程运行: 一应用程序可以同时启动多个实例运行
多线程: 在一个进程内, 同时有多个线程运行
比较单线程与多线程?
多线程
优点
能有效提升CPU的利用率
缺点
创建多线程开销
线程间切换开销
死锁与状态同步问题
单线程
优点
顺序编程简单易懂
缺点
效率低
JS是单线程还是多线程?
js是单线程运行的
但使用H5中的 Web Workers可以多线程运行
浏览器运行是单线程还是多线程?
都是多线程运行的
浏览器运行是单进程还是多进程?
有的是单进程
firefox
老版IE
有的是多进程
chrome
新版IE
如何查看浏览器是否是多进程运行的呢?
任务管理器–>进程

浏览器内核

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

不同的浏览器可能不一样

  • Chrome, Safari : webkit
  • firefox : Gecko
  • IE : Trident
  • 360,搜狗等国内浏览器: Trident + webkit

内核由很多模块组成

  1. 主线程

    • js引擎模块 : 负责js程序的编译与运行
    • html,css文档解析模块 : 负责页面文本的解析
    • DOM/CSS模块 : 负责dom/css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)

  2. 分线程

    • 定时器模块 : 负责定时器的管理
    • DOM事件响应模块 : 负责事件的管理
    • 网络请求模块 : 负责ajax请求

定时器引发的思考

1.定时器真是定时执行吗?

​ 定时器并不能保证真正定时执行

​ 一般会延迟一丁点(可以接受),也有可能延迟很长时间(不能接受)

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

​ 在主线程执行的,JS是单线程的

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>03_定时器引发的思考</title>
</head>

<body>
    <button>启动定时器</button>
    <script>
        var btn = document.getElementsByTagName("button")[0];

        btn.onclick = function () {
            //获取当前时间    
            // var start  = new Date().getTime(); //方式一


            var start = Date.now(); //方式二

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

            //算出时间差值  
            setTimeout(function () {
                console.log('定时器执行了', Date.now() - start); 
            }, 200)

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

            //定时器执行了 202(不加上下面的循环则没什么太大的影响)

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

            }

            //定时器执行了 928
        }

    </script>
</body>

</html>

JS是单线程的

1.如何证明JS执行的单线程的?

​ setTimeout()的回调函数是在主线程执行的

​ 定时器回调函数只有运行栈中的代码全部执行完后才有可能执行

2.为什么JS要用单线程模式,而不用多线程模式?

​ javaScript的单线程,与它的用途有关

​ 作为浏览器脚本语言,javaScript的主要用途是与用户互动,以及操作DOM

​ 这决定了它只能是单线程,否则会带来会复杂的同步问题

3.代码的分类

​ 初始化代码

​ 回调函数

4.JS引擎执行代码的基本流程

先执行初始化代码:包含一些特别的代码

​ 设置定时器

​ 绑定监听

​ 发送AJAX请求

后面在某个时刻才会执行回调代码

<!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_JS是单线程的</title>
</head>
<body>
 <script>
    setTimeout(function(){
        console.log('timeout 2222');
    },5000);
    setTimeout(function(){
        console.log('timeout 1111');
    },4000);
    function fn(){
        console.log('fn');
    }
    fn();
    //执行顺序为:fn --> timeout 1111 --> timeout 2222

    //alert会暂停当前主线程的执行,
    //  同时暂停了计时(2022年7月12日22:39:24:并没有暂停,等五秒按确定之后直接出来了)
    console.log("alert之前");
    alert("------");
    console.log("alert之后");
 </script>
</body>
</html>

事件循环模型

1.所有代码分类

​ 初始化执行代码(同步代码):包含绑定DOM事件监听,设置定时器,发送ajax请求的代码

​ 回调执行代码(异步代码):处理回调逻辑

2.JS引擎执行代码的基本流程:

​ 初始化代码===>回调代码

3.模型的2个重要组成部分:

​ 事件管理模型

​ 回调队列

4.模型的运转流程

​ 执行初始化代码,将事件回调函数交给对应模块管理

​ 当事件发生时,管理模块会将回调函数及其数据添加到回调队列中

​ 只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行

模型原理图

在这里插入图片描述

重要相关概念

  1. 执行栈
    • execution stack
    • 所有的代码都是在此空间中执行的
  2. 浏览器内核
    • browser core
    • js引擎模块(在主线程处理)
    • 其它模块(在主/分线程处理)
  3. 任务队列
    • task queue
  4. 消息队列
    • message queue
  5. 事件队列
    • event queue
  6. 事件轮询
  • event loop
  • 从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
  1. 事件驱动模型
    • event-driven interaction model
  2. 请求响应模型
    • request-response model
<!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>
    <button>点击测试</button>
     <script>
        function fn1(){
            console.log('fn1()');
        }
        fn1();

        document.getElementsByTagName("Button")[0].onclick = function(){
            console.log("点击了btn");
        }
        setTimeout(function(){
            console.log('定时器执行了');
        },1000);
        function fn2(){
            console.log('fn2()');
        }
        fn2();

        //执行顺序为:
        // 'fn1()' --> 'fn2()' --> '定时器执行了'/'点击了btn'(看手速)
     </script>
</body>
</html>

H5 Web Workers(多线程)

介绍

  • Web Workers 是 HTML5 提供的一个javascript多线程解决方案
  • 我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面
  • 但是子线程完全受主线程控制,且不得操作DOM
    所以,这个新标准并没有改变JavaScript单线程的本质

1.H5规范提供了JS分线程的实现,取名为:Web Workers

2.相关API

​ Worker:构造函数,加载分线程执行的jS文件

​ Worker.prototype.onmessage:用于接收另一个线程的回调函数

​ Worker.prototype.postMessage:向另一个线程发送消息

3.不足

​ Worker内代码不能操作DOM(更新UI)

​ 不能跨域加载JS

​ 不是每个浏览器都支持这个新特性

图解

在这里插入图片描述

使用

创建在分线程执行的js文件

var onmessage =function (event){ //不能用函数声明
    console.log('onMessage()22');
    var upper = event.data.toUpperCase();//通过event.data获得发送来的数据
    postMessage( upper );//将获取到的数据发送会主线程
}

在主线程中的js中发消息并设置回调

//创建一个Worker对象并向它传递将在新线程中执行的脚本的URL
var worker = new Worker("worker.js");  
//接收worker传过来的数据函数
worker.onmessage = function (event) {     
    console.log(event.data);             
};
//向worker发送数据
worker.postMessage("hello world");    

主线程

<!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_ Web_Workers</title>
</head>
<body>
<input type="text" placeholder="计算">
<button>计算</button>
<script>
    
    var inp = document.getElementsByTagName("input")[0];
    
    document.getElementsByTagName("button")[0].onclick = function(){
        var number = inp.value;
        //创建一个Worker对象
        var worker = new Worker('worker.js'); //路径:worker.js

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

        //设置一个监听用来接收分线程的消息
        worker.onmessage = function(event){
            console.log('主线程接收分线程返回的数据' + event.data);
            alert(event.data);
        }
    }

    //斐波那契数列
    //此时如果用户输入的数据比较大,那么这段代码就需要运行很久
    //并且是在主线程运行的,运行期间用户不能进行任何操作
    //所以可以将下列代码放到分线程执行,这样就不会影响主线程的操作
    // function fibonacci(n){
    //     if(n <= 2){
    //         return 1;
    //     }

    //     return fibonacci(n-1) + fibonacci(n-2);
    // }
    
</script>
</body>
</html>

​ 分线程:worker.js

//创建在分线程执行的js文件

function fibonacci(n){
    if(n <= 2){
        return 1;
    }

    return fibonacci(n-1) + fibonacci(n-2);
}

var onmessage = function(event){
    var number = event.data;
    console.log("分线程接收到主线程发送的数据" + number);
    var result = fibonacci(number);
    postMessage(result);
    console.log("分线程向主线程返回数据" + result);

    // alert(result); alery是window的方法,在分线程不能调用
} 
//分线程中的全局对象不再是window,所以在分线程中不可能更新界面
console.log(this);
/*
DedicatedWorkerGlobalScope 
{name: '', 
onmessageerror: null, 
onmessage: ƒ, 
cancelAnimationFrame: ƒ, 
close: ƒ, 
….....}
*/
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值