JS高级总结

前言

  学习JS高级后的总结。这里内容多了些,找个时间在分开细写~在这里插入图片描述

强类型的变量类型是不能改变的,弱类型变量是随需改变的。所以JS是弱类型语言。

1 预解析(预编译)

1.1 代码段

    1)一个script标签就是一个代码段;JS代码在执行时,是一个代码段一个代码段执行;
    2)代码段和代码段之间是彼此独立的,上面的代码段报错了,不会影响下面的代码段执行;
    3)上面代码段不能使用下面代码段定义的数据;
    4)下面代码段中可以使用上面的代码段中定义的变量。

<script>
    var a = 100;
    // 上面代码段不能使用下面代码段定义的数据
    console.log(b);  // ReferenceError: b is not defined
</script>
<!-- 代码段和代码段之间是彼此独立的,上面的代码段报错了,不会影响下面的代码段执行 -->
<script>
    var b = 666;
    // 下面代码段中可以使用上面的代码段中定义的变量
    console.log(a);
</script>

1.2 预解析(预编译)

JS代码的执行分两个阶段:
  JS代码在执行时,分成两个阶段,一个叫预解析(预编译),一个叫执行,预解析(预编译)结束后,才会进入到执行阶段。
什么是预解析?

     1)浏览器在执行JS代码的时候会分成两部分操作:预解析以及逐行执行代码
     2)也就是说浏览器不会直接执行代码, 而是加工处理之后再执行,
     3)这个加工处理的过程, 我们就称之为预解析

预编译期间:

    1)把声明提升:加var的变量就是被提升,function声明的函数也会提升,提升到代码段的最前面。
    2)函数内部的局部变量,提升到函数体的最前面。
    注意:变量的提升仅仅是声明 函数的提升不只提升了声明,也提升赋值。

例1:

<script>
    // 下面的代码有2个声明
    // 1) 加var的变量a  
    // 2)使用function 声明了一个函数
    console.log(a) 
    // 赋值操作并不会提升
    var a = 110;
    console.log(a) 
    fn();
    // function声明的函数提升的是整体
    function fn() {
        console.log("这是一个fn函数~")
    }
</script>
========================预编译后的代码=================================
<script>
    // 提升的仅仅是声明  不会提升赋值
    var a;  // und
    // 函数的声明和赋值都提升了
    function fn() {
        console.log("这是一个fn函数~")
    }
    // 代码进入到了执行阶段
    console.log(a);  // und
    a = 110; // 仅仅是赋值操作
    console.log(a);  // 110
    
    fn();  // 函数调用
</script>

在这里插入图片描述
例2:

提升的同名变量名或函数名,函数会覆盖变量,函数的优先给是高于变量的优先级的。
在JS中,函数是一等公民

<script>
    console.log(value); 
    var value = 123;
    function value() {
        console.log("fn value");
    }
    console.log(value); 
</script>

<script>
    // 预编译后的代码
    // var value; 
    // 当函数提升后,由于它的名字和变量的名字一样
    // 提升后,只会有一个value,这个value就是函数了
    // 加var的value就被覆盖了
    function value() {
        console.log("fn value");
    }
    // .log(value);   
    // value = 123;  重新给value覆盖  以前value是函数,现在你把123覆值给了value
    // .log(value);  123
</script>

在这里插入图片描述

2 执行上下文和数据存储

2.1 全局对象/window对象

全局对象:
        叫window 只要我们写的全局变量或在全局中写的函数,都会挂载到window上面。
        Globle Object ===> GO 说白了,就是window对象
        全局对象上,默认也有很多的东西,alert().....
     var a = 110; // 全局变量
     b = 666; // 全局变量
     function fn() { // 全局函数
     console.log("fn...")
}

2.2 执行上下文

代码分类:
   全局代码:函数外面的代码都是全局代码。
   函数/局部代码:一个函数就是一个局部代码。

EC:Execute Context(执行上下文)
EC(G):Execute Context Globle(全局执行上下文)
EC(Fn()):Execute Context Fn()(局部执行上下文)
ECS:Execute Context Stack(执行上下栈)
GO:Globle Object(全局对象)
AO:Active Object(活动对象)
VO:Variable Object(变量对象)

