js

作者: fanstastic | 来源:发表于2019-12-09 09:55 被阅读0次
    • 旧与新
      当旧版浏览器不支持新特性的时候,主要使用polying and transpilling技术
      polyfilling表示根据新特性的定义,创建一段与之等价但能够在旧浏览器运行的行为

    • transpiling
      语言中新增语法无法进行polyfilling。新语法在旧版浏览器会抛出错误。
      因此需要通过工具将新代码转换成等价的旧版代码。这个过程通常被称为transpiling。它是由transpiling + compiling组成

    var a = (b = 2) => { console.log(b) }
    a(undefined) // 2
    a() // 2
    a(null) // null
    

    语法不能polyfill,但可以transpiling

    模块

    • es6使用基于文件的模块
    • es6的模块是单例,模块只有一个实例,其中维护了它的状态。每次向其他模块导入的时候,得到的是单个实例中心的引用。

    class

    class Foo {
      constructor(a, b) {
        this.x = a
        this.b = b
      }
      gimmeXY() {
        return this.x * this.y
      }
    }
    
    • extends 和 super
      extends用来在两个函数原型之间建立proto委托,在构造器中,super自动指向父构造器,比如super.gimmeXY(), Bar extend Foo 指的是Bar.prototype的_proto关联到Foo.prototype.

    随笔技术

    • seo
      服务端渲染
      直出,在服务器端获取数据并渲染完成页面后给到前端,用户能够直接看到页面,这样既解决了首屏问题(服务器端获取数据速度快)也解决了seo的问题。

    同构,浏览器端会在执行一次,所以要进行一次判断,如果数据已经获取,那么就不要再获取数据。

    在当前作用域之外执行的函数就是闭包,定义时确定作用域,运行时确定this,作用域链是基于调用栈的。

    if (!Function.prototype.bind) {
      Function.prototype.bind = function(oThis) {
        if (typeof this !== 'function') {
            throw new TypeError('')
        }
      }
      let args = Array.prototype.slice.call(arguments, 1),
      fToBind = this, // this指向需要被绑定this的函数
      fNOP = function() {},
      fBound = function() {
        
      }
      
    }
    

    函数是对象的子类型,可以像使用对象一样使用函数,new 出来的实例可以通过instance判断是否是new 出来的实例

    • [[ Prototype ]]
      js的对象中有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。当你试图引用对象属性时会触发[[GET]]操作,对于默认的get来说,第一步是检查对象本身是否有这个属性,如果有就使用它。
      对于默认的get操作来说,如果无法在对象本身找到需要的属性,就会继续访问protorype链,Object.create会创建一个对象并把这个对象的prototype关联到指定的对象。当使用各种语法进行属性查找时都会查找prototype链。
      给一个对象设置属性不仅仅是添加一个新属性或者修改已有属性.
      不存在于自有对象,但是存在prototype中,会有三种情况
      如果在原型链上存在属性,并且它是一个setter,那就一定会调用这个setter,属性不会被添加到对象上。
    1. 如果在prototype链上层存在foo,但是他被标记为只读,那么无法修改已有属性,或者创建屏蔽属性。只读会阻止链下层隐式创建同名属性。这个限制只会存在于=赋值中,使用Object.defineProperty并不会受到影响。
      a++,首先通过原型查找到a,然后再对象自身的属性上创建一个值,原型的值不会发生改变。

    • 原型继承本质是创建一个新的对象并且和原型建立关联

    • 构造函数
      Foo.prototype 默认有一个公有并且不可枚举的属性constructor,这个属性引用的是对象关联的函数。new Foo 创建的对象也有一个constructor,指向创建这个对象的函数。
      实例上的constructor被委托到了原型的constructor,
      原型与原型之间可以通过object.create关联,也可以通过new关联

    • 检查类的关系
      假设有对象a,如何寻找对象a委托的对象呢?检查一个实例的继承祖先通常被称为反射。
      instanceof 操作符的左侧是一个普通对象,右操作数是一个函数。在左操作数的[[prototype]]中是否有指向右操作数的prototype的对象
      Foo.prototype.isPrototypeOf(a) 在a的整条proto中是否出现了Foo.prototype

    • 对象关联
      如果在对象上没有找到需要的属性或者方法,引擎就会继续在proto关联的对象上查找。

    • 创建关联
      使用Object.create不会生成prototype和constructor
      proxy是方法无法找到的行为

    • 比较思维模型

    function Foo(who) {
      this.me = who
    }
    Foo.prototype.identify = function() {
      return 'i am' + this.me
    }
    function Bar(who) {
      Foo.call(this, who)
    }
    Bar.prototype = Object.create(Foo.prototype)
    
    • 事件循环
      js引擎并不是独立运行的,它运行在宿主环境,对多数开发者通常就是web浏览器。
      它们都提供了一种机制处理程序中多个块的执行,且执行时调用js引擎,这种机制被称为事件循环。
      浏览器会侦听网络的响应,拿到数据后,就会把回调函数插入到事件循环
      事件循环是一个用做队列的数组,是一个持续运行的循环,循环的每一轮称为一个tick。对每个tick而言,如果队列中存在等待事件,那么就会从队列中摘下事件并执行。这些事件就是你的回调函数。
      setTimeout并没有把回调函数挂在事件循环队列中。它所做的是设定一个定时器,定时器到时后,环境会把你的回调函数放在事件循环中,这样在未来某个时刻的tick会摘下并执行这个回调。

    • 并行和线程
      事件循环把自身的工作分成一个个任务并顺序执行,不允许对共享内存的访问和修改。通过分立线程中彼此合作的事件循环,并行和顺序执行可以共存。
      js一次只能处理一个事件

    • 任务
      有一个新的概念建立在事件循环队列之上,叫做任务队列,它是挂在事件循环队列的每个tick之后的一个队列。可能出现的异步动作不会导致一个完整的新事件添加到事件循环队列当中,而会在当前tick的任务队列末尾添加一个任务。
      事件循环类似于有一个活动免费领礼物,你领完礼物之后需要排队才能再领。而任务队列类似于领了之后,插队继续领礼物。
      一个任务可能引起更多任务被添加到同一个队列末尾,所以,理论上说,job event可能无限循环。任务处理是在当前tick循环结尾处。
      一旦有事件需要运行,事件循环就会运行,直到队列清空。事件循环的每一轮称为一个tick。用户交互、IO和定时器会向事件队列中加入事件。
      不管then回调返回的是什么,都会被自动设置为promise的完成。

    • 元编程
      元编程是对程序编程的编程
      举例来说,如果想要查看对象a和另一个对象b的关系是否是prototype链接的。用for in循环枚举对象的键,或者检查对象是否是某个类构造器的实例。
      元编程关注以下几点:代码查看自身,代码修改自身,代码修改默认语言特性,以此影响其他代码。
      元编程的目标是利用语言自身的内省能力使代码的其余部分更具描述性、表达性和灵活性。如果函数设定了name值,那么这个值通常就是开发者工具中栈踪迹使用的名称。你的代码在某些情况下可能想要了解自身,想要知道某个函数的名称是什么。

    • 元属性

    • 代理
      代理是一种由你创建的特殊的对象,它封装了另一个普通对象,或者说挡在这个普通对象前面。你可以在代理对象上注册特殊的处理函数,代理上执行各种操作时会调用这个程序。
      你可以在代理上定义的trap处理函数的一个例子是get,当你试图访问对象属性的时候,它拦截get运算。
      在跟踪console.log之后,我们把对obj的操作通过reflect.get转发。每个可用的代理trap都有一个对应的同名Reflect函数即可。
      这里的映射是有意对称的。每个代理处理函数在对应的元编程任务执行的时候进行拦截.
      我们把对obj的操作通过Reflect.get()转发。每个代理处理函数都有一个自动调用相应的Reflect工具的默认定义。

    • 代理局限性
      可以在对象上执行的很广泛的一组基本操作都可以通过这些元编程处理函数trap。但有一些操作是无法拦截的。

    • 可取消代理

    • 使用代理
      通过元编程的proxy,我们可以拦截对对象处理的几乎所有行为。

    1. 代理在先,代理在后
      我们通常可以把代理看作是对目标对象的包装。代理成为了代码交互的主要对象,而实际的目标对象保持被保护的状态。
    • 代理hack[[ prototype ]]链
      [[ prototype ]]机制主要是通过[[ Get ]]运算,当直接对象没有属性时,get会把这个运算交给[[ prototype ]]。

    • Reflect对象就是一个平凡对象
      它持有对应于各种可控的元编程任务的静态函数。这些函数一对一对应着代理可以定义的处理函数方法。
      Reflect和Object的对应工具行为方式类似。但是,如果第一个参数不是对象,Object会试图把它转换成对象。而Reflect则会抛出一个错误。
      Reflect的元编程能力提供了模拟各种语法特性的编程等价事物,把之前隐藏的抽象操作暴露出来。比如,可以利用这些能力扩展功能和API,以实现领域特定语言(DSL)。

    • 属性排序
      对于es6,拥有属性的列出顺序由算法定义,可以顺序只对特斯你给的ownKeys等方法有保证
      其顺序为:

    1. 首先,按照数字上升排序,枚举所有整数索引拥有的属性;
    2. 然后,按照创建顺序枚举其余的拥有的字符串属性名;
    3. 最后,按照创建顺序枚举拥有的符号属性;
    • 尾递归调用
      通常,在一个函数内部调用另一个函数的时候,会分配第二个栈zhen来独立管理第二个函数调用的变量/状态。这个分配不但消耗处理时间,也消耗额外内存。
      当进行递归编程时,调用栈的深度很容易达到成百上千,甚至更多。如果内存的使用无限制增长,将会导致问题。
      有一种称为尾调用的函数调用模式,可以以避免额外栈帧分配的方式进行优化。如果可以避免额外的分配,就没有理由任意限制调用栈的深度。
      尾调用是一个return函数调用的语句,除了调用后返回其返回值之外没有任何其他动作。通过这种方式不需要分配额外的栈帧。引擎不需要对下一个函数调用创建一个新的栈帧。这能够工作是因为一个函数不需要保留任何当前状态。

    • 非tco优化
      把每个部分的结果用一个函数表示,这些函数或者返回另外一个部分结果函数,或者返回最终结果。然后只需要循环直到得到的结果不是函数,得到的就是最终结果。

    function trampoline(res) {
      while(typeof res == 'function') {
        res = res()
      }
      return res
    }
    
    let foo = (() => {
      function _foo(acc, x) {
        if (x <= 1) return acc;
        return () => {
          return _foo((x/2)+acc, x-1)
        }
      }
      return (x) => {
        return trampoline(_foo(1, x))
      }
    })()
    

    这个重写需要最小的改动把递归转化为trampoline中的循环。

    1. 首先,把 return _foo .. 一行封装在·partial表达式中
    2. 然后,把_foo调用封装在trampoline中
    function foo(x) {
      let acc = 1;
      while(x>1) {
        acc = (x/2)+acc
        x--
      }
      return acc
    }
    
    • 元在何处
      可以在运行时判断引擎支持哪些特性。其中就包括TCO,尽管方法十分暴力
    try {
      (function foo(x) {
        if (x < 5E5) return foo(x+1)
      })(1)
    }
    catch (err) {
      
    }
    

    在非TCO引擎中,递归循环最终会失败,抛出一个异常被捕获。
    所以我们可以通过这种特性测试来决定是加载使用递归的应用代码版本,还是转换为不需要递归的版本。

    自适应代码

    function foo(x) {
      function _foo() {
        if (x > 1) {
          acc = acc + (x/2);
          x--
          return _foo()
        }
      }
      var acc=1;
      while(x>1) {
        try {_foo()} catch(err){}  
      }
      return acc
    }
    foo(123456)
    

    这个算法尽可能多的使用了递归,但是是通过作用域内的变量x和acc保持进展状态。如果整个问题都可以不出错的通过递归解决,那么很好。如果引擎在某处杀死了递归,我们就会在catch中捕获到,然后再试一次,继续我们其余的工作。
    这种形式可以看做一种元编程,理由是在运行时探索引擎的能力来递归地完成任务,并可能为引擎局限提供替代版本。

    • 小结
      元编程是指把程序的逻辑转向关注自身,要么是为了查看自己的结构,要么是为了修改它。元编程的主要价值是扩展语言的一般机制来提供额外的新功能。
      从匿名函数到函数名推导,到提供了构造器调用方式这样的信息的元属性,你可以比过去更深入查看程序运行时的结构。通过公开符号可以覆盖原本特性,代理可以拦截并自定义对象的各种底层操作,Reflect提供了工具来模拟它们。

    • 异步函数
      async function 本质上就是生成器+promise+run模式的语法糖
      async function有一个没有解决的问题,因为它只返回一个promise,所以没有办法从外部取消一个正在运行的async function实例。

    相关文章

      网友评论

          本文标题:js

          本文链接:https://www.haomeiwen.com/subject/ueimgctx.html