- @author: Robin LEI
- Advanced knowledge of JavaScript
- I can fly higher than an eagle, for you are the wind beneath my wings. 目录
数据类型
- 数据类型分为基本数据类型和引用(对象)数据类型
- 基本数据类型:String、Number、Boolean、undefined、null
- 引用(对象)数据类型:Object、Array、Function ...
- 对象用来存储数据的,无序的
- 数组有序的,是一种特别的对象,数值下标(属性值)
- 函数也是一种特殊的类型,存储代码,用来执行的
- typeof可以用来判断number、string、boolean、function、undefined,不能用来判断null与object,不能用来判断array与object,判断null和array时,均返回object
- === 全等号可以用来判断undefined和null,不能用来判断数值,因为number数值不唯一,但是undefined和null的各自只有一个值
- intanceof 用来判断对象的具体类型,A(实例对象) intanceof B(构造函数),意思是问A是不是B的实例?
- 对象分为两种,实例对象和类型对象, function Person() {} //这是一个构造函数,var p = new Person(),Person本身是一个类型对象,p就是一个实例对象
- undefined和null有什么区别?undefined是变量定义了但是并没有赋值,但是null是变量定义了且为该变量赋值为null,那么在什么情况下我们需要为变量赋值为null呢?var b = null; //说明我们将要为b赋值一个对象,最后再次将b = null,目的是让b指向的对象为垃圾对象(被垃圾回收器回收)
- 变量分为基本类型的数据和引用类型的数据,判断一个变量是什么类型,是根据这个变量在内存中的值的类型来判断的,如果变量存储的是地址值,那么就是引用类型变量
- 数据类型也分为两种,基本类型的数据和对象类型的数据
对内存的认识
- 数据是存储在内存中的,本质上还是0101...,代表特定的数据信息
- 内存在通电后,会产生一片存储空间,这片空间是临时存储数据的地方
- 注意,在断电后,内存的存储空间消失,此时保存在内存上的数据也会丢失
- 每一个变量都用来对应一块内存,变量名用来找内存,是一个标识符
- 变量是引用类型变量,那么变量存储的是地址值
- 内存分为两大类,堆和栈,堆:存储的是对象(本身),栈:存储全局变量和局部变量、函数名
- 代码存储在内存中,经过编译后被解析执行
- 数据的特点:可传递
- 与其说万物皆对象,不如说一切皆数据
- 内存中所有的操作目标:数据,算数运算、逻辑运算、赋值运算、运行函数
- var a = xxx;变量a在内存中保存的到底是什么?如果此时a是基本数据类型,那么在内存中a保存的是数据指,如果a是对象类型,那么在内存中a保存的是内容是地址值,如果xxx也是一个变量,那么a保存的是xxx的内存内容,也同样分为两种,基本数据类型和对象类型
- 关于引用变量赋值的问题
- 2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据;
- 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象
值传递、址传递、释放内存
- JS调用函数时传递变量是值传递还是址传递?
- 理解1:都是值传递,一个传的是基本数据值、一个传递的是引用类型的地址值
- 理解2:可能是值传递,也可能是址传递(对于引用类型)
- 什么时候进行写内存,什么时候进行读内存,赋值号左边的是写内存,其余的是读内存
- JS引擎是如何管理内存的?
- 分配小内存空间-得到它的使用权-存储数据-反复操作-释放小内存空间
- var a = 3; var obj = {};此时占据3个小内存空间,obj = null;这个时候占据两个小内存空间,堆内存的空间被垃圾回收器回收
- 函数执行结束,函数内的局部变量自动释放,对象:成为垃圾对象-由垃圾回收器回收
什么是对象?
- 对象是多个数据的封装体,用来保存多个数据
- 一个对象在现实生活中就代表一个事物
- 为什么要用对象?为了统一管理多个数据
- 对象的组成,属性名和属性值,方法是一种特别的属性,值为函数
- 属性由名和值组成,名的类型是字符串,值的类型是任意类型
- 如何访问对象中的数据? . 和 [],两种方法,但是前者有时候不能用,后者可以通用,属性名包含特殊字符诸如-时前者不能用,当属性名是一个变量不确定时,不能用前者
认识函数
- 什么是函数?函数是用来实现特定功能的n行语句的封装体
- 只有函数是可执行的,其他类型是不能执行的
- 为什么要使用函数?为了实现代码的复用性,函数体现的是封装的**
- 如何定义函数?两种方式,函数声明式function fn() {...}和表达式var fn = function(){...}
- 如何调用函数?
- 直接调用 fn()
- obj.fn() 通过对象来调用
- new Fn() new调用
- fn.call/apply(obj) 临时调用,临时让fn成为obj对象的方法进行调用
认识回调函数
- 什么是回调函数?
- 自己定义的
- 自己没有调
- 但是函数最终执行了
- 常见的回调函数有哪些?
- dom事件回调 document.getElementById('#id').onclick = function() {...}
- 定时器回调 setTimeout(function() {...}, time)
- ajax
- 生命周期回调
IIFE-立即执行的函数表达式
- 匿名函数自调用
(function() {...})()
- 用于隐藏实现,不会污染外部(全局)的命名空间
- 小demo
;(function() { var a = 1 function test() { console.log(++a) } window.$ = function() { return { test: test } } } })() $().test() // 2
函数中的this
- this是什么?
- 所有函数内部都有一个变量叫this
- 它的值是调用函数的当前对象
- 任何函数本质上都是通过某一个对象来调用的
- this指什么?
- test() // window
- p.test() // p
- new Test() // 新创建的实例对象
- p.call/apply(obj) // obj
分号到底加不加
- 首先参考知乎上的一个回答尤玉溪
- 没有应该不应该,只有喜欢不喜欢
- 下列两种情况下必须要加分号,否则可能会出现问题
- ()开头前一条语句要加分号
- []开头前一条语句要加分号
- demo
var a = 4 (function() {...})() // 程序出错 var b = 5 [1,2,3].forEach(item => {...}) // 程序出错
- 上述条件下加分号,也有利与降低代码合并带来的风险
函数的prototype
- 每一个函数都有一个prototype属性,默认指向一个Object空对象(也叫做原型对象)
- 什么是空对象?就是其中没有我们的属性
- 原型对象中有一个属性constructor,它指向函数对象
- 给原型对象添加属性(一般是添加方法),供实例对象访问
显示原型与隐式原型
- 每个函数function都有一个prototype属性,也就是显示原型(属性)
- 每一个实例对象都有一个__proto__属性,也就是隐式原型(属性)
- 对象的隐式原型的值是对应的构造函数的显示原型的值
- 函数的prototype属性是在定义函数时自动添加的,默认值是一个Object空对象
- 实例对象的__proto__是在创建对象时自动添加的,默认值是构造函数的prototype属性值
- function Fn() {} //内部语句:this.prototype = {}
- var fn = new Fn() // 内部语句:this.proto = Fn.prototype
- Fn.prototype === fn.proto // true
hold住原型链
- 原型链也被称为隐式原型链
- 访问一个对象的属性时,现在自身的属性中查找,若找到就返回,如果找不到,就沿着__proto__向上查找
- 原型链的尽头是:Object.prototype._proto_ //null
- 原型链是用来查找对象的属性(方法),如果要查找变量,那么需要沿着作用域链
- JS引擎在加载页面的时候,会把内置的函数先加载进来,再去执行代码,比如加载Object
- 所有的函数__proto__都是一样的,因为都是new Function产生的
- 对象上没有的属性,其值为undefined
- 所谓的原型继承指的是构造函数实例对象自动拥有构造函数原型对象的属性和方法
- 原型继承利用的是原型链
- function Function() {...},它的prototype和__proto__都是指向同一片区域,即Function.prototype
补充原型链
- 上面谈到过,任何一个函数它的显示原型默认都是一个空的Object对象
- 但凡事都有例外,function Object() {...}它的显示原型是Object prototype
- Function是它自身的一个实例,,所有函数都是Function的实例
- Object的原型对象时原型链的尽头
- demo
Object.prototype instanceof Object // false,因为Object.prototype.__proto__为null Function.prototype instanceof Object // true
原型链的属性问题
- 读取对象的属性时,会自动的到原型链中查找
- 设置对象的属性时,不会去查找原型链,如果当前对象中没有该属性,直接在当前对象中添加此属性并设置其值
- 方法一般定义在原型上,属性一般通过构造函数定义在对象本身上
探索instanceof的秘密
- instanceof是如何判断的?
- 判断的表达式:A instanceof B,其中A是实例对象,B是构造函数
- 如果B函数的显示原型对象在A对象的原型链上,则结果为true,反之为false
- 函数的原型对象默认是一个空的Object对象,也就是说所有函数的原型对象都是Object的实例,即Function.prototype._proto_ === Object.prototype //true
变量声明提升与函数提升
- 变量声明提升,就是指通过var声明(定义)的变量,在定义语句之前局可以访问到,但是其值为undefined
- 函数声明提升,是指通过function声明的函数,在之前就可以直接调用,值为函数定义(对象)
执行上下文
- 代码的分类(位置)
- 全局代码
- 函数(局部)代码
- 执行上下文分为
- 全局执行上下文
- 在执行全局代码之前将window确定为全局执行上下文
- 对全局数据进行预处理
- var 定义的全局变量->undefined,添加为window的属性
- function声明的全局函数-添加为window的方法
- this->window
- 函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(这是一个虚拟的对象,存在于栈中)
- 对函数内部局部数据进行处理
- 形参变量->赋值(实参)->添加为函数执行上下文的属性
- arguments->赋值(实参列表)->添加为函数执行上下文的属性
- var定义的局部变量->undefined,添加为执行上下文的属性
- function声明的函数->添加为执行上下文的方法
- this->指向调用函数的对象
- 开始执行函数体代码
- 全局执行上下文
- 局部变量(包含全局变量和局部变量)存储在栈中
- 函数执行结束,栈中的空间就会被释放(其实释放或者不释放我们并不知道,因为它本身就是一个封闭的空间)
执行上下文栈
- 先搞清楚一个概念,定义和调用,函数定义比调用先执行,这是两个概念
- 只有函数调用时才会产生执行上下文
- 队列:先进先出,一头进一头出,栈:后进先出,只有一个口,用于出入栈
- 整个js文件包含n+1个上下文,1是window
- 一定是栈顶的上下文先执行
- 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后,栈中只剩下window
- 递归调用:在函数内部调用自己
作用域
- 概念
- 类似于"地盘",一个封闭空间
- 分类
- 全局作用域
- 局部作用域
- 块级作用域(es6)
- 作用
- "隔离变量",即在不同区域可以重复定义相同变量名
作用域与执行上下文
- 区别
- 区别1
- 全局作用域之外,每一个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后,JS代码执行之前创建
- 函数执行上下文是在函数调用时,函数体代码执行之前创建
- 区别2
- 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
- 上下文环境是动态的,函数调用时创建,函数调用结束时上下文环境就会被释放
- 区别1
- 联系
- 上下文环境(对象)从属于所在的作用域
- 全局上下文环境 => 全局作用域
- 函数上下文 => 对应的函数作用域
闭包的理解
- 执行函数定义就会产生闭包(不要调用内部函数)
- 函数是一个特殊的变量
- 执行函数定义(要调用外部函数),就会产成闭包,不用调用内部函数
如何产生?
- 当一个嵌套的内部子函数引用了嵌套的外部函数的变量时 闭包是什么?
- 使用Chrome调式查看
- 闭包是嵌套的子函数(绝大部分人)
- 是一个对象,包含被引用变量(函数)的对象
- 存在于嵌套的内部函数中 产生闭包的条件?
- 内部函数引用了外部函数的数据(变量/函数)
- 内部函数可以不调用,但是外部函数必须调用
常见闭包
- 将一个函数作为另一个函数的返回值(且这个返回值函数引用了外部函数的变量)
- 看产生几个闭包就看外部函数调用几次
- 将函数作为实参传递给另一个函数调用(把一个实参传递给一个嵌套的子函数使用)
闭包的作用
- 使函数内部的变量在函数执行完后仍然存在于内存中(延长了局部变量的生命)
- 让函数外部可以操作到函数内部的数据
问题
- 函数执行结束后,函数内部声明的局部变量是否还存在?
- 一般不存在,存在于闭包中的变量才存在
- 再函数外部可以直接访问到函数内部的局部变量吗?
- 不能,但是通过闭包可以做到
- 函数执行结束后,函数内部声明的局部变量是否还存在?
闭包的生命周期
- 产生
- 在嵌套的内部函数定义执行完就产生了(不是在调用)
- 死亡
- 嵌套的内部函数成为垃圾对象时
闭包的应用-自定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或者函数
- 模块的使用者只需要通过这个暴露的对象去调用内部的方法即可
内存溢出与内存泄漏
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就出现抛出内存溢出的错误
- 内存泄漏
- 占用的内存没有及时释放
- 内存泄漏多了就会导致内存溢出
- 常见的内存泄漏
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
对象创建模式
- 对象创建模式
new Object()
* 开始时对象内部数据不确定
- 字面量创建模式
var obj = {}
* 适用于对象内部数据确定
* 弊端就是在某些场景下会有重复代码
- 工厂模式
function Person(name, age) {
var obj = {
name: name,
age: age
}
return obj
}
* 通过工厂函数动态创建对象并返回
* 适用于创建多个对象
* 创建出来的对象都是只有一种类型,object
- 自定义构造函数模式
function Person(name, age) {
this.name = name
this.age = age
this.setName = function() {
}
}
* 通过new创建
* 创建多个类型确定的对象
* 每一个对象都具有相同的数据,浪费内存
- 构造函数+原型组合模式
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function() {
}
* 属性在函数中初始化
* 方法添加到原型上
* 原型方法给实例对象调用
* 适用于需要创建多个类型确定的对象
实例对象.constructor,查看实例对象的构造安函数是谁
- Sub.prototype.constructor = Sub
原型链继承
- 套路
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型的构造函数
- 创建父类型的实例对象赋值给子类型的原型
- 将子类型原型的构造属性设置为子类型构造函数
- 给子类型原型添加方法
- 创建子类型的对象,可以调用父类型的方法
- 关键
- 子类型的原型为父类型的实例对象
借用构造函数继承
- 套路
- 定义父类型构造函数
- 定义子类型构造函数
- 在子类型构造函数中调用父类型构造
- 关键 在子类型中通过call调用父类型的构造函数
组合继承
- 利用原型链实现对父类型对象的方法继承
- 利用call借用父类型构造函数初始化相同属性
进程与线程
- 进程是独有的内存空间
- 内存空间相互之间是独立的
- 线程是进程的执行单元
- 线程是CPU的最小调度单元
- 单线程优点:顺序编程,缺点:效率低
- JS是单线程的,H5的web workers可以多进程运行
- 主线程只有一个
- 浏览器都是多线程的
- 浏览器可以是多进程也可以是单进程,Chrome和新版IE是多进程,Firefox和老版IE是单进程
- 一个程序可以是多进程也可以是单进程,一个进程可以有单个线程也可以有多个线程
- 线程池,保存线程对象的,实现线程的复用
- 一个程序必须运行在一个进程的某一个线程上
- 一个进程的数据在其的多个线程内是共享的
- 多线程的优点是:提升CPU效率,缺点是:创建线程的开销、线程切换开销、死锁与状态同步问题
浏览器内核
- 本质上是一个程序,是浏览器运行最核心的程序
- 不同的浏览器内核不一样
- Chrome、Safari:webkit
- Firefox:gecko
- IE:trident
- 360、搜狗...:trident + webkit
- 内核由很多个模块组成,分为两大类,一类在主线程中运行,一类在子线程中运行
- 主线程
- JS引擎模块:负责js的编译和运行
- html/css解析模块:负责页面文本的解析
- dom/css模块
- 布局和渲染模块
- ......
- 子线程
- 定时器模块
- 事件模块
- 网络请求模块
- 主线程