执行上下文:Execute Context ====> EC
   全局代码执行产生ECG,每当调用一个函数,就产生一个函数的EC。每产生一个EC,需要放到ECS,当函数调用完毕,这个EC就是出栈,出栈后的EC,会被销毁,所谓的销毁 指是它们分配的内存空间都要被释放掉。
   作用:给当前代码提供数据。
全局执行上下文:(可放进杯子)
   当全局代码执行时,就会产生一个全局的执行上下文,EC(G)。
局部执行上下文:(可放进杯子)
   当函数代码执行时,就会产生一个局部的执行上下文,EC(Fn)。只要调用一个函数,就会产生一个局部执行上下文。调用100个函数,就会产生100个执行上下文。
执行上下文栈:
   Execute Context Stack ===> ECS
   栈:杯子
JS在执行代码时,肯定先执行全局代码,就会产生EC(G),这个EC(G)就要入栈。
当我们调用一个函数,就会产生一个局部的执行上下文,此时这个局部的执行上下文也要入栈。
当函数调用完毕后,这个EC就要出栈,又进入EC(G),当全局代码执行完后,EC(G)也要出栈。
注:栈:在计算机中, 栈是一种数据结构, 这种数据结构描述了操作数据的一方式。先进后出。
  队列:先进先出。

2.3 数据存储

内存分区:
   这里只需要掌握两个区,一个是栈区,一个是堆区。
   基本数据类型,是存储在栈区的,引用数据类型是存储在堆区,堆区的地址,还是保存在栈区。(引用数据为对象包括:数组,函数,对象...)
对于函数而言:形参相当于函数内部的加var的局部变量。

3 var let const(声明变量的方式和区别)

3.1 加var的变量和不加var的变量有什么区别

  1)加var的变量在预编译期间会提升,不加var的变量在预编译期间不会提升。
  2)不管有没有加var,创建的变量,都会放到GO中的,也就是可以通过window.xx。
  3)加var的变量,可能是全局变量,也可能是局部变量,不加var的变量,只能是全局变量。
  4)加var的局部变量,不会作用window的属性
项目中尽量不要使用全局变量。在项目中,不要使用没有加var的变量。

使用var声明变量的缺点不足:

    1)提升
    2)重复声明 浏览器中有这样一个机制,如果一个变量提升过了,后面遇到同名变量,就不会提升了。

其中:

    var a=b=4;等价于var a=4;b=4;//这里的b是全局变量,不会提升!
    var a=4,b=4;等价于 var a=4;var b=4;

3.2 使用let声明的变量的特点

1)使用let声明的变量没有提升。

        针对这个错误:ReferenceError: Cannot access 'a' before initialization
        有人是这样说的:let声明的变量也有提升。但是没有赋值(没有初始化)
        就记一种:使用let声明的变量也提升,只不过没有初始化,去使用的时候就报错了。

2)使用let配合{}会形成块级作用域。 在C语言中,就有块级作用域。

        在JS中,原本是没有块级作用域。在块中声明的变量,只能在块中使用。

3)使用let声明的变量并不会挂载到GO中
4)使用let声明的变量不能重复声明
需要知道:项目中不要使用var来声明变量,你要声明变量,使用let。

3.3 使用const声明的变量(常量)的特点

  1)声明的变量不能修改(常量)
  2)声明时必须赋值(初始化) 定义 = 声明+赋值(初始化)
  3)const声明的常量也不会提升。
  4)const声明的常量配合{} 也会形成块级作用域
  5)const声明的常量也不会挂载到GO上。

总结:项目中不要使用var 如果声明变量使用let,如果声明常量,使用const。

4 JS中常见的错误

4.1 语法错误

你写的代码 JS编译器不认识。 代码也没有进行预编译,更没有执行。

  解决非常简单 找到你的代码,哪里不符合人家的语法。
  常见的IDE,自带语法错误检查。
           var a = 1;
           function fn(){}

