基础总结深入
数据类型
- 分类
- 基本(值)类型
- String:任意字符串
- Number:任意数字
- boolean:true/false
- undefined:undefined
- null:null
- 对象(引用)类型
- Object:任意对象
- Function:一种特别的对象(可执行)
- Array:一种特别的对象(数值下标,内部数据是有序的)
- 基本(值)类型
- 判断
- typeof:
- 可以判断:undefined/数值/字符串/布尔值/function
- 不能判断:null与object、array(都返回object)
- instanceof:判断对象的具体类型
- ===
- 可以判断:undefined,null
- typeof:
相关问题
- undefined与null 的区别?
- undefined代表定义未赋值
- null定义并赋值了,只是值为null
- 什么时候给变量赋值为null?
- 初始赋值为null,表明将要赋值为对象
- 结束前,让对象成为垃圾对象(被垃圾回收期回收)
- 严格区别变量类型与数据类型?
- 数据的类型
- 基本类型
- 对象类型
- 变量的类型(变量内存值的类型)
- 基本类型:保存的就是基本类型的数据
- 引用类型:保存的是地址值
- 数据的类型
数据,变量与内存
- 什么是数据?
- 存储在内存中代表特定信息的‘东东’,本质上是010101…
- 数据的特点:可传递,可运算
- 一切皆数据
- 内存中所有操作的目标:数据
- 算术运算
- 逻辑运算
- 赋值
- 运行函数
- 什么是内存?
- 内存条通电后产生的可存储数据的空间(临时的)
- 内存产生和死亡:内存条(电路板)==> 通电 ==> 产生内存空间 ==> 存储数据 ==> 处理数据 ==> 断电 ==> 内存空间和数据都消失
- 一块小内存的2个数据
- 内部存储的数据
- 地址值
- 内存分类
- 栈:全局变量/局部变量
- 堆:对象
- 什么是变量?
- 可变化的量,由变量名和变量值组成
- 每个变量都对应的一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据
- 内存,数据,变量三者之间的关系
- 内存用来存储数据的空间
- 变量是内存的标识
相关问题
- 关于赋值与内存的问题?
- 问题:var a = xxx,a内存中到底保存的是什么?
- xxx是基本数据,保存的就是这个数据
- xxx是对象,保存的是对象的地址值
- xxx是一个变量,保存的xxx的内存内容(可能是基本数据,也可能是地址值)
- 问题:var a = xxx,a内存中到底保存的是什么?
- 关于引用变量赋值问题?
- 2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据
- 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一引用变量依然指向前一个对象
- 关于数据传递问题?
- 问题:在js调用函数时传递变量参数时,是值传递还是引用传递
- 理解1:都是值(基本/地址值)传递
- 理解2:可能是值传递,也可能是引用传递(地址值)
- 问题:在js调用函数时传递变量参数时,是值传递还是引用传递
- js引擎如何管理内存?
- 内存生命周期
- 分配小内存空间,得到它的使用权
- 存储数据,可以反复进行操作
- 释放小内存空间
- 释放内存
- 局部变量:函数执行完自动释放
- 对象:成为垃圾对象==>垃圾回收器回收
- 内存生命周期
对象
- 什么是对象?
- 代表现实中的某个事物,是该事物在编程中的抽象
- 多个数据的集合体(封装体)
- 用于保存多个数据的容器
- 为什么要用对象?
- 便于对多个数据进行统一管理
- 对象的组成
- 属性
- 代表现实事物的状态数据
- 由属性名和属性值组成
- 属性名都是字符串类型,属性值是任意类型
- 方法
- 代表现实事物的行为数据
- 是特别的属性===>属性值是函数
- 属性
- 如何访问对象内部数据?
- .属性名:编码简单,但有时不能用
- [‘属性名’]:编码麻烦,但通用
- 问题:什么时候必须使用[‘属性名’]的方式?
- 属性名包含特殊字符:- 空格
- 变量名不确定
函数
- 什么是函数?
- 实现特定功能的n条语句的封装体
- 只有函数是可以执行的,其他类型的数据不能执行
- 为什么要用函数?
- 提高代码复用
- 便于阅读交流
- 如何定义函数?
- 函数声明
- 表达式
- 如何调用(执行)函数?
- test():直接调用
- obj.test():通过对象调用
- new test():new调用
- test.call/apply(obj):临时让test成为obj的方法进行调用
回调函数
- 什么函数才是回调函数?
- 你定义的
- 你没有调
- 但它最终执行了
- 常见的回调函数?
- dom事件回调函数
- 定时器回调函数
- Ajax请求回调函数
- 生命周期回调函数
IIFE
- 理解
- 全称:Immediately-Invoked Function Expression
- 作用
- 隐藏实现
- 不会污染外部(全局)空间
- 用它来编码js模块
this
- this是什么?
- 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 它的值时调用函数的当前对象
- 如何确定this的值?
- test():window
- p.test():p
- new test():新创建的对象
- p.call(obj):obj
语句分号问题
- js一条语句后可以不加分号
- 是否加分号是编码风格问题,没有应该不应该,只有喜欢不喜欢
- 在下面2种情况下不加分号会有问题
- 小括号开头的前一条语句
- 中方括号开头的前一条语句
- 解决办法:在行首加分号
- 强有力的例子:vue.js库
函数高级
原型与原型链
原型(prototype)
- 函数的prototype属性
- 每个函数都有一个prototype属性,它默认指向一个object空对象(即:原型对象)
- 原型对象种有一个属性constructor,它指向函数对象
- 给原型对象添加属性(一般都是方法)
- 作用:函数的所有实例对象自动拥有原型中的属性(方法)
显式原型与隐式原型
- 每个函数function都有一个prototype,即显式原型
- 每个实例对象都有一个__proto__,可称为隐式原型
- 对象的隐式原型的值为其对应构造函数的显式原型的值
- 内存结构
- 总结:
- 函数的prototype属性:在定义函数时自动添加的,默认值是一空的object对象
- 对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
- 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)
原型链
- 原型链
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有,再沿着__proto__这条链向上查找,找到返回
- 如果最终没找到,返回undefined
- 别名:隐式原型链
- 作用:查找对象的属性(方法)
- 注:
- 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
- 所有函数都是Function的实例(包含Function)
- Object的原型对象是原型链尽头
- 访问一个对象的属性时,
- 构造函数/原型/实体对象的关系1
- 构造函数/原型/实体对象的关系2
属性问题
- 读取对象的属性值时:会自动到原型链中查找
- 设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
- 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
探索instanceof
- instanceof是如何判断的?
- 表达式:A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
- Function是通过new自己产生的实例
执行上下文与执行上下文栈
变量提升与函数提升
- 变量声明提升
- 通过var定义(声明)的变量,在定义语句之前就可以访问到
- 值:undefined
- 函数声明提升
- 通过function声明的函数(function xxx(){…}),在之前就可以直接调用
- 值:函数定义(对象)
- 问题:变量提升和函数提升是如何产生的?
执行上下文
- 代码分类(位置)
- 全局代码
- 函数(局部)代码
- 全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量===>undefined,添加为window的属性
- function声明的全局函数===>赋值(fun),添加为window的方法
- this===>赋值(window)
- 开始执行全局代码
- 函数执行上下文
- 在调用函数,准备执行函数之前,创建对应的函数上下文对象(虚拟的,存在于栈中)
- 对局部数据进行预处理
- 形参变量===> 赋值(实参) ===>添加为执行上下文的属性
- argument===> 赋值(实参列表),添加为执行上下文的属性
- var定义的局部变量===>undefined,添加为执行上下文的属性
- function声明的函数===>赋值(fun),添加为执行上下文的方法
- this===>赋值(调用函数的对象)
执行上下文栈
- 在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后,栈中只剩下window
作用域与作用域链
作用域
- 理解
- 就是一块“地盘”,一个代码段所在的区域
- 它是静态的(相对于上下文对象),在编写代码时就确定了
- 分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
- 作用
- 隔离变量,不同作用域下同名变量不会有冲突
作用域链
- 理解
- 多个上下级关系的作用域形成的链,它的方向是从下向上(从内到外)的
- 查找变量时就是沿着作用域链来查找的
- 查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
- 在 上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
- 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
闭包
闭包理解
- 如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量/函数时,就产生了闭包
- 闭包是什么?
- 使用chrome调试查看
- 理解一:闭包是嵌套的内部函数
- 理解二:包含被引用变量(函数)的对象
- 注意:闭包存在于嵌套的内部函数中
- 产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
常见的闭包
- 将函数作为另一个函数的返回值
- 将函数作为实参传递给另一个函数调用
闭包的作用
- 使用函数内部的变量在函数执行完成后,仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
- 问题:
- 函数执行完后,函数内部声明的局部变量是否还存在?——一般是不存在,存在于闭包中(定义变量接受函数返回值)的变量才可能存在
- 在函数外部能直接访问函数内部的局部变量吗?——不能,但是我们可以通过闭包让外部操作它
闭包的生命周期
- 产生:在嵌套内部函数定义执行完成时就产生了(不是在调用)
- 死亡:在嵌套的内部函数成为垃圾对象时
闭包应用-自定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个页面内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者只需要通过模块暴露的对象调用方法来实现对应的功能
闭包的缺点与解决
- 缺点
- 函数执行完后,函数内部的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄露
- 解决
- 能不用闭包就不用
- 及时释放
内存溢出与内存泄漏
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误
- 内存泄漏
- 占用的内存没有及时释放
- 内存泄漏积累多了就容易导致内存溢出
- 常见的内存泄漏:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
面向对象高级
对象创建模式
方式一:Object构造函数模式
- 套路:先创建空Object对象,再动态添加属性/方法
- 适用场景:起始时不确定对象内部数据
- 问题:语句太多
方式二:对象字面量模式
- 套路:适用{}创建对象,同时指定属性/方法
- 适用场景:起始时对象内部数据是确定的
- 问题:如果创建多个对象,有重复代码‘
方式三:工厂模式
- 套路:通过工厂函数动态创建对象并返回
- 适用场景:需要创建多个对象
- 问题:对象没有一个具体的类型,都是Object类型
方式四:自定义构造函数模式
- 套路:自定义构造函数,通过new创建对象
- 适用场景:需要创建多个类型确定的对象
- 问题:每个对象都有相同的数据,浪费内存
方式五:构造函数+原型的组合模式
- 套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
- 适用场景:需要创建多个类型确定的对象
继承模式
原型链继承
- 套路:
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型构造函数
- 创建父类型的对象赋值给子类型的原型(Child.prototype = new Parent())
- 将子类型原型的构造属性(constructor)设置为子类型(Child.prototype.constructor = Child)
- 给子类型原型添加方法
- 创建子类型的对象:可以调用父类型的方法
- 关键:
- 子类型的原型为父类型的一个实例对象
- 子类型的原型为父类型的一个实例对象
借用构造函数继承
(假的)
- 套路:
- 定义父类型构造函数
- 定义子类型构造函数
- 在子类型构造函数中调用父类型构造(Parent.call(this, xxx, xxx) 相当于this.Parent(xxx, xxx))
- 关键:
- 在子类型构造函数中通过call()调用父类型构造函数
组合继承
原型链+借用构造函数的组合继承
- 利用原型链实现对父类型对象的方法继承
- 利用super()借用父类型构造函数初始化相同属性
线程机制与事件机制
进程与线程
进程
- 程序的一次执行,它占有一片独有的内存空间
- 可以通过windows任务管理器查看进程
线程
- 是进程的一个独立执行单元
- 是程序执行的一个完整流程
- 是CPU的最小调度单元
相关知识
- 应用程序必须运行在某个进程的某个线程上(类似于火车 和车厢)
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个进程之间的数据是不能直接共享的
- 线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用
相关问题
- 何为多进程与多线程?
- 多进程运行:一应用程序可以同时启用多个实例运行
- 多线程:在一个进程内,同时有多个线程运行
- 比较单线程与多线程?
- 多线程
- 优点:
- 能有效提升CPU的利用率
- 缺点
- 创建多线程开销
- 线程间切换开销
- 死锁与状态同步问题
- 优点:
- 单线程
- 优点:顺序编程简单易懂
- 缺点:效率低
- 多线程
- js是单线程还是多线程?
- js是单线程运行的
- 但是使用H5中的WebWorkers可以多线程运行
- 浏览器是单线程还是多线程?
- 都是多线程运行的
- 浏览器是单进程还是多进程?
- 有的单进程:老版ie和Firefox
- 有的多进程:Chrome、新版Firefox
浏览器内核
- 支撑浏览器运行的最核心的程序
- 不同的浏览器可能不一样
- Chrome、Safari:webkit
- Firefox:Gecko
- IE:Trident
- 360、搜狗等国内浏览器:Trident+webkit
- 内核由很多模块组成
- 主线程
- js引擎模块:负责js程序的编译与运行
- html、css文档解析模块:负责页面文本的解析
- DOM/CSS模块:负责DOM/CSS在内存中的相关处理
- 布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
- 分线程
- 定时器模块:负责定时器的管理
- DOM事件响应模块:负责事件的管理
- 网络请求模块:负责Ajax请求
- 主线程
定时器引发的思考
- 定时器真是定时执行的吗?
- 定时器并不能保证真正定时执行
- 一般会延迟一丁点儿(可以接受),也有可能延迟很长时间(在原有设定时间基础上+)(不能接收)
- 定时器回调函数是在哪个线程执行的?
- 在主线程执行的,js是单线程的
- 定时器是如何实现的?
- 事件循环模型
js是单线程执行
- 如何证明js是单线程的?
- setTimeout()的回调函数是在主线程执行的
- 定时器回调函数只有在运行栈中的代码全部执行完成后才有可能执行
- 为什么js要用单线程模式,而不用多线程模式?
- js的单线程与它的用途有关
- 作为浏览器脚本语言,js的主要用途是与用户互动,以及操作DOM
- 这决定了它只能是单线程,否则会带来很复杂的同步问题
- 代码的分类:
- 初始化代码
- 回调代码
- js引擎执行代码的基本流程
- 先执行初始化代码:包含一些特别的代码
- 设置定时器
- 绑定监听
- 发送Ajax请求
- 后面在某个时刻才会执行回调代码
- 先执行初始化代码:包含一些特别的代码
浏览器的事件循环(轮询)模型
- 所有代码分类
- 初始化执行代码(同步代码):包含绑定dom事件监听,设置定时器,发送Ajax请求的代码
- 回调执行代码(异步代码):处理回调逻辑
- js引擎执行代码的基本流程:
- 初始化代码==>回调代码
- 模型的2个重要组成部分:
- 事件管理模块
- 回调队列
- 模型的运转流程
- 执行初始化代码,将事件回调函数交给对应模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调队列中
- 只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行
- 相关重要概念
- 执行栈 execution stack:所有的代码都是在此空间中执行的
- 浏览器内核 browser core:js引擎模块(在主线程处理)、其他模块(在主/分线程处理)
- 任务队列、消息队列、事件队列:都是同一个,回调队列 callback queue
- 事件轮询 event loop:从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
- 事件驱动模型 event-driven interaction model
- 请求响应模型 request-response model
H5 Web Workers(多线程)
- 介绍:
- H5规范提供了js分线程的实现,取名为:Web Workers
- 我们可以将一些大计算量的代码交由Web Workers运行而不冻结用户界面
- 但是子线程完全受主线程控制,且不得操作DOM。所以这个新标准并没有改变js单线程的本质
- 使用:
- 创建在分线程执行的js文件
- 在主线程中的js中发消息并设置回调
- 相关API:
- Worker:构造函数,加载分线程执行的js文件
- Worker.prototype.onmessage:用于接收另一个线程的回调函数
- Worker.prototype.postMessage:向另一个线程发送消息
- 不足
- worker内代码不能操作DOM(更新UI)(因为worker内是分线程,只有主线程可以操作DOM)
- 不能跨域加载js
- 不是每个浏览器都支持这个新特性