javascript笔记

157 阅读13分钟

基础总结深入

数据类型的分类和判断

  • 基本(值)类型
    • Number ----- 任意数值 -------- typeof
    • String ----- 任意字符串 ------ typeof
    • Boolean ---- true/false ----- typeof
    • undefined --- undefined ----- typeof/===
    • null -------- null ---------- ===
  • 对象(引用)类型
    • Object ----- typeof/instanceof
    • Array ------ instanceof
    • Function ---- typeof

数据,变量, 内存的理解

  • 什么是数据?
    • 在内存中可读的, 可传递的保存了特定信息的'东东'
    • 一切皆数据, 函数也是数据
    • 在内存中的所有操作的目标: 数据
  • 什么是变量?
    • 在程序运行过程中它的值是允许改变的量
    • 一个变量对应一块小内存, 它的值保存在此内存中
  • 什么是内存?
    • 内存条通电后产生的存储空间(临时的)
    • 一块内存包含2个方面的数据
      • 内部存储的数据
      • 地址值数据
    • 内存空间的分类
      • 栈空间: 全局变量和局部变量
      • 堆空间: 对象
  • 内存,数据, 变量三者之间的关系
    • 内存是容器, 用来存储不同数据
    • 变量是内存的标识, 通过变量我们可以操作(读/写)内存中的数据

函数的理解和使用

  • 什么是函数?
    • 用来实现特定功能的, n条语句的封装体
    • 只有函数类型的数据是可以执行的, 其它的都不可以
  • 为什么要用函数?
    • 提高复用性
    • 便于阅读交流
  • 函数也是对象
    • instanceof Object===true
    • 函数有属性: prototype
    • 函数有方法: call()/apply()
    • 可以添加新的属性/方法
  • 函数的3种不同角色
    • 一般函数 : 直接调用
    • 构造函数 : 通过new调用
    • 对象 : 通过.调用内部的属性/方法
  • 函数中的this
    • 显式指定谁: obj.xxx()
    • 通过call/apply指定谁调用: xxx.call(obj)
    • 不指定谁调用: xxx() : window
    • 回调函数: 看背后是通过谁来调用的: window/其它
  • 匿名函数自调用:
    (function(w, obj){
      //实现代码
    })(window, obj)
    
    • 专业术语为: IIFE (Immediately Invoked Function Expression) 立即调用函数表达式
  • 回调函数的理解
    • 什么函数才是回调函数?
      • 你定义的
      • 你没有调用
      • 但它最终执行了(在一定条件下或某个时刻)
    • 常用的回调函数
      • dom事件回调函数

      • 定时器回调函数

      • ajax请求回调函数(后面讲解)

      • 生命周期回调函数(后面讲解)

函数高级

原型与原型链

原型

Object 的隐形原型指向null。 Funciton的显示原型和隐形原型指向都一样 image.png image.png

显示原型和隐形原型
  • 显示原型(所有函数都有一个特别的属性):
    • 每个函数都有一个prototype属性(显示原型), 它默认指向一个Object空对象(即称为: 原型对象)
    • 原型对象中有一个属性constructor, 它指向函数对象
    • 给原型对象添加属性(一般都是方法),作用: 函数的所有实例对象自动拥有原型中的属性(方法)
  • 所有实例对象都有一个特别的属性:
  • __proto__ : 隐式原型属性,作用:查找对象的属性(方法)
显式原型与隐式原型的关系
  • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
  • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
  • 对象的隐式原型的值为其对应构造函数的显式原型的值
显示原型和隐形原型关系总结
  • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

原型链

对象查找属性的流程:
  • 先在自身属性中查找,找到返回
  • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
  • 如果最终没找到, 返回undefined
原型链
  • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
  • 这样通过__proto__属性就形成了一个链的结构---->原型链
  • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
  • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

执行上下文与执行上下文栈

变量提升与函数提升

  • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
  • 函数提升: 通过function声明的函数, 在函数定义语句之前就可以直接调用
  • 先有变量提升, 再有函数提升

执行上下文和执行上下文栈

  • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性

    执行一个函数就会产生一个函数执行上下文,譬如调用b(),就产生了一个函数执行上下文

  • 执行上下文栈: 用来管理产生的多个执行上下文

    1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
    2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
    3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
    4. 在当前函数执行完后,将栈顶的对象移除(出栈)
    5. 当所有的代码执行完后, 栈中只剩下window

分类:

  • 全局执行上下文: window
    • 在执行全局代码前将window确定为全局执行上下文
    • 对全局数据进行预处理
      1. var定义的全局变量==>undefined, 添加为window的属性
      2. function声明的全局函数==>赋值(fun), 添加为window的方法
      3. this==>赋值(window)
    • 开始执行全局代码
  • 函数执行上下文: 对程序员来说是透明的
    • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
    • 对局部数据进行预处理
      1. 形参变量==>赋值(实参)==>添加为执行上下文的属性
      2. arguments==>赋值(实参列表), 添加为执行上下文的属性
      3. var定义的局部变量==>undefined, 添加为执行上下文的属性
      4. function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
      5. this==>赋值(调用函数的对象)
    • 开始执行函数体代码

生命周期

  • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
  • 函数 : 调用函数时产生, 函数执行完时死亡

执行上下文创建和初始化的过程

  • 全局:
    • 在全局代码执行前最先创建一个全局执行上下文(window)
    • 收集一些全局变量, 并初始化
    • 将这些变量设置为window的属性
  • 函数:
    • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
    • 收集一些局部变量, 并初始化
    • 将这些变量设置为执行上下文的属性