在这里插入图片描述

4.2 引用错误

  当使用了一个没有定义的数据,可能就会报引用错误 解决:你的数据有没有定义出来。
  特点:从引用错误开始,后面的代码就不会执行了。
           console.log(b);

在这里插入图片描述

4.3 类型错误

  数据类型使用不当,你提供的数据类型,并不是JS所需要的数据类型,
  解决:检测你的数据的数据类型。
          let c = ["hello"];
          ;(function(){
              c(); // c本身是个数组,这里当成函数使用了
          })();

在这里插入图片描述

4.4 范围错误

  在使用容器时,方式不对 容器:array object 特别是,使用长度(容器的大小)。
           let arr = new Array(-10);//定义一个数组,里面有10个空间(这里写-10)
           console.log(arr);

在这里插入图片描述

4.5 逻辑错误

  它在控制台中不会错误,结果并不是我们需要的,打印的结果和我们想像的结果不一样。
  解决:通过JS断点调试。
  强调:再出错,不要问我,先打断点调试。

断点可以通过两个地方打

  1)通过代码 debugger bug虫子 debug调试错误。
  2)在浏览器的调试工具中打断点。
     debugger;//写在哪行表示在哪行终止。此语句用于停止执行 JavaScript,并调用 (如果可用) 调试函数。

  形参相当于函数内部的加var的局部变量。
  使用let声明变量a let不能重复声明。

5 闭包

  在一个函数内部嵌套了一个函数,并且里面函数引用外面函数的变量,这样就会形成闭包。
  闭包的前提是函数嵌套,x所应对的栈空间释放不了。
专业一点:一个不会释放内存的栈空间就是一个闭包,闭包是一个没有被释放掉内存的栈空间。

闭包:
    一个不能销毁的栈空间(局部EC),就是一个闭包。两大作用:
    1)保存 延长闭包中数据的生命周期 所对应的内存一直不会被释放掉;缺点:造成内存泄露。
    2)保护 外面想去访问里面的数据,是访问不了的。由于x是函数内部的变量,外面也不能访问,对x有保护作用

合理地使用闭包,项目中有时候,也需要使用到闭包。
例:

    var i = 5;
    function fn(i) {
        return function (n) {
            console.log(n+(++i))
        }
    }
    var f = fn(1);//如果函数内没有返回值 这里就是undefined!
    fn(2);//没人引用它,所以出栈销毁。
    console.log("--------");
    fn(3)(4);//后面被引用(4)/fn内部的f()函数引用 形成闭包,执行结束,销毁。
    console.log("--------");
    fn(5)(6);//后面被引用(6)/fn内部的f()函数引用 形成闭包,执行结束,销毁。
    f(7);//前面的结束都销毁了,这里引用第7行形成闭包。
    console.log(i)

结果展示

6 IIFE(立即调用函数表达式)

MDN
  IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。

    ;(function fn(){//这个分号是预防前面代码影响立即调用函数表达式;前方如果写对象没有带;就会出错
          console.log("fn...");
    }())
    另外几种写法:
    ;(function f(){}())
    ;+function f(){}()
    ;-function f(){}()
    ;!function f(){}()

  这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。第一部分是包围在 圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
  第二部分再一次使用 () 创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。

注意:当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。

7 this

JS中的this:

  1)如果this出现在一普通的函数中,你通过window.f调用此函数,那么里面的this就表示window就看前面是谁,那么this就表示谁 。window.f() f中的this就表示window。

    function fn() {   // this出现在普通的函数中
        console.log(this) // 此时this表示window
    }
    fn()
    window.fn()

  2)事件绑定中 监听器中的this表示事件源。

    let btn = document.getElementsByTagName("button")[0]
    btn.onclick = function(){
        // 在监听器中,this表示事件源
        console.log(this)
    }

  3)对象是属性的无序集合 在一个对象中也可以能函数 如果一个函数出现在对象中 这个函数叫方法。通过对象打点调用了方法,那么这个方法中的this表示这个对象;就看.前面是谁,那么this就表示谁。

    let obj = {
        name: "wangcai",
        age: 100,
        // this出现在方法中
        run: function () {         // run叫方法  函数
            console.log(this)            // this表示当前的对象
            console.log("run....")       // this代表是谁,你就看.前面是谁,
        }                          // this代表什么谁,只有去调用此方法时,才知道this是谁
    }
    obj.run()                      // .前面是obj,代表this是obj

  4)在IIFE中,this表示window。

  5)在严格模式和非严格模式下,this代表的含义是不一样的。
    this指向总结:严格模式和非严格模式
    JavaScript严格模式下关于this的几种指向详解
  6)死记:别人写的一片代码中有this,谁也不知道this代表什么含义,只能代码执行起来后,才知道this表示什么。

