zoukankan      html  css  js  c++  java
  • JavaScript学习

    编程有面向过程、面向对象和函数式编程,JavaScript可以使用这3种编程思想。

    现代JS引擎主要包含调用栈和堆,比如chrome和node.js都使用V8引擎:

    JavaScript不是单纯的解释型或编译型语言,而是JIT型。编译是把整个文件翻译成源文件后再执行,可以不立即执行;而解释是翻译一条执行一条,缺点是慢;JIT则是把整个文件翻译完后再立即执行。

    一段代码被送入JS引擎会经历分析、编译、执行3个阶段。在分析阶段,代码被划分成有意义的AST(Abstract Syntax Tree),同时会检查语法错误。
    接着在编译阶段使用生成的AST翻译成机器码,最后在执行阶段执行机器码,在调用栈中执行。这就完了吗?并没有,现代JS引擎有一种优化策略:第一次生成执行的是没有经过优化的版本,这样只是为了更快地运行起来。在执行过程中,还会再次编译生成优化的机器码来替代之前的机器码。

    JavaScript的运行时类似一个盒子,包含了JavaScript运行所需的东西,比如对于浏览器,JavaScript运行时包含JS引擎、Web API以及调用队列。当调用栈为空时,会把调用队列第一个事件入栈,这就构成Event Loop,事件循环是浏览器或Node的一种解决JavaScript单线程运行时不会阻塞的一种机制,也就是异步的原理。

    node.js运行时:

    异步面试题

    下列代码打印的结果是?

    setTimeout(() => console.log(1), 0);
    console.log(2);
    

    不要误以为第一行在0秒后执行就是立即执行,所以先打印1再打印2。

    正确执行是先打印2再打印1.

    事件循环与任务队列

    一段js代码刚开始执行的时候会有一个匿名的主事件在Callback Queue任务队列中,js引擎会去任务队列中取一个事件来执行(js是单线程的,每次只能处理一个事件),在执行这个事件中,如果里面有异步操作(如DOM、ajax、setTimeout),它就会将其丢给WebAPIs执行,且丢过去后js引擎就不管了,继续执行后面的代码。WebAPI执行完异步操作后,会把回调函数的js代码再放到Callback Queue中(异步任务都是有回调函数的,如onClick)。如果回调函数中还有异步任务,则在js引擎执行中还会丢给WebAPI,如此反复,这就是事件循环。

    再看上面的代码,一开始Callback Queue中会有一个主事件,进入js引擎执行,执行到setTimeout时发现这是一个异步任务,于是将其丢给WebAPI来执行,它继续执行后面的代码,WebAPI在0毫秒后就执行完了,接着把回调函数也就是打印1的任务放到任务队列中,但是发现任务队列中还有一个事件没执行完,也就是主事件,等到主事件执行完打印2以后,才轮到执行打印1。

    执行上下文

    代码被编译后,首先会为顶层代码(不包含函数体内部)创建一个全局的执行上下文(global execution context),然后在global EC中执行顶层代码,再然后是在每个函数被调用是创建该函数的执行上下文中,然后在其中执行函数和等待回调函数。所以函数的执行上下文构成了call stack。

    执行上下文里面有什么

    主要包含:

    • Variable Environment
    • Scope chain:能够让函数访问到这个函数外定义的变量,如全局变量。
    • this关键字
      这些都是在creation phase(在执行之前)创建的。

    箭头函数没有argument object和this关键字,他们可以从最近的父函数使用argu object和this。

    js引擎是如何知道函数调用的顺序以及当前所处哪一个函数的上下文?:Call stack

    Scope chain

    注意,只有let和const是block-scoped,var是function-scoped,所以下图中if语句中decade属于if block scope,而millenial属于first() scope。在严格模式下,函数也是block-scoped。

    一个函数可以访问父函数的变量,并不是将父函数的变量复制了过来,而是通过scope chain向上找到的。

    Call stack vs Scope chain

    函数调用顺序不会影响scope chain。

    lexical scoping and dynamic scoping:
    In JavaScript, we have lexical scoping, so the rules of where we can access variables are based on exactly where in the code functions and blocks are written.

    变量提升(Hoisting)

    为什么需要变量提升?这样可以在声明函数之前使用函数,比如相互递归(mutual recursing),也可以让代码变得更可读。
    var的变量提升只是引进let和const变量提升的一个副产物(因为历史包袱不能去掉var),因此var的初始值未undefined,容易引起错误和难以发现的bug,因此尽量不要使用var。

    暂时死区

    为什么需要暂时死区?在变量声明前引用变量是一个不好的行为,应当避免。
    注意下面代码中 console.log(Jonas is a ${job}) 提示的错误是变量未初始化,而不是未定义,因为在执行代码前js引擎已经扫描过一遍代码了,知道job是定义了,只是还未初始化。而 console.log(x) 则是变量未定义。

    console.log(me);
    console.log(job);
    console.log(year);
    
    var me = 'Jonas';
    let job = 'teacher';
    const year = 1991;
    


    // Functions
    console.log(addDecl(2, 3));
    console.log(addExpr(2, 3));
    console.log(addArrow(2, 3));
    
    function addDecl(a, b) {
      return a + b;
    }
    
    const addExpr = function (a, b) {
      return a + b;
    }
    
    const addArrow = (a, b) => a + b;
    



    若将const改成var

    // const addExpr = function (a, b) {
      return a + b;
    }
    
    // const addArrow = (a, b) => a + b;
    
    var addExpr = function (a, b) {
      return a + b;
    }
    
    var addArrow = (a, b) => a + b;
    


    same as:

    console.log(addExpr)值为undefined。

    使用var变量提升的bug例子

    if (!numProduct) deletedAllProducts();
    
    var numProduct = 10;
    
    function deletedAllProducts() {
      console.log('All products deleted!');
    }
    

    即使numProduct不为0,依然会调用deletedAllProducts(),因为undefined的值和0一样,也为false。

    var和const、let的另一个小区别

    var声明变量会在window对象创建一个属性,而const和let不会。

    var x = 1;
    const y = 2;
    let z = 3;
    
    console.log(x === window.x);
    console.log(y === window.y);
    console.log(z === window.z);
    


    this关键字

    • 调用方法时,this指调用方法的那个object
    • 普通调用函数时,this在非严格模式下指window对象,在严格模式下为undefined
    • 箭头函数没有自己this,箭头函数的this指其所在函数的this
    • 事件监听中this指事件处理程序的DOM元素
    'use strict'
    
    console.log(this);
    
    const calcAge = function (birthYear) {
      console.log(2021 - birthYear);
      console.log(this);
    }
    calcAge(1998);
    
    const calcArrow = birthYear => {
      console.log(2021 - birthYear);
      console.log(this);
    }
    calcArrow(1997);
    

    const jonas = {
      year: 1998,
      calcAge: function () {
        console.log(this);
        console.log(2021 - this.year);
      }
    }
    jonas.calcAge();
    
    const matila = {
      year: 2000,
    }
    matila.calcAge = jonas.calcAge;
    matila.calcAge();
    

    PRIMITIVES VS OBJECTS

    let age = 30 : age指向地址0001,值为30;let oldAge = age : oldAge指向地址0001,值为30;age = 31 : 不是直接将地址0001的值改为31,而是开辟新的内存0002,age指向0002。
    primitive类型是存在call stack的执行上下文中的,而object则在heap中。
    heap中的修改并不影响call stack,所以const什么的变量只是call stack的值不能改变,而不是heap中的值。

    头等函数(first-class function) 和高阶函数(higher-order function)

    当一门编程语言的函数可以被当作变量一样用时,则称这门语言拥有头等函数。
    在js中,函数是object,object是value,所以函数也是value。作为value,函数可以当做参数传入另一个函数,也可以被另一个函数返回,还可以赋值给变量或属性。作为object,函数可以有方法和属性。
    头等函数是一个概念,不是实际的一类函数。

    一个函数就可以接收另一个函数作为参数,或者返回值为一个函数,这种函数就称之为高阶函数。作为参数传入的函数称为回调函数(callback function, means not call me now, call me back when something happend, eg. click),

    // 闭包
    在下面的代码中,首先声明了一个函数secureBooking,然后调用了该函数,将其赋值给booker。passengerCount是secureBooking的变量,属于secureBooking的执行上下文。

    因此在执行完const booker = secureBooking()后,secureBooking的执行上下文出栈,这时glocal的执行上下文按照scope chain应该是无法访问passengerCount的,但是后面两次调用book()函数,依然可以修改passengerCount,这就是因为闭包。

    当函数执行时要访问一个自己没有的变量,首先是看闭包,然后才是去看scope chain,也就是说闭包的优先级是高于scope chain的。

    一个函数是有权限访问创造这个函数的执行上下文的变量环境的。

    闭包就先人和家乡的关系,即使人不在家乡,他和家乡的联系也还是在的。或者说闭包就像一个函数带着的背包,里面有创建这个函数的所有变量环境。

    2个例子

    // Example 1
    let f;
    
    const g = function () {
      const a = 23;
      f = function () {
        console.log(a * 2);
      };
    };
    
    g();
    f();
    
    
    // Example 2
    const boardPassengers = function (n, wait) {
      const perGroup = n / 3;
      setTimeout(function () {
        console.log(`We are now boarding all ${n} passengers`);
        console.log(`There are 3 groups, each with ${perGroup} passengers`);
      }, wait * 1000);
      console.log(`Will start boarding in ${wait} seconds`);
    };
    
    const perGroup = 1000;
    boardPassengers(180, 3);
    

    Array方法

  • 相关阅读:
    jquery.cookie.js
    CSS实现三角形
    关于seajs模块化的搭建
    浏览器版本类型及版本
    js || 和 &&
    bootstraps字体图标无法显示
    Thymeleaf的一些操作
    C语言I博客作业02
    C语言I博客作业03
    20169306《网络攻击与防范》第二周学习总结
  • 原文地址:https://www.cnblogs.com/pengweii/p/14342411.html
Copyright © 2011-2022 走看看