JavaScript学习

226 阅读17分钟

JavaScript基础+高级(李立超2016)

  • JS简介

    • 语言发展:纸带机-汇编语言-现代语言

    • JS起源:诞生于95年,主要是处理网页中的前端验证

      • 检查用户输入内容是否符合一定的规则
      • 用户名的长度、密码的长度、邮箱的格式
    • Netscape:LiveScript

    • Sun:Java-JavaScript

    • Microsoft:JScript

    • ECMAScript:简称ES,几个公司形成一个组织发布JS标准

    • Chome浏览器,使用V8引擎实现JavaScript

    • JavaScript:

      • ECMAScript
      • DOM
      • BOM
    • 特点:

      • 解释性语言(不用编译,写完直接运行)
      • 动态语言(弱类型语言)
      • 基于原型的面向对象
  • HelloWorld

    • 在script标签中编写
    • 浏览器窗口弹出:alert(’HelloWorld’)
    • 注释 //
    • 页面中输出:document.write(’HelloWorld’)
    • 控制台输出:console.log(’HelloWorld’)
  • JS编写位置

    • 元素结构中:JS代码可以写到标签的onclick属性中(不推荐)
    • script标签中(练习用)
    • 外部JS文件中,通过script标签引入(开发用)
    • JS代码是按顺序从上到下执行的
  • 基本语法

    • 注释:单行注释和多行注释 // /* */,可以通过注释对代码进行一些简单的调试
    • JS中严格区分大小写
    • JS中每一条语句以分号结尾(不写其实也行,但可能出现小问题)
    • JS中会忽略多个空格和换行,可以利用空格和换行对代码进行格式化美化
  • 字面量和变量

    • 字面量:都是不可改变的值

      • 例如12345
      • 字面量可以直接使用,但一般不会直接使用,而是存入变量中
    • 变量:保存字面量,变量的值可以任意改变

      • 变量方便使用,开发中用变量保存字面量
    • 声明变量:

      • var 有变量提升
      • let 没有变量提升,有块级作用域,声明的变量可更改
      • const 没有变量提升,有块级作用域,声明的变量不可更改且声明时必须有初始值
  • 标识符

    • JS中所有可以由我们自主命名的都可以称为是标识符

    • 变量名、函数名、属性名

    • 需要遵守一定的规则

      • 标识符可以函数字母、数字、_、$

      • 标识符不能以数字开头

      • 标识符不能是ES中的关键字或保留字

      • 标识符一般都采用驼峰命名法

        • 小驼峰:首字母小写,后续单词首字母大写
        • 大驼峰:所有单词首字母都大写
      • JS底层保存标识符时实际上采用的Unicode编码

  • 数据类型(基本数据类型和引用数据类型)

    • String字符串

      • 在JS中字符串需要使用引号引起来
      • 相同类型引号不同嵌套,但是单双引号可以嵌套
      • 字符串中可以使用\作为转义字符,表示一些特殊符号
      • \n 换行 \t tab键 \’ \” \
    • Number数值

    • Boolean布尔值

    • Null空值

    • Undefined未定义

    • Object对象

    • Symbol

    • BigInt

  • 162、变量提升与函数提升

    1、通过var定义的变量,在定义语句前可以访问到

    2、通过function声明的函数,在之前可以直接调用

  • 163、执行上下文

    1、全局代码和局部代码

    2、全局执行上下文(预处理操作)

    • 执行全局代码前将window确定为全局执行上下文
    • var定义的全局变量添加为window的属性
    • function声明的全局函数添加为window的方法
    • this为window
    • 开始执行全局代码

    3、函数执行上下文(函数要执行才会创建空间,预处理操作)

    • 执行函数体之前,创建对应的函数执行上下文对象(虚拟封闭的区域,存在于栈中)
    • 形参变量,赋值实参,添加为执行上下文的属性
    • arguments,赋值实参列表,添加为执行上下文的属性
    • var定义的局部变量undefined,添加为执行上下文的属性
    • function声明的函数,复制,添加为执行上下文的方法
    • this,赋值为调用函数的对象
  • 164、执行上下文栈

    • 定义函数不产生,调用执行函数时产生执行上下文
    • 1、全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象(后进先出)
    • 2、在全局执行上下文window确定后,将其添加到栈中(压栈)
    • 3、在函数执行上下文创建后,将其添加到栈中(压栈)
    • 4、在当前函数执行完后,将栈顶的对象移除(出栈)
    • 5、当所有代码执行完后,栈中只剩下window
  • 165、题目练习

    1、依次输出什么,产生了几个执行上下文?

    • 递归调用:在函数内部调用自己
    • 栈内有5个执行上下文,依次弹出的过程中又打印了一次

    2、先执行变量提升,再执行函数提升

    3、undefined

    4、报错,c被赋值为1,不能作为函数执行

  • 166、复习

    • 原型与原型链

      • 原型prototype

        • 原型对象,是对象Object的实例,即执行了this.prototype = {}
        • 执行函数定义和执行函数
      • 显式原型和隐式原型

        • 构造函数的prototype是显式原型
        • 实例的__proto__是隐式原型(this.proto === Fn.prototype)
        • 两者相等或指向同一个对象
      • 原型链

        • 隐式原型指向的链
        • 帮助我们查找对象的属性和方法
        • 构造函数/原型/实例对象的关系
        • 原型继承
      • instanceof

        • 实例和构造函数之间的关系
        • 沿着原型链查找,判断并返回布尔值
    • 执行上下文与执行上下文栈

      • 变量提升与函数提升

        • 变量先提升,函数再提升(变量声明在上面,函数声明在下面)
      • 执行上下文(预处理)

        • 全局执行上下文和函数执行上下文
        • 收集全局变量,函数和this
        • 收集形参和局部变量,函数,this和arguments
      • 执行上下文栈

        • 管理和保存执行上下文对象
        • 后进先出
      • 练习题

        • 有一些迷惑性
  • 167、作用域与作用域链

    • 作用域

      • 1、一块地盘,代码所在的区域,编码时就确定了
      • 2、全局作用域和函数作用域(ES6:块作用域)
      • 3、跟执行上下文一样也是n+1个(n指函数,1指全局)
      • 4、隔离变量,不同作用域下同名变量不会有冲突
    • 作用域与执行上下文

      1、函数作用域在函数定义时就已确定;执行上下文是调用函数时代码执行前确定

      2、作用域是静态的,定义好就存在;执行上下文是动态的,调用函数存在

    • 作用域链

      • 嵌套的作用域由内向外的过程
  • 168、练习

    • 全局中定义一个函数,将其传入另一个函数中执行,查找变量查找的是全局中的变量
    • 作用域在定义函数时就确定了,而不是在调用函数时确定,所以查找变量是根据定义函数时确定的作用域去查找
  • 169、闭包

    • 两大神兽:原型和闭包
    • 案例:遍历按钮,定义数组变量只计算一次,按钮绑定点击函数,var定义的绑定回调函数执行时for循环已经执行完毕;
    • 解决方法:代码用括号括起来作为立即执行函数,传入i作为参数;循环遍历+监听,用闭包实现;ES6中用let定义i,形成块级作用域
  • 170、理解闭包(closure)

    • 一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包

    • 使用chrome调试查看

    • 闭包是嵌套的内部函数(大多数人,理解一)

    • 闭包是包含被引用变量的对象(极少数人,理解二)

    • 闭包存在于嵌套的内部函数中

    • 产生闭包的条件:

      • 1、函数嵌套
      • 2、内部函数引用了外部函数的数据,执行了外部函数(产生外部函数数据)(变量/函数)
      • 3、执行函数定义就会产生闭包(不用调用内部函数)
  • 171、常见的闭包

    • 1、将内部函数作为外部函数的返回值
    • 调用栈:执行上下文栈,函数定义早已执行,
    • 2、将函数作为实参传递给另一个函数调用
  • 172、闭包的作用

    • 1、使局部变量在函数执行完后,仍然存在于内存中(延长局部变量的生命周期)
    • 2、让函数外部可以操作到函数内部的数据
    • return返回值保存给变量,变量指向了函数对象
    • 函数外部不能直接访问函数内部局部变量,但是通过闭包可以操作
  • 173、闭包的生命周期

    • 1、产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
    • 2、死亡:包含闭包的函数对象称为垃圾对象f=null
  • 174、闭包的应用

    • JS模块,具有特定功能的JS文件,将所有数据和功能封装在一个函数内部(私有的)
    • 1、私有数据
    • 2、操作数据的函数
    • 3、向外暴露对象(给外部使用的方法)
    • window.module = { 方法 }
  • 175、闭包的缺点和解决

    • 缺点:

      • 函数执行完内部的局部变量没有释放,占用内存时间会变长
      • 容易造成内存泄漏(内存被白白占用,用不上)
    • 解决:

      • 能不用闭包就不用
      • 及时释放f=null(让内部函数成为垃圾对象,浏览器回收闭包)
    • 内存溢出和内存泄漏

      • 内存溢出:程序运行时出现的错误

        • 程序运行需要的内存超过了剩余内存,就会抛出内存溢出的错误、
      • 内存泄漏:占用的内存没有及时释放

        • 内存泄漏积累多了就容易导致内存溢出
        • 常见的内存泄漏:意外的全局变量;没有及时清理的计时器或回调函数;闭包
  • 176、练习

    • var that = this
    • 函数嵌套
    • 内部函数引用了外部函数的变量
    • 调用外部函数才能产生闭包
  • 177、对象创建模式

    • Object构造函数模式:

      • 先创建空Object对象,再动态创建属性和方法(不确定对象内部数据,但是语句太多)
    • 对象字面量模式:

      • 使用{}创建对象,同时指定属性和方法(对象内部数据确定,但如果创建多个对象,有重复代码)
    • 工厂模式:(不用)

      • 通过工厂函数动态创建对象并返回(传入参数,可以创建多个对象,不能区分具体类型,都是Object类型)
    • 自定义构造函数模式

      • 自定义构造函数,通过new创建对象(传入参数,可以创建多个对象,但每个对象都有相同数据,浪费内存(方法,引用数据))
    • 构造函数+原型的组合模式

      • 自定义构造函数,属性再构造函数中初始化,方法添加到原型上prototype(可以创建多个类型确定的对象,且不浪费内存,原型上是同样的方法)
  • 178+179、继承模式

    • 原型链继承

      • 定义父类型构造函数function Supper() {}
      • 定义子类型构造函数function Sub() {}
      • 父类型添加属性,原型上添加方法
      • 子类型添加属性,原型上添加方法
      • 子类型创建实例对象
      • 子类型的原型对象成为父类型的实例(Sub.prototype = new Supper())
      • 这样,子类型的实例就能访问父类型的原型方法
      • 让子类型的原型constructor指向子类型,这样才完整(修正)
      • Sub.prototype.constructor = Sub
    • 借用构造函数继承(假的)

      • 定义父类型构造函数Person () {}
      • 定义子类型构造函数Student() {}
      • 在子类型构造函数中调用父类型构造Person.call(this, name, age)
    • 组合继承(真正使用的)

      • 原型链+借用构造函数的组合继承
      • 1、利用原型链实现对父类型对象的方法继承
      • 2、利用call()实现借用父类型构建函数初始化相同属性
  • 180、JS高级复习

    • 作用域与作用域链

      • 作用域:代码定义时即确定了,和调用执行没有关系
      • 作用域:代码区域:全局作用域和函数作用域。隔离变量,避免命名污染。
      • 作用域嵌套,函数中嵌套函数
      • 作用域链:查找变量,由内向外。window.a返回undefined,直接找a报错。n+1个作用域
      • 执行上下文:调用函数时产生
    • 闭包

      • 循环遍历监听事件,按钮属性引用函数,闭包一直未释放

      • 函数嵌套

      • 内部函数引用外部函数变量(数据)

      • 闭包本质上是包含引用变量的对象(极少数人)

      • 外部函数调用多少次就有多少个闭包,作用:闭包延长变量生命周期;外部操作内部变量

      • 常见闭包

        • 将函数作为另一个函数的返回值:return出去
        • 将函数作为实参传递给另一个函数调用
      • 闭包产生:嵌套内部函数定义代码执行完后就产生了(不是调用);闭包死亡:嵌套的内部函数成为垃圾对象时(f = null)

      • 应用:自定义JS模块(JS文件)

        • 特定功能的js文件
        • 所有数据和功能都封装在一个函数内部(私有的)
        • 只向外暴露一个包含n个方法的对象或函数
        • 模块使用者,通过模块暴露的对象调用方法来实现对应的功能
      • 内存泄漏和内存溢出

        • 内存溢出:不断创建数据,当运行需要的内存超过了剩余内存,就会抛出内存溢出的错误
        • 内存泄漏:占用的内存没有及时释放;内存泄漏积累多了就容易导致内存溢出
    • 对象高级

      • 对象的创建模式

        • Object构造函数模式const obj = {}
        • 对象字面量模式const obj = {name: ‘Tom’}
        • 构造函数模式function Person(name, age) {}
        • 构造函数+原型的组合模式:方法定义到原型对象上
      • 继承模式

        • 原型链继承:得到方法:在原型对象上定义方法

        • 借用构造函数:得到属性:this.xxx = xxx

        • 原型链+借用构造函数组合模式:得到属性+得到方法

        • new一个对象后面做了什么:

          • 创建一个空对象
          • 给对象设置__proto__,值为构造函数的prototype
          • 执行构造函数体(给空对象添加属性和方法)
  • 181、闭包练习

    • 太绕了,恶心,返回的对象中的方法,n引用外部函数的变量,要么一直没变,即没有变量接收并执行方法;要么变量接收了并执行方法传入参数之后,n就变了
  • 182、线程机制

    • 进程与线程:

      • 进程process:程序的一次执行,占有一片独有的内存空间,可以通过任务管理器查看
      • 线程thread:进程内的一个独立执行单元,是程序执行的一个完整流程,是CPU最小的调度单元
      • 一个程序可以是单进程或多进程,一个进程可以是单线程或多线程的
      • 应用程序必须运行在某个进程的某个线程上
      • 一个进程中至少有一个运行的线程,主线程,进程启动后自动创建
      • 一个进程内的数据可以供多个线程共享
      • 多个进程间数据不能直接共享
      • 线程池thread pool:保存多个线程对象的容器,实现线程对象的反复利用
      • 多进程与多线程?比较单线程与多线程?
      • JS是单线程运行,但是H5中Web Workers可以多线程运行
      • H5包含html、css和js中的新语法
      • 浏览器是多线程运行的
      • 浏览器有单进程的firefox,也有多进程的chrome和新版IE
  • 183、浏览器内核

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

    • 不同浏览器内核可能不一样

      • webkit - Chrome/Safari
      • Gecko - firefox
      • Trident - IE
      • 国内:Trident+webkit
    • 内核由很多模块组成

      • js引擎模块:负责js程序的编译与运行
      • html、css文档解析模块:负责页面文本的解析
      • DOM/CSS模块:负责dom/css在内存中的相关处理
      • 布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
      • 以上都是主线程
      • 以下是分线程
      • 定时器模块:负责定时器的管理setInterval()
      • DOM事件响应模块:负责事件的管理
      • 网络请求模块:负责ajax请求
  • 184、定时器引发的思考

    • 定时器真的是定时执行的吗?

      • 一般会延迟一丁点(可以接受),如果有长时间工作,可能延迟长时间(不能接受)
    • 定时器回调函数是在主线程执行的,js是单线程的

    • 定时器是在事件循环模型

    • 定时器并不是真正定时

  • 185、JS是单线程的

    • alert暂停当前主线程执行,也暂停了计时器的计时,点击确定后恢复程序执行和计时

    • 代码的分类:

      • 初始化代码

      • 回调代码

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

        • 设置定时器(到点后执行函数)
        • 绑定事件监听(事件触发后执行函数)
        • 发送ajax请求
      • 后面在某个时刻才执行回调代码

      • 回调函数异步执行(初始化代码执行完后才执行)

    • 为什么js要用单线程模式而不是多线程模式

      • 与它的用途有关
      • 作为浏览器脚本语言,js的主要用途是与用户互动以及操作DOM
      • 如果多线程,某个线程操作了p元素,那么其他线程不允许操作p元素,这个事情会很麻烦,而且代码会复杂很多
      • 因此js只能是单线程,要有先后顺序
  • 186、事件循环

    • 三部分:JS堆和调用栈(JS主线程)、WebAPIs(DOM/ajax/setTimeout)(分线程执行,浏览器负责)、回调队列(回调函数待执行)
    • 事件轮询模型
    • 初始化代码执行完后,才会去回调队列中执行待处理的回调函数,JS是单线程的
    • 执行栈、浏览器内核、任务队列、消息队列、事件队列、事件轮询、事件驱动模型、请求响应模型(浏览器和服务器)
  • 187、Web Workers

    • 斐波那契数列 f(n) = f(n-1) + f(n-2)

    • 递归调用:函数内部调用自己return n ≤ 2 ? 1 : f(n-1) + f(n-2)

    • 递归效率较低,主线程不断计算,页面无法操作

    • webworkers是H5提供的js多线程解决方案

    • 将计算量大的代码交给webworkers运行而不冻结用户界面

    • 主线程代码

    • 创建worker对象

    • const worker = new Worker(’worker.js’)

    • 向分线程发送消息

    • worker.postMessage(number)

    • 接收worker传过来的数据函数

    • worker.onmessage = function(event) { alert(event.data) }

    • 分线程代码(做长时间的计算)

    • const onmessage = function(event) { const number = event.data; const result = f(number); postMessage(result) }

    • 分线程中看不到window,因此不能用window上的alert等方法

    • 缺点:

      • 不能跨域加载JS
      • 不是每个浏览器都支持这个新特性
      • worker内代码不能访问DOM(更新UI)
    • 虚拟路径,域名

  • 188、复习