高阶js

88 阅读10分钟

高阶js

原型与原型链

导入

  1. 一切引用类型皆对象。数组是对象,函数是对象,对象还是对象。对象里面的一切都是属性,只有属性,没有方法。那么这样方法如何表示呢?——方法也是一种属性。因为它的属性表示为键值对的形式。

  2. 函数是对象的一种,因为通过instanceof函数可以判断。

    var fn = function () { };
    console.log(fn instanceof Object);  // true
    
  3. 对象都是通过函数创建的

    var obj = new Object();
    obj.a = 10;
    obj.b = 20;
    var arr = new Array();
    arr[0] = 5;
    arr[1] = 'x';
    arr[2] = true;
    

正文

总结:

​ 每个对象的__proto__都指向其对于的构造函数的prototype,

​ 任何一个函数的__proto__都指向Function.prototype

​ Function构造函数是一个对象,指向构造函数的构造函数的prototype Function.__proto__==Function.prototype

​ 普通对象本身是一个对象,是由Object构造函数构造出来的

​ 任何一个对象的__proto__都指向Object.prototype

Object.__proto__.__proto__==Object.prototype

  1. 每个函数都有一个prototype属性。
  • 这个prototype的属性值是一个对象(属性的集合),默认的只有一个叫做constructor的属性,指向这个函数本身。Function.prototype.constructor==Function//ture
  1. 每个对象都有一个__proto__属性,指向创建该对象的函数的prototype。即:fn.__proto__ === Fn.prototype

    • 这里的__proto__成为“隐式原型”

      function Fn() { }
      Fn.prototype.name = '张三';
      Fn.prototype.getYear = function () {
          return 1988;
      };
      var fn = new Fn();
      console.log(fn.name);
      console.log(fn.getYear());
      //Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。
      
  2. Object.prototype确实一个特例——它的__proto__指向的是null,切记切记!(Object.__proto__.__proto__==Object.prototype)

  3. Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的。所以它的__proto__指向了自身的Prototype。

    即:Function.__proto===Function.prototype(foo.__proto__===Function.prototype===Function.__proto__)

  4. Function.prototype指向的对象,它的__proto__也指向Object.prototype

    因为Function.prototype指向的对象也是一个普通的被Object创建的对象

  5. Instanceof的判断规则是:(A instanceof B)沿着A的proto这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。

  6. 原型链:访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链

  console.log(foo.__proto__.__proto__ === Object.prototype);//true

  console.log(Function.__proto__ === Function.prototype);//true

  console.log(foo.__proto__ === Function.prototype);//true

  console.log(foo.__proto__ === Function.__proto__);//true

  function foo() { }

  console.log(foo instanceof Function);//true

  console.log(foo instanceof Object);//true
    //构造函数
    function Foo(a, b) {
        this.a = a;
        this.b = b;
    }
    //实例化函数
    var f1 = new Foo('qq', 'ww')

    console.log(f1.__proto__ == Foo.prototype);

    console.log(Foo.prototype.__proto__ == Object.prototype);

    console.log(Foo.__proto__ == Function.prototype);

    console.log(Foo.__proto__.__proto__ == Object.prototype);

    console.log(Object.__proto__ == Function.prototype);

    console.log(Function.prototype == Function.__proto__);
  1. 原型的灵活性:对象属性可以随时改动。如果感觉当前缺少要用的方法,可以自己去创建。

  2. 如何区分一个属性是在本身还是在原型上呢?

    Object.prototype.hasOwnProperty(item)

    尤其是在for in遍历一个对象时,考虑是否需要原型上的属性

继承

  • 原型继承

  • 盗用构造函数

  • 组合继承

  • 原型式继承

  • 寄生式继承

javascript中的继承是通过原型链来体现的。

js执行机制

进程线程协程

  1. 进程:

    • 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位(cpu资源分配的最小单位)。

    • 会在内存空间里形成一个独立的内存体

    • 状态:就绪、运行、阻塞

  2. 线程:

    • cpu资源调度的最小单位,被包含在进程之中,有单线程和多线程。
  3. 一个进程可以包含多个线程,每条线程执行不同的任务,但可以共享资源完成任务提高了资源使用效率以及系统效率

    • 状态:就绪、运行、阻塞
  4. 对比:

    • 进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

    • 进程拥有独立的内存空间,线程则共享所在进程中的内存空间

    • 进程之间切换开销较大,而线程间切换开销较小

  5. 协程:

    • 比线程更小,由用户管理控制,多个协程可以运行在一个线程上面

并发&并行

  1. 并发:在某一时间段,几个程序在同一cpu上运行,但在任意一个时间点,只有一个程序在cpu运行

  2. 并行:两线程同时进行

并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以最关键的点就是:是否是 同时

同步&异步

  1. 同步任务:主线程上排队执行的任务,只有前一个任务执行完,才继续执行下一个
  2. 异步任务:不进入主线程而进入任务队列的任务。只有任务队列同时主线程,异步任务才进入主线程执行