8 面向对象

8.1 什么是面向对象

  每个对象都由类定义,可以把类看做对象的配方。
类:有了类,就可以创建对象 类名都会大写(为了和变量名区分)
对象:通过类创建出来的。

一种面向对象语言需要向开发者提供四种基本能力(特征):
         1)封装 - 把相关的信息(无论数据或方法)存储在对象中的能力
         2)聚集 - 把一个对象存储在另一个对象内的能力
         3)继承 - 由另一个类(或多个类)得来类的属性和方法的能力
         4)多态 - 编写能以多种方法运行的函数或方法的能力

8.2 JS中创建对象有两种方式

  1)通过字面量创建,仅仅是JS中才有,在JAVA中,对象都是new出来的
  2)通过new一个类

        // 通过new一个类去创建对象
        let obj = new Object();
        obj.name = "xq";
        console.log(obj.name); // {}
        // 通过字面量的形式创建对象
        let obj2 = {
        name:"wc"
        } 
        obj2.age = 110;
        console.log(obj2.name)
        console.log(obj2.age)

8.3 JS中提供的内置类

// 先有类,通过new就可以创建一个对象
// Number叫类  在JS中也叫构造器    在JAVA中,叫类
// new出来的是对象   对象是存储在堆中
// num1是保存了堆的地址   num1指向那个堆
// 不严谨地来说,把num1称为一个对象
    let num1 = new Number(110)
    // 对象是属性的无序集合
    console.dir(num1);

下面的例子中: Object叫类/构造器; obj2叫对象

    let obj2 = new Object();//类名都会大写(为了和变量名区分)
    obj2.name= "xq";
    obj2.age = 666;
    // instanceof 判断某个对象是否属于某个类
    console.log(obj2 instanceof Object);  // true
    // Number也是一个类
    console.log(obj2 instanceof Number); // false

9 一切都是对象

  只有对象才能打点。
  对象是属性的无需集合。
  基本数据类型也可以是对象:

    let str = "hello";
    str.toUpperCase()//str会瞬间包装成一个对象
    str.length;// str会瞬间包装成一个对象
    let num = 110;
    num.toFixed(2);//num会瞬间包装成一个对象

10 对象是属性的无需集合

  操作集合中的属性,说到操作,就是指"增删改查"。

10.1 查(遍历对象、数组)

如何访问对象中的属性。在JS中提供了两种方式,可以访问对象中的方式
  1)打点调用.属性访问运算符 非常常用
  2)通过[]调用 目前来说,先了解
当访问一个对象上不存在的属性,得到的undefiend
遍历对象:把对象中每一个属性都获取到 for in来遍历对象。(不能使用for循环来遍历对象)
key 表示对象中所有的属性名 也叫键 key是变量

    for(let key in obj){
        // 这个地方,只能通过[]来访问
        console.log(obj[key]);
    }

遍历数组:数组就是一种特殊的对象,键是索引的对象 。

    let arr = ["a","b","c"];
    // 1)使用for循环来遍历数组
    for(let i=0; i<arr.length; i++){
        console.log(arr[i]);
    }
    // 2)使用foreach来遍历数组
    arr.forEach(function(item){    // item 表示数组中的每一个元素
        console.log(item);
    })
    // 3)使用for in来遍历数组
    for(let key in arr){
        console.log(arr[key]);
    }
    // 4)使用for of来遍历数组 ES6新方法
    for(let item of arr){
        console.log(item);
    }