作用域与作用域链

作用域的定义

  • 就是一块"地盘", 一个代码段所在的区域
  • 它是静态的(相对于上下文对象), 在编写代码时就确定了
  • 作用: 隔离变量,不同作用域下同名变量不会有冲突
  • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
  • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量

. 查找一个变量的查找规则

  1. 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  2. 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  3. 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

作用域和执行上下文的区别

  1. 区别1
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  2. 区别2
    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  3. 联系
    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域
  4. 总结他们的关系
    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

分类:

  • 全局
  • 函数
  • js没有块作用域(在ES6之前)

作用

  • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突

  • 作用域链: 查找变量

闭包

概念

  • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包(外部函数必须被调用)
  • 通过chrome工具得知: 闭包本质是内部函数中的一个对象Closure, 这个对象中包含引用的变量属性

作用:

  • 延长局部变量的生命周期
  • 让函数外部能操作内部的局部变量

闭包应用:

  • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
  • 循环遍历加监听
  • JS框架(jQuery)大量使用了闭包

缺点:

  • 变量占用内存的时间可能会过长
  • 可能导致内存泄露
  • 解决:
    • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

内存溢出

  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误

内存泄露

  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包

对象高级

对象的创建模式

  • Object构造函数模式
    var obj = {};
    obj.name = 'Tom'
    obj.setName = function(name){this.name=name}
    
  • 对象字面量模式
    var obj = {
      name : 'Tom',
      setName : function(name){this.name = name}
    }
    
  • 构造函数模式
    function Person(name, age) {
      this.name = name;
      this.age = age;
      this.setName = function(name){this.name=name;};
    }
    new Person('tom', 12);
    
  • 构造函数+原型的组合模式
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.setName = function(name){this.name=name;};
    new Person('tom', 12);
    

对象继承模式

  • 原型链继承 : 得到方法
function Parent(){}
Parent.prototype.test = function(){};
function Child(){}
Child.prototype = new Parent(); // 子类型的原型指向父类型实例
Child.prototype.constructor = Child
var child = new Child(); //有test()
  • 借用构造函数 : 得到属性
    function Parent(xxx){this.xxx = xxx}
    Parent.prototype.test = function(){};
    function Child(xxx,yyy){
        Parent.call(this, xxx);//借用构造函数   this.Parent(xxx)
    }
    var child = new Child('a', 'b');  //child.xxx为'a', 但child没有test()
    
  • 组合
    function Parent(xxx){this.xxx = xxx}
    Parent.prototype.test = function(){};
    function Child(xxx,yyy){
        Parent.call(this, xxx);//借用构造函数   this.Parent(xxx)
    }
    Child.prototype = new Parent(); //得到test()
    var child = new Child(); //child.xxx为'a', 也有test()
    
  • new一个对象背后做了些什么?
  • 创建一个空对象
  • 给对象设置__proto__, 值为构造函数对象的prototype属性值 this.proto = Fn.prototype
  • 执行构造函数体(给对象添加属性/方法)

线程机制与事件机制

线程与进程

  • 进程:
    • 程序的一次执行, 它占有一片独有的内存空间
    • 可以通过windows任务管理器查看进程
  • 线程:
    • 是进程内的一个独立执行单元
    • 是程序执行的一个完整流程
    • 是CPU的最小的调度单元
  • 关系
    • 一个进程至少有一个线程(主)
    • 程序是在某个进程中的某个线程执行的
    • 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
    • 一个进程内的数据可以供其中的多个线程直接共享
    • 多个进程之间的数据是不能直接共享的 *. 浏览器运行是单进程还是多进程?
    • 有的是单进程
      • firefox
      • 老版IE
    • 有的是多进程
      • chrome
      • 新版IE *. 如何查看浏览器是否是多进程运行的呢?
    • 任务管理器==>进程 *. 浏览器运行是单线程还是多线程?
    • 都是多线程运行的

浏览器内核模块组成

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

js线程

  • js是单线程执行的(回调函数也是在主线程)
  • H5提出了实现多线程的方案: Web Workers
  • 只能是主线程更新界面

注意事项 分线程中的定时器模块、Dom时间模块、网络请求模块都是在主进程中执行的。他们只有初始化代码执行完之后才有可能调用,所以如果在主线程执行了一个长时间的操作, 可能导致延时才处理

代码的分类:

  • 初始化代码
  • 回调代码

js引擎执行代码的基本流程

  • 先执行初始化代码: 包含一些特别的代码 回调函数(异步执行)
    • 设置定时器
    • 绑定事件监听
    • 发送ajax请求
  • 后面在某个时刻才会执行回调代码

事件处理机制

image.png

  • 代码分类
    • 初始化执行代码: 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
    • 回调执行代码: 处理回调逻辑
  • js引擎执行代码的基本流程:
    • 初始化代码===>回调代码
  • 模型的2个重要组成部分:
    • 事件管理模块
    • 回调队列
  • 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行

H5 Web Workers

  • 可以让js在分线程执行
  • Worker
    var worker = new Worker('worker.js');
    worker.onMessage = function(event){event.data} : 用来接收另一个线程发送过来的数据的回调
    worker.postMessage(data1) : 向另一个线程发送数据
    
  • 问题:
    • worker内代码不能操作DOM更新UI
    • 不是每个浏览器都支持这个新特性
    • 不能跨域加载JS