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

作用域与作用域链
作用域
- 什么是作用域?- 就是一块”地盘”,一个代码段所在的区域
- 特点:它是静态的(相当于上下文对象),在编写代码时就确定了
 
- 作用域分类- 全局作用域
- 函数作用域
- 块作用域(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
 23- function 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
 7- var woker = new Worker('worker.js'); 
 // 用来接收另一个线程发送过来的数据的回调
 worker.onMessage = function(event){
 event.data;
 }
 // 向另一个线程发送数据
 worker.postMessage(data1);
- 问题 