常见异步操作
  1. 回调函数

    • 同步回调,比如数组的一些方法(pop、unshift...)会立即执行完全执行完了才结束,不会放入回调队列中

    • 异步回调,不会立即执行,放入回调队列来执行

  2. 事件监听

满足事件发生的条件才执行

  1. 发布订阅模式

  2. promises对象

    • Promise是一个构造函数,用来封装一个异步操作并可以获得异步执行结果

    • Promise的参数是一个回调函数(同步),返回值为仍Promise对象

    • Promise对象必须实现then方法,而且then必须返回一个Promise对象,同一个Promise对象的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致

    • then方法接受两个参数,第一个参数是成功时的回调(异步),在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调(异步)

  3. Generator函数

  4. async/await

    • async是用来声明一个异步方法,await用来等待异步方法的执行

    • 正常情况下await后边是一个Promise对象,返回该对象的结果。如果不是Promise对象,直接返回封装好的promise。await都会阻塞函数中后边的代码(即加入微队列)

js执行机制:

  • 首先判断js代码是同步还是异步,同步就进入主线程,异步就进入event table

  • 异步任务在event table中注册函数,当满足触发条件后,被推入event queue

  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中

宏任务&微任务

  1. 宏任务:setTimeout、setInterval、I/O...

  2. 微任务:promise

  3. promise:异步的容器

    • 三种状态:pending fullilled rejected 具有固化性 变为成功就不会在执行rejected函数

    • 同步代码优先执行,再执行异步代码, promise内部的为同步 只有then中的成功和失败函数才是异步任务。异步任务放在异步队列中。微任务与宏任务相比 微任务先执行

作用域和执行上下文

  1. 作用域是一个很抽象的概念,类似于一个“地盘”。作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突
  2. 函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域,if和for没有块级作用域(let和const可以形成),只有函数作用域。
  3. 一共有三种方式可以形成作用域,全局、函数、块。全局作用域在运行时便默认存在,我们可以使用的是函数与块作用域。
  4. 作用域链:
    • 现在当前作用域查找a,如果有则获取并结束。如果没有则继续;
    • 如果当前作用域是全局作用域,则证明a未定义,结束;否则继续
    • (不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域
    • 跳转到第一步。
  5. 执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段
    • 创建阶段:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。
    • 执行阶段
    • 回收阶段:执行上下文出栈等待虚拟机回收执行上下文
 var a = 10;
 function fn(){
  console.log(a)//10 函数创建时就确定了a要取值的作用域
 }
 function bar(f){
  var a = 20;
  f();
 }
 bar(fn)
  1. 全局代码的上下文环境数据内容为
AB
普通变量、函数表达式,如: var a = 10;默认赋值为undefined
函数声明,如: function fn() { }赋值
this赋值

​ 如果代码段是函数体:

AB
参数赋值
arguments赋值
自由变量的取值作用域赋值
  1. 执行上下文栈 : 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境,并压入栈的顶部。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程。

this

  1. 默认绑定规则:函数独立调用 指向window 与函数位置没有关系 立即执行函数也指向window
var obj = {
    a: 2,
    foo: function () {
        var a = 3;
        console.log(this);//obj
        function test() {
            console.log(this);//window
            console.log(a);//3
        }
        test()
    }
}
obj.foo()//隐式绑定规则
  1. 隐式绑定绑定规则:谁调用指向谁

    隐式丢失

    • 变量赋值

      function foo() {
          console.log(this);
      }
      var obj = {
          a: 2,
          foo: foo
      }
      var bar = obj.foo;
      bar();//window
      
    • 参数赋值

      父函数有能力决定子函数的this

      var arr = [];
      arr.forEach(function (item, index, arr) {
          console.log(this);//window
      })
      arr.sort(function (a, b) {
          console.log(this);//window
          return a - b;
      })
      setInterval(function () {
          console.log(this);//window
      })
      
  2. 显示绑定规则:

    • apply

      传参:bar.apply(obj,[ 1 , 2 , 3 ])

    • bind

      传参: bar.bind(obj)(1 , 2 , 3 )

    • call

      传参: bar.call(obj , 1 , 2 , 3)

    • obj为1时,this绑定则为Number 而null和undefined没有包装类,this绑定为window

  3. new绑定:绑定为实例化对象

  4. 优先级:new>显示绑定>隐式绑定>默认绑定

  5. 箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this 。

闭包

  1. 上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放

  2. 闭包的情况

    • 函数作为返回值

    • 函数作为参数被传递

    • 作用域内部变量被外部环境变量所接受

      var c = 1;
      function fn() {
          let a = 10;
          console.log(c);
          c = function () {
              console.log(a);
          }
      }
      fn()
      c()//10
      console.log(a)//a is not defined
      
  3. 闭包的作用

    • 保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其它区域中的变量不会有任何的冲突(防止全局变量污染)

    • 保存:如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文中调取使用(延伸变量的作用范围)

  4. 危害

    • 内存泄漏:闭包产生的变量都会常驻在内存中,对内存的消耗非常大,如果滥用闭包,会造成内存泄漏,降低网页的性能;