10.2 增

  给对象中增加一个属性:通过 obj.xxx = “xxx”

10.3 改

  所谓的修改,就是添加一个同名的属性,后面添加的同名属性,会把前面的覆盖掉

10.4 删

  delete运算符 是一个运算符。

    let obj = {
    name:"wangcai",
    age:100
    } 
    delete obj.age; // 删除obj中的age属性
    console.log(obj.age); // 相当于访问一个对象上不存在的属性 结果是undefined

11 公有属性和私有属性

console.log()语句打印的是结果,直接显示信息;
console.dir()语句打印的是内容,显示对象的所有属性和方法。
  对象是属性的无序集合。
  操作集合中的属性:增删改查。
  每个对象上面都是一个属性叫__proto__,它指向一个对象,这个对象叫叫隐式原型对象,简称为原型对象

    let obj = {
        name:"wc",  // 私有属性
        age:100   // 私有属性
    }
    console.dir(obj);//显示对象的所有属性和方法

属性分类:
  1)公有属性:对于obj来说,公有属性是指__proto__里面的属性。
  2)私有属性:指name和age。
hasOwnProperty(): 判断一个属性是否是一个对象的私有属性。
delete运算符,只能删除私有属性,不能删除公有属性。
如果说私有属性和公有属性同名: 调用时,调用的是自己的私有属性。 在一个对象中找一个属性,先在自己的私有属性中找,找不到,才会去公有属性中找。
在这里插入图片描述在这里插入图片描述

12 原型对象和原型链

1)每一个对象上,都有一个属性,叫__proto__,它指向了一个对象,这个对象我们叫原型对象
2)每一个构造器(类,函数)也是对象,这个对象上,有一个属性,叫prototype,它也指向一个对象,和__proto__指向同一个对象,也是原型对象
3)每一个原型对象上,都是有一个属性,叫constructor,它指向此原型对象所对应的构造器
4)原型链:原型链指查找对象上某个属性的查找机制,查找一个对象上的私有属性,先在自己的私有属性中找,找不到,就沿着__proto__去原型对象上找…
5)作用域链:在EC中,查找数据的一个机制。(EC是局部执行上下文)

.叫属性访问运算符

13 对象是由类(函数)创建的

函数有多个角色:函数,方法,对象,类
  角色1:把函数当作是一个普通的函数

    function fn() { // 函数声明
        console.log("fn...")
    } f
    n(); // 函数调用

  角色2:函数出现在对象,是一个方法

    let obj = {
        name: "wangcai",
        age: 100,
        //下面的函数是一个方法
        say: function () { // 一个函数出现在对象中, 我们叫它为方法
            console.log("say...")
        }
    } o
    bj.say();

  角色3:函数也是一个对象

    function fn() {
        console.log("fn...")
    } /
        / fn也可以是一个对象 对象是属性的无序集合
    fn.a = 1; // 给对象上添加私有属性 a
    fn.b = 2; // 给对象上添加私有属性 b
    fn.c = 3; // 给对象上添加私有属性 c
    console.dir(fn)

  角色4:函数也可以是一个类(这时候推荐首字母大写)

    function Person(name) {
        this.name = name; // 给对象设置一个私有属性
    } // 只要是对象,必定有一个属性叫__proto__
    let p1 = new Person("wangcai"); // p1是对象 对象是属性的无序集合
    console.dir(p1);

new本意是新的意思,在计算机中,new就是用来创建对象。 只要加new,最终的结果肯定是对象。

14 call apply bind的使用

  call,aplly:都可以改变一个函数中的this指向,并且让函数执行。
  区别:传参不一样,apply传参需要传递一个数组。

    function say(){
        console.log("我是say函数~");
        console.log(this.name);
    }
    let obj1 = { name:"wc" }
    let obj2 = { name:"xq" }
    // 1)让函数执行  2)改变this指向
    // 让say函数中的this指向obj1
    say(obj1)//这里没有加call
    say.call(obj2);    // 让say中的this指向obj2,并且让say执行
                         传参的区别
    //say.call(obj1,参数1,参数2);//如果有参数的话这样写
    //say.apply(obj1,[参数1,参数2]);//apply传参需要传递一个数组

