JS 高级
JS 高级
基础总结深入
数据类型
分类
- 基本(值)类型
String
:任意字符串Number
:任意的数字Boolean
:true falseUndefined
:undefinedNull
:null
- 对象(引用)类型
Object
:任意对象Function
:一种特别对象(可以执行)Array
:一种特别的对象(数值下标,内部数据有序)
判断
===: 判等判断
可以判断:undefined, null
typeof
: 返回数据类型的字符串表达可以判断:undefined / number / string / true/false / function
不能判断:null与object object与array
1
2
3
4
5
6
7
8
9
10
11var a
console.log(a, typeof a, typeof a==='undefined',a===undefined ) // undefined 'undefined' // true true
console.log(undefined==='undefined') //false
a = 4
console.log(typeof a==='number')
a = 'atguigu'
console.log(typeof a==='string')
a = true
console.log(typeof a==='boolean')
a = null
console.log(typeof a, a===null) // 'object'instanceof
:判断对象的具体类型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3')
return function () {
return 'xfzhang'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array) // true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true
console.log(typeof b1.b2) // 'object' typeof数组返回'object'
console.log(typeof b1.b3==='function') // true
b1.b2[2](4)
console.log(b1.b3()())
几个问题
undefined 与 null 的区别
undefined代表定义未赋值
null定义并赋值了, 只是值为null
什么时候给变量赋值为null呢?
初始赋值, 表明将要赋值为对象
结束前, 让对象成为垃圾对象(被垃圾回收器回收)
1
2
3
4
5
6//起始
var b = null // 初始赋值为null, 表明将要赋值为对象
//确定对象就赋值
b = ['atguigu', 12]
//最后
b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收)严格区别变量类型与数据类型
数据类型:基本类型/对象类型
变量类型:
基本类型:保存就是基本类型的数据
引用类型:保存的是地址值
数据变量内存
关于引用变量赋值问题
2个引用变量指向同一个对象, 通过一个变量修改对象内部数据, 另一个变量看到的是修改之后的数据
1 | var obj1 = {name: 'Tom'} |
2个引用变量指向同一个对象, 让其中一个引用变量指向另一个对象, 另一引用变量依然指向前一个对象
1 | var a = {age: 12} |
- 对象释放内存
局部变量: 函数执行完自动释放
对象: 成为垃圾对象==>垃圾回收器回收
对象
对象的组成
属性: 属性名(字符串)和属性值(任意)组成
方法: 一种特别的属性(属性值是函数)
访问对象内部数据
.属性名: 编码简单, 有时不能用
[‘属性名’]: 编码麻烦, 能通用
*必须使用[‘属性名’]的方式
属性名包含特殊字符: - , 空格
属性名不确定
1 | var p = {} |
函数
定义函数
1 | function fn1 () { //函数声明 |
函数调用
test(): 直接调用
obj.test(): 通过对象调用
new test(): new调用
test.call/apply(obj): 临时让test成为obj的方法进行调用
1 | var obj = {} |
回调函数
什么函数才是回调函数?
- 你定义的
- 你没有调
- 但最终它执行了(在某个时刻或某个条件下)
常见的回调函数?
dom事件回调函数 ==>发生事件的dom元素
定时器回调函数 ===>window
ajax请求回调函数
生命周期回调函数
IIFE
全称: Immediately-Invoked Function Expression
作用
- 隐藏实现
- 不会污染外部(全局)命名空间
- 用它来编码js模块
1 | (function () { //匿名函数自调用 |
函数中的this
- this是什么?
- 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 它的值是调用函数的当前对象
- 如何确定this的值?
- test(): window
- p.test(): p
- new test(): 新创建的对象
- test.call(obj): obj
1 | function Person(color) { |
函数高级
原型和原型链
原型prototype
函数的prototype属性
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 这个原型对象中有一个属性constructor, 它指回这个函数对象
给(一般是构造函数的)原型对象添加属性(一般都是方法)
- 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
1 | // 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象) |
显式原型和隐式原型
- 每个函数function都有一个prototype,即显式原型(属性)
- 每个实例对象都有一个
__proto__
,可称为隐式原型(属性) - 对象的隐式原型的值为其对应构造函数的显式原型的值
- 总结:
- 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
- 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
- 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

1 | //1.定义构造函数 |
原型链
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着
__proto__
这条链向上查找, 找到返回 - 如果最终没找到, 返回undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)

深入理解原型
1.
1 | var o1 = {} |

直接创建对象时,obj这个对象本质上是被Object函数创建的,因此obj.__proto__=== Object.prototype,即,每个对象都有一个proto属性,指向创建该对象的函数的prototype
```js
var f1 = new Foo()1
2
3
4
5
6
7
8
{% asset_img 1780825-20190829182201479-1238403745.png 1780825-20190829182201479-1238403745 %}
当通过自定义函数创建对象时,对象的\_\_proto\_\_指向这个函数的原型,而自定义函数的prototype本质上就是一个空对象,都是被Object创建,所以它的\_\_proto\_\_指向的就是Object.prototype。**但是,Object.prototype确实一个特例——它的\_\_proto\_\_指向的是null**
3. ```js
function foo(x, y){return x+y}
var foo = new Function("x","y","return x+y")函数也是一种对象,函数也有__proto__属性,每个函数都是Function的实例对象,包括Object函数,所以自定义函数Foo.__proto__指向Function.prototype,Object.__proto__指向Function.prototype。但是,这里Function.__proto__指向Function.prototype,原因是:Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以Function是被自身创建的。所以它的__proto__指向了自身的Prototype

Function.prototype指向的对象也是一个普通的被Object创建的对象,它的__proto__是不是也指向Object.prototype
- 完整的关系如下
几个问题
- 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
1
2
3console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true所有函数都是Function的实例(包含Function)
1
console.log(Function.__proto__===Function.prototype)
Object的原型对象是原型链尽头
1
console.log(Object.prototype.__proto__) // null
原型的属性
读取对象的属性值时: 会自动到原型链中查找
设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
instanceof
instanceof是如何判断的?
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
1 | function Foo() { } |
执行上下文与执行上下文栈
变量提升和函数提升
在ES6
之前,JavaScript
没有块级作用域(一对花括号{}
即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分,js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升。
变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。
当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明
执行上下文
代码分类(位置)
- 全局代码
- 函数(局部)代码
全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值, 添加为window的方法
- this==>赋值(window)
- 开始执行全局代码
函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
对局部数据进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
函数在定义的时候(不是调用的时候),就已经确定了函数体内部变量的作用域。
开始执行函数体代码
执行上下文栈
- 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下window
作用域和作用域链
分类
全局作用域
函数作用域
没有块作用域(ES6有了)
作用
- 隔离变量,不同作用域下同名变量不会有冲突
作用域链
- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
查找一个变量的查找规则(自由变量跨作用域取值时,要去创建这个函数的作用域取值)
在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
闭包
如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
闭包到底是什么?
使用chrome调试查看
理解一: 闭包是嵌套的内部函数(绝大部分人)
理解二: 包含被引用变量(函数)的对象(极少数人)
注意: 闭包存在于嵌套的内部函数中
产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
1 | function fn1 () { |
常见的闭包
- 将函数作为另一个函数的返回值
1 | function fn1() { |
- 将函数作为实参传递给另一个函数调用
1 | function showDelay(msg, time) { |
闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
1 | function fn1() { |
闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时
自定义JS模块
JS模块:
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
1 | function myModule() { |
1 | (function () { |
闭包的缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
解决
- 能不用闭包就不用
- 及时释放
1 | function fn1() { |
对象高级
创建对象
- Object构造函数模式
1 | // 先创建空Object对象 |
- 套路: 先创建空Object对象, 再动态添加属性/方法
- 适用场景: 起始时不确定对象内部数据
- 问题: 语句太多
- 对象字面量模式
1 | var p = { |
- 套路: 使用{}创建对象, 同时指定属性/方法
- 适用场景: 起始时对象内部数据是确定的
- 问题: 如果创建多个对象, 有重复代码
- 工厂模式
1 | function createPerson(name, age) { //返回一个对象的函数===>工厂函数 |
- 套路: 通过工厂函数动态创建对象并返回
- 适用场景: 需要创建多个对象
- 问题: 对象没有一个具体的类型, 都是Object类型
- 自定义构造函数模式
1 | function Person(name, age) { |
- 套路: 自定义构造函数, 通过new创建对象
- 适用场景: 需要创建多个类型确定的对象
- 问题: 每个对象都有相同的数据, 浪费内存
- 构造函数+原型的组合模式
1 | function Person(name, age) { //在构造函数中只初始化一般属性 |
- 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
- 适用场景: 需要创建多个类型确定的对象
继承模式
原型链继承
子类型的原型为父类型的一个实例对象
1 | //父类型 |
2.借用构造函数继承(没有真正继承)
在子类型构造函数中通用call()调用父类型构造函数
1 | function Person(name, age) { |
原型链+借用构造函数的组合继承
利用原型链实现对父类型对象的方法继承
* 利用call()借用父类型构建函数初始化相同属性
1 | function Person(name, age) { |
线程机制和事件机制
浏览器内核
内核由很多模块组成
js引擎模块:复制js程序的编译和运行
- html,css文档解析模块 : 负责页面文本的解析
- dom/css模块 : 负责dom/css在内存中的相关处理
- 布局和渲染模块 : 负责页面的布局和效果的绘制
- 定时器模块 : 负责定时器的管理
- 网络请求模块 : 负责服务器请求(常规/Ajax)
- 事件响应模块 : 负责事件的管理
事件循环模型
- 所有代码分类
- 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
- 回调执行代码(异步代码): 处理回调逻辑
- js引擎执行代码的基本流程:
- 初始化代码===>回调代码
- 模型的2个重要组成部分:
- 事件(定时器/DOM事件/Ajax)管理模块
- 回调队列
- 模型的运转流程
- 执行初始化代码, 将事件回调函数交给对应模块管理
- 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
- 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
