JS高级学习笔记
基础总结深入理解
数据类型
1. 分类
- 基本(值)类型
- String: 任意字符串
- Number: 任意的数字
- Boolean: true/false
- undefined: undefined
- null: null
- 对象(引用)类型
- Object: 任意类型
- Function: 一种特别的对象(可以执行)
- Array: 一种特别的对象(对象下标)
2. 判断
- 如何判断数据类型?
- typeof
- instanceof
- ===
相关问题
undefined与null的区别?
undefined代表定义未赋值
null定义并赋值了,只是值为null什么时候给变量赋值为null呢?
初识赋值,表示将要赋值为对象
结束前,让对象成为垃圾对象(被垃圾回收器回收)严格区别变量类型与数据类型?
- 数据的类型
- 基本类型
- 对象类型
- 变量的类型(变量内存值的类型)
- 基本类型: 保存就是基本类型的数据
- 引用类型: 保存的是地址值
- 数据的类型
数据_变量_内存
1. 什么是数据?
- 存储在内存中代表特定信息的’玩意’,本质是以0101形式存储
- 数据的特点
- 可传递
- 可运算
- 哲学思想
- 一切皆数据
- 内存中操作的目标
- 就是操作数据
- 数据能进行哪些操作?
- 算术运算
- 赋值
- 逻辑运算
- 运行函数
2. 什么是内存?
- 内存条通电后产生的可存储数据的空间(临时的)
- 内存的产生和死亡
- 内存条(一块电路板) -> 通电 -> 产生内存空间 -> 存储数据 -> 处理数据 -> 断电 -> 内存空间和数据都消失
- 一块小内存的2个数据
- 内部存储的数据
- 地址值
- 内存分类
- 栈: 存储 全局变量/局部变量
- 堆: 存储 对象
3. 什么是变量?
- 可变化的量,由变量名和变量值组成
- 每个变量都对应的一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据
4. 内存,数据,变量三者之间的关系?
- 内存用来存储数据的空间
- 变量是内存的标识
关于赋值和内存的问题
var a = xxx, a内存中到底保存的是什么?
- xxx是基本数据,保存的就是这个数据
- xxx是对象, 保存的就是对象的地址值
- xxx是一个变量, 保存的xxx的内存内容(可能是基本数据, 也可能是地址值)
关于引用变量赋值问题
- 2个引用变量指向同一对象, 通过一个变量修改对象内部数据, 另一个变量看到的是修改之后的数据
1 | // 2个引用变量指向同一个对象 |
- 2个引用变量指向同一个对象, 让其中一个引用变量指向另一个对象, 另一个引用变量依然指向前一个对象
1 | // 2个引用变量指向同一个对象 |
- 在JS调用函数时传递变量参数时, 是值传递还是引用传递
- 理解1: 都是值(基本/地址值)传递
- 理解2: 可能是值传递, 也可能是引用传递(地址值)
1 | var a = 3; |
- JS引擎如何管理内存?
- 内存生命周期
- 分配小内存空间, 得到它的使用权
- 存储数据, 可以反复进行操作
- 释放小内存空间
- 释放内存
- 局部变量: 函数执行完自动释放
- 对象: 成为垃圾对象 -> 垃圾回收器回收
- 内存生命周期
对象
什么是对象?
- 多个数据的封装体
- 用来保存多个数据的容器
- 一个对象代表现实中的一个事物
为什么要用对象?
- 统一管理多个数据
对象的组成
- 属性
- 属性名(字符串)和属性值(任意)组成
- 方法
- 一种特别的属性(属性值是函数)
- 属性
如何访问对象内部数据?
- .属性名
- 编码简单,有时不能用
- []
- 编码麻烦,能通用
- .属性名
什么时候必须使用['属性名']的方式?
- 属性名包含特殊字符: - 空格
- 变量名不确定
函数
了解函数
- 什么是函数?
- 实现特定功能的n条语句的封装体
- 只有函数是可以执行的, 其他类型的数据不能执行
- 为什么要用函数?
- 提高代码复用
- 便于封装
- 函数的核心思想
- 封装
- 如何定义函数?
- 函数声明
- 表达式
- 如何调用(执行)函数?
- test() : 直接调用
- obj.test() : 通过对象调用
- new.test() : new调用
- test.call/apply(obj) : 临时让test成为obj的方法进行调用
回调函数
- 什么函数才是回调函数?
- 你定义的
- 你没有掉,但最终它执行了
- 常见的回调函数?
- dom事件回调函数
- 定时器回调函数
- ajax请求回调函数
- 生命周期回调函数
1 | <button id='btn'>测试点击事件</button> |
1 | // dom事件回调函数 |
IIFE(立即调用函数表达式)
- 理解
- 全称: Immediately-Invoked Function Expression
- 中文解释: 立即调用的函数表达式
- 作用
- 隐藏实现
- 不会污染外部(全局)命名空间
- 用它来编写js模块
1 | // 匿名函数自调用 |
函数中的this
this是什么?
- 任何函数本质上都是通过某个对象来调用的, 如果没有直接指定的就是window
- 所有函数内部都有一个变量this
- 它的值是调用函数的当前对象
- 自己的理解
- this其实就是函数的调用者, 可能是window,可能是某个对象
如何确定this的值?
- test() : window
- p.test() : p
- new test() : 新创建的对象
- p.call(obj) : obj
总结
数据类型:两大类
基本类型(值): String Number Boolean undefined null
对象类型(引用): Object Function(可以执行) Array(有序)
判断数据类型的方法(3种)
typeof(返回数据类型名, 不能区别null、对象和数组)
instanceof(返回true/false, 专门用来判断Object、Array、Function)
===(返回true/false, 只能判断undefined和null)
数据是存储在内存中代表特定信息的东西
内存是通电后临时产生可存储数据的空间
变量是可变化的量, 变量以及变量的值会以数据的形式保存到内存中
对象是多个数据的封装体
函数是实现特定功能的n条语句的封装体
回调函数是自己定义, 没有调用却执行了的特殊函数
IIFE 立即调用函数表达式
this代表函数的调用者
函数高级(重点内容)
原型与原型链
原型
- 函数的prototype属性
- 函数都有prototype属性,默认指向一个Object空对象(既称为原型对象),每个原型对象中有一个属性constructor,它指向函数对象
- 给原型对象添加属性(一般是方法)
- 作用:函数的所有实例对象自动拥有原型中的属性(方法)
显式原型与隐式原型
- 每个函数function都有一个prototype, 既显式原型(属性)
- 每个实例对象都有一个proto, 可称为隐式原型(属性)
- 对象的隐式原型的值为其对应构造函数的显式原型的值
总结
函数的prototype属性: 在定义函数时自动添加的,默认值是一个空Object对象
对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)
原型链
- 说明
- 访问一个对象的属性时,先在自身属性中查找,找到返回,如果没有,则沿着__proto__这条链向上查找,找到返回,如果最终没有找到,返回undefined
- 别名: 隐式原型链
- 作用
- 查找对象的属性(方法)
- 理解原型链的三个基本点
- 函数的显式原型指向的对象默认是空Object实例对象(但Objec不满足)
- 所有函数都是Function的实例(包含Function)
- Object的原型对象是原型链的尽头
原型链_属性问题
- 读取对象属性值时: 会自动到原型链中查找
- 设置对象属性值时: 不会查找原型链, 如果当前对象中没有此属性,直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身内
instanceof
- instanceof是如何判断的?
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
- Function是通过new自己产生的实例
执行上下文与执行上下文栈
变量提升与函数提升
- 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值为undefined
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值为函数定义(对象)
总结一句话就是变量定义前可以使用变量,但值 为undefined
- 值为函数定义(对象)
- 通过function声明的函数, 在之前就可以直接调用
执行上下文(帮助理解内部是如何执行的)
- 代码分类(位置)
- 全局代码
- 函数(局部)代码
- 全局执行上下文(流程)
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量==>undefined,添加为window的属性
- 将var定义的变量作为添加到window中
- function声明的全局函数==>赋值(fun), 添加为window的方法
- 将function声明的函数作为方法添加到window中
- this==>赋值(window)
- 最后才开始执行全局代码
- var定义的全局变量==>undefined,添加为window的属性
- 函数执行上下文(流程)
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在与栈中)
- 对局部数据进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表),添加为执行上下文的属性
- var定义的局部变量==>undefined,添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 最后才开始执行函数体代码
执行上下文栈
- 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移出(出栈)
- 所有的代码执行完后,栈中只剩下window
![](F:\素材整理\素材库.library\images\KCKGAPGDU5S33.info[译] 理解 JavaScript 中的执行上下文和执行栈 - 掘金_thumbnail.png)
作用域与作用域链
作用域
- 什么是作用域?
- 就是一块”地盘”,一个代码段所在的区域
- 特点:它是静态的(相当于上下文对象),在编写代码时就确定了
- 作用域分类
- 全局作用域
- 函数作用域
- 块作用域(ES6存在)
- 作用域的作用
- 隔离变量,不同作用域下同名变量不会有冲突
作用域与执行上下文
- 作用域与执行上下文的区别
- 区别一
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
- 函数执行上下文是在调用函数时,函数体代码执行之前创建
- 区别二
- 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
- 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
- 区别一
- 作用域与执行上下文的联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
作用域链
- 什么是作用域链?
- 多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
- 查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
- 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
- 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
闭包
了解闭包
- 如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
- 闭包是什么?
- 闭包是嵌套的内部函数
- 包含被引用变量(函数)的对象
- 注意:
- 闭包存在于嵌套的内部函数中
- 产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
1 | function fn1() { |
闭包的作用
- 使用函数内部的变量在函数执行完厚,仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
闭包的生命周期
- 产生
- 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡
- 在嵌套的内部函数成为垃圾对象时
闭包的应用
- 定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
闭包的缺点以及如何解决
- 缺点
- 函数执行完厚,函数内的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄漏
- 解决
- 能不用闭包就不用闭包
- 及时释放
- 变量 = null
内存溢出与内存泄露
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
总结
原型分为显式原型和隐式原型
每个函数都有一个prototype,也就是显式原型
每个实例对象都有一个__proto__,也就是隐式原型
实例对象的隐式原型的值等于构造函数的显式原型的值
原型链:通过隐式原型查找属性或方法的一条路径,Object的原型对象是原型链的尽头
原型链属性问题:读取对象属性或方法时才会查找原型链,设置时则不会
A instanceof B
B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
变量声明提升:通过var声明的变量, 在执行语句前,就会保存到window对象中,只是没有赋值
函数声明提升:通过function声明的函数,在声明语句前就可以调用
执行上下文:当前javascript代码被解析和执行时所在的环境的抽象概念
特点:动态生成 调用函数时就会自动生成一个执行上下文 结束时会自动销毁
作用域:一个代码所在区域
特点:静态,作用域在编写代码时就确定了
闭包:一个函数可以访问另一个函数作用域中的变量,前者是闭包
作用:让子函数能访问到父函数中的变量
面向对象高级(重点内容)
对象的创建模式
Object构造函数模式
创建流程
- 先创建空的Object对象,再动态添加属性/方法
适用场景
- 起始时不确定对象内部数据
存在问题
- 语句太多
实现代码
1
2
3
4
5
6
7
8
9// 创建空Object对象
var obj = new Object();
var obj = {};
// 动态添加属性/方法
obj.name = 'Tom';
obj.age = 14;
obj.setName = function(name){
this.name = name
}
对象字面量模式
创建流程
- 使用{}创建对象,同时指定属性/方法
适用场景
- 起始时对象内部属性是确定的
存在问题
- 如果创建多个对象,代码冗余
实现代码
1
2
3
4
5
6
7
8// {}创建对象, 同时指定属性/方法
var obj = {
name: 'Tom',
age: 14,
setName: function(name){
this.name = name;
}
}
工厂模式
创建流程
- 通过工厂函数动态创建对象并返回
适用场景
- 需要创建多个对象
存在问题
- 对象没有具体的类型,都是Object类型
实现代码
1
2
3
4
5
6
7
8
9
10
11// 创建工厂函数
function createPerson(name, age){
var obj = {
name: name,
age: age,
setName: function(name){
this.name = name
}
}
return obj
}
自定义构造函数模式
创建流程
- 自定义构造函数,通过new创建对象
适用场景
- 需要创建多个类型确定的对象
存在问题
- 每个对象都有相同的数据,浪费内存
实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13// 创建自定义构造函数
function Person(name, age){
this.name = name;
this.age = age;
this.setName = function(name){
this.name = name
}
}
// new创建对象
var p1 = new Person('Tom', 12);
p1.setName('YF');
var p2 = new Person('Bom', 14);
p2.setName('YangFan');
构造函数+原型的组合模式
创建流程
- 自定义构造函数,属性在函数中初始化,方法添加到原型中
适用场景
- 需要创建多个类型确定的对象
实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 自定义构造函数
function Person(name, age){
this.name = name;
this.age = age;
}
// 方法添加到原型中
Person.prototype.setName = function(name){
this.name = name;
}
// new 创建对象
var p1 = new Person('Tom', 14);
p1.setName('YF');
var p2 = new Person('Bom', 12);
p2.setName('YangFan');
继承模式
原型链继承
- 实现流程
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型的构造函数
- 创建父类型的对象赋值给子类型的原型
- 将子类型的原型的构造属性设置为子类型
- 给子类型原型添加方法
- 创建子类型的对象: 可以调用父类型的方法
- 实现继承的关键
- 子类型的原型为父类的一个实例对象
- 实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 定义父类型构造函数
function Supper(){
this.supPro = "Supper property";
}
// 给父类型的原型添加方法
Supper.prototype.showSupPro = function(){
console.log(this.supPro);
}
// 定义子类型的构造函数
function Sub(){
this.subPro = "Sub property";
}
// 创建父类型的对象赋值给子类型的原型
Sub.prototype = new Supper();
// 将子类型的原型的构造属性设置为子类型
Sub.prototype.constructor = Sub;
// 给子类型原型添加方法
Sub.prototype.showSubPro = function(){
console.log(this.subPro);
}
// 创建子类型对象
var s1 =new Sub();
// 调用父类型方法
s1.showSupPro();
借用构造函数继承(不是真正意义上的继承)
- 实现流程
- 定义父类型构造函数
- 定义子类型构造函数
- 在子类型中构造函数中调用父类型构造
- 实现继承的关键
- 在子类型构造函数中通过call()调用父类型构造函数
- 实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 定义弗雷西构造函数
function Person(name, age){
this.name = name;
this.age = age;
}
// 定义子类型构造函数
function Student(name, age, price){
// 在子类型中构造函数中调用父类型构造
Person.call(this, name, age);
this.price = price;
}
var s1 = new Student('Tom', 14, 1000);
console.log(s1.name, s1.age, s1.price);
原型链+借用构造函数的组合继承
实现关键
- 利用原型链实现对父类型对象的方法的继承
- 利用call()借用父类型构建函数初始化相同属性
实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function Person(name, age){
this.name =name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
}
function Student(name, age, price){
Person.call(this, name, age);
this.price = price;
}
Student.prototype = new Person(); // 这一步是为了让子类型能使用到父类型的方法
Student.prototype.constructor = Student; // 这一步是为了修正constructor属性
Student.prototype.setPrice = function(price){
this.price = price;
}
var s1 =new Student('Tom', 14, 10000);
s1.setName('Bom');
s1.setPrice(8000);
console.log(s1);
知识点
- new一个对象背后做了什么?
- 创建一个空对象
- 给对象设置proto,值为构造函数的prototype属性值 this.proto = Fn.prototype
- 执行构造函数体(给对象添加属性/方法)
线程机制与事件机制
线程与进程
进程
- 每个程序必须有一个进程,占有一片独立的内存空间
- 可以通过windows任务管理查看进程
线程
- 进程内的一个地理执行单元
- 执行程序的一个完整流程
- CPU的最小调度单元
进程与线程的关系
- 一个进程至少有一个线程(主)
- 程序是在某个进程中的某个线程执行的
浏览器内核模块组成
主线程
- JS引擎模块
- 负责js程序的编译与运行
- HTML/CSS文档解析模块
- 负责页面文本的解析
- DOM/CSS模块
- 负责dom/css在内存中的相关处理
- 布局和渲染模块
- 负责页面的布局和效果的绘制(内存中的对象)
- JS引擎模块
分线程
- 定时器模块
- 负责定时器的管理
- DOM事件模块
- 负责事件的管理
- 网络请求模块
- 负责Ajax请求
- 定时器模块
JS线程
- JS是单线程执行的(回调函数也是在主线程)
- H5提出了实现多线程的方案: Web Workers
- 只能是主线程更新界面
定时器问题
- 定时器并不真正完全定时
- 如果在主线程执行了一个长时间的操作,可能导致延时才处理
事件处理机制
代码分类
- 初始化执行代码
- 包含绑定DOM事件监听、设置定时器、发送Ajax请求的代码
- 回调执行代码
- 处理回调逻辑
- 初始化执行代码
JS引擎执行代码的基本流程
- 初始化代码 ==> 回调代码
模型的两个重要组成部分
- 事件管理模块
- 回调队列
模型的运转流程
- 执行初始化代码,将事件回调函数交给对应模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调列队中
- 只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行
H5 Web Workers
可以让js在分线程执行
Worker实现代码
1
2
3
4
5
6
7var woker = new Worker('worker.js');
// 用来接收另一个线程发送过来的数据的回调
worker.onMessage = function(event){
event.data;
}
// 向另一个线程发送数据
worker.postMessage(data1);问题