输出
凭借apply传参传递数组的特性可以用来计算数组的最大值。

    // apply可以让max执行,并且改变max中的this指向
    // apply传参正好是传递一个数组
    // max中的this不需要考虑
    console.log(Math.max.apply({},arr));//arr是数组名,arr前面的参数是谁不重要,仅是为了让它指向object

  bind:也可以改变函数中this的指向,但是不会让函数执行,仅仅是改变this指向。返回一个改变了this指向的函数。

15 继承

继承:
    有两类:
          父类:也叫基类
          子类:也叫派生类
    好处:可以让子类去继承父类的属性和方法 子类中本来没有,如果继承了父类的,子类就可以使用这些属性和方法了。

15.1 原型继承

特点:
  1)继承父类的公有属性和私有属性,作为子类的公有属性
  2)核心代码:Son.prototype = new Father();

    function Father(a){
        this.a = a; // 这是父类的私有属性
    }
    Father.prototype.getA = function(){ // getA是它的公有属性
        console.log(this.a);
    }// 此时Father和Son没有任何关系
    //子类  派生类 Son中有没有a    可以让Son继子Father中的a
    function Son(){
    }
    // 改变了Son的原型对象  说白了,改变了Son原型
    // 让Son继承了Father的公有属性和私有属性,作为Son的公有属性
    Son.prototype = new Father();
    // 修正原型对象上的contructor指向
    Son.prototype.constructor = Son;
    let son = new Son();
    console.log(son.a);
    son.getA();  // 

15.2 call继承

特点:
  1)继承父类的私有属性,作为子类的私有属性;用的不多 一般情况下,别人私有的,是不想让别人继承。
  2)核心代码:Father.call(this);

    function Father(){
        // 改变的仅仅是此this指向
        this.a = 110; 
    }
    Father.prototype.getA = function(){
        console.log(this.a);
    }
    // let f1 = new Father()

    function Son(){

        // Father是类 是函数  是对象  是方法
        // .call  作用:1)改变this指向  2)让Father执行
        // 指向谁call的第1个参数  指向this
        Father.call(this)
    }
    let son = new Son();
    console.log(son.a);
    son.getA();//此时getA还是无法访问,即只能继承

15.3 组合继承

  组合继承: call继承+原型继承
特点:
  1)继承父类的公有属性和私有属性,作为子类的公有属性 >原型继承
  2)继承父类的私有属性,作为子类的私有属性 >call继承
  3)父的私有属性被继承了两次,当我们去访问一个属性时,会先访问私有属性

    function Father() {
        this.a = 110;
    }
    Father.prototype.getA = function () {
        console.log(this.a);
    }
    function Son() {
        // call继承
        Father.call(this)
    }
    // 原型继承
    Son.prototype = new Father(); 
    Son.prototype.constructor = Son;

    let son = new Son();
    console.log(son.a); // 访问a  访问的是son的私有属性
    son.getA()

15.4 只继承父类的公有属性作为子类的公有属性

    function Father() {
        this.a = 110;
    }
    Father.prototype.getA = function () {
        console.log("我是Father的公有属性");
        console.log(this.a);
    }

    // 把Father的原型对象,赋值给Son的原型对象  这样就继承了原型对象中的getA
    // 不好,因为Son和Father共享一个原型对象,当通过Son修改了原型对象,也会影响Father的原型对象
    // Son.prototype = Father.prototype;
    var Fn = function (){}
    // 此时Fn和Father共享同一个原型对象  如果你不对Fn的原型对象操作,就会影响到Father的原形对象
    Fn.prototype = Father.prototype;

    // new Fn(); 创建出来一个新的对象
    Son.prototype = new Fn();

    function Son() {
    }
    let son = new Son();
    console.log(son.a); // 访问a  访问的是son的私有属性
    son.getA()
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值