JS高级
文章目录
一、基础总结
typeof: 返回的是数据类型的字符串表达形式
1.基本类型
类型 | 判断 |
---|---|
Number | typeof |
String | typeof |
Boolean | typeof |
undefined | typeof/=== |
null | === |
2.对象类型
对象 | 判断 |
---|---|
Object | typeof/instanceof |
Array | instanceof |
Function | typeof(特别的对象,可执行) |
3.数据,变量,内存的理解
- 什么是数据?
- 在内存中可读的, 可传递的保存了特定信息的’东东’
- 一切皆数据, 函数也是数据
- 在内存中的所有操作的目标: 数据
- 数据对象
- 基本类型
- 对象类型
- 什么是变量?
- 在程序运行过程中它的值是允许改变的量
- 一个变量对应一块小内存, 它的值保存在此内存中
- 变量类型:
- 基本类型: 保存基本类型数据的变量
- 引用类型: 保存对象地址值的变量
- 什么是内存?
- 内存条通电后产生的存储空间(临时的)
- 一块内存包含2个方面的数据
- 内部存储的数据
- 地址值数据
- 内存空间的分类
- 栈空间: 全局变量和局部变量
- 堆空间: 对象
- 内存,数据, 变量三者之间的关系
- 内存是容器, 用来存储不同数据
- 变量是内存的标识, 通过变量我们可以操作(读/写)内存中的数据
4.关于引用变量赋值问题
- 2个引用变量指向同一个对象, 通过一个引用变量修改对象内部数据, 另一个引用变量也看得见
- 2个引用变量指向同一个对象,让一个引用变量指向另一个对象, 另一个引用变量还是指向原来的对象
//1.
var obj1 = {}
var obj2 = obj1
obj2.name = 'Tom'
console.log(obj1.name)//Tom
function f1(obj) {
obj.age = 12
}
f1(obj2)
console.log(obj1.age)//12
//2.
var obj3 = {name: 'Tom'}
var obj4 = obj3
obj3 = {name: 'JACK'}
console.log(obj4.name)//Tom
function f2(obj) {
obj = {name: 'Bob'}
}
f2(obj4)
console.log(obj4.name)//Tom
5.对象的理解和使用
- 什么是对象?
- 多个数据(属性)的集合
- 用来保存多个数据(属性)的容器
- 属性的分类:
- 一般 : 属性值不是function 描述对象的状态
- 方法 : 属性值为function的属性 描述对象的行为
- 方法
- 对象.属性名
- 对象[‘属性名’]: 属性名有特殊字符/属性名是一个变量
6.函数的理解和使用
-
什么是函数?
- 用来实现特定功能的, n条语句的封装体
- 只有函数类型的数据是可以执行的, 其它的都不可以
-
为什么要用函数?
- 提高复用性
- 便于阅读交流
-
函数也是对象
- instanceof Object===true
- 函数有属性: prototype
- 函数有方法: call()/apply()
- 可以添加新的属性/方法
-
函数的3种不同角色
- 一般函数 : 直接调用
- 构造函数 : 通过new调用
- 对象 : 通过.调用内部的属性/方法
-
函数中的this
- 显式指定谁: obj.xxx()
- 通过call/apply指定谁调用: xxx.call(obj)
- 不指定谁调用: window
- 回调函数: 看背后是通过谁来调用的: window/其它
-
匿名函数自调用:IIFE立即执行函数:
(function(){ //实现代码 })()
-
回调函数的理解
像这种函数,由我们创建但是不由我们调用的,我们称为回调函数- 常用的回调函数
- dom事件回调函数
- 定时器回调函数
- ajax请求回调函数
- 生命周期回调函数
- 常用的回调函数
二、函数高级
1.原型与原型链
1.原型
- 所有函数都有一个特别的属性:
prototype
: 显式原型属性,它是一个普通的对象
- 所有实例对象都有一个特别的属性:
__proto__
: 隐式原型属性,它也是一个普通的对象
- 所有引用类型,它的’_ _ proto_ _'属性指向它的构造函数的’prototype’属性
function Fn() {
}
var fn = new Fn()
console.log(Fn.prototype===fn.__proto__)//true
- 当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找
2.原型链
-
访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着__proto__这条链向上查找, 找到返回
- 如果最终没找到, 返回undefined
-
判断:
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
-
属性问题:
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
2.执行上下文
1.变量提升与函数提升
- 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
- 通过var xxx=function定义的函数,无函数提升
- 先有变量提升, 再有函数提升
2.执行上下文
函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 代码分类(位置)
全局代码
函数(局部)代码 - 全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值(fun), 添加为window的方法
- this==>window
- 开始执行全局代码
- 函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
对局部数据进行预处理- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 开始执行函数体代码
3.执行栈
可以参考博客
4.作用域与作用域链
- 1.分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
-
- 作用
- 隔离变量,不同作用域下同名变量不会有冲突
- 多个上下级关系的作用域形成的链, 它的方向是从内到外
- 查找变量时就是沿着作用域链来查找的
5.闭包
1.
-
理解:函数嵌套函数,内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制收回
-
产生闭包的条件:
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 执行函数
-
常见的闭包
- 将函数作为另一个函数的返回值
function fn1() { var a = 2 function fn2() { console.log(a) } return fn2 } fn1()
- 将函数作为实参传递给另一个函数调用
2.作用
1.闭包拥有全局变量的不被释放的特点
2.闭包拥有局部变量的无法被外部访问的特点
3.可以让一个变量长期在内存中不被释放
4.避免全局变量的污染,和全局变量不同,闭包中的变量无法被外部使用
5.私有成员的存在,无法被外部调用,只能直接内部调用
3.生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时
4.应用
定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包信n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
例如:
/**
* 自定义模块1
*/
function coolModule() {
//私有的数据
var msg = 'abc'
//私有的操作数据的函数
function doSomething() {
console.log(msg)
}
function doOtherthing() {
console.log(msg)
}
//向外暴露包含多个方法的对象
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
/**
* 自定义模块2
*/
(function (window) {
//私有的数据
var msg = 'abc'
var names = ['I', 'Love', 'you']
//操作数据的函数
function a() {
console.log(msg)
}
function b() {
console.log(names.join(' '))
}
//向外暴露包含多个方法的对象
window.coolModule2 = {
doSomething: a,
doOtherthing: b
}
})(window)
5.缺点:内存溢出与内存泄露
-
缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
-
内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
-
内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
三、对象高级
1.对象创建模式
- 方式一: Object构造函数模式
- 方法: 先创建空Object对象, 再动态添加属性/方法
- 适用场景: 起始时不确定对象内部数据
- 问题: 语句太多
var p = new Object()
p = {}
p.name = 'Tom'
- 方式二: 对象字面量模式
- 方法: 使用{}创建对象, 同时指定属性/方法
- 适用场景: 起始时对象内部数据是确定的
- 问题: 如果创建多个对象, 有重复代码
var p = {
name: 'Tom',
setName: function (name) {
this.name = name
}
}
- 方式三: 工厂模式
- 方法: 通过工厂函数动态创建对象并返回
- 适用场景: 需要创建多个对象
- 问题: 对象没有一个具体的类型, 都是Object类型
function createPerson(name, age) {
var p = {
name: name,
age: age,
setName: function (name) {
this.name = name
}
}
return p
}
var p1 = createPerson(‘Tom’, 12)
var p2 = createPerson(‘JAck’, 13)
- 方式四: 自定义构造函数模式
- 方法: 自定义构造函数, 通过new创建对象
- 适用场景: 需要创建多个类型确定的对象
- 问题: 每个对象都有相同的数据, 浪费内存
function Person(name, age) {
this.name = name
this.age = age
this.setName = function (name) {
this.name = name
}
}
var p1 = new Person('Tom', 12)
var p2 = new Person('Tom2', 13)
- 方式五: 构造函数+原型的组合模式
- 方法: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
- 适用场景: 需要创建多个类型确定的对象
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('TOM')
2.继承
方式1: 原型链继承
function Supper() { //父类型
this.superProp = 'The super prop'
}
//原型的数据所有的实例对象都可见
Supper.prototype.showSupperProp = function () {
console.log(this.superProp)
}
function Sub() { //子类型
this.subProp = 'The sub prop'
}
// 子类的原型为父类的实例
Sub.prototype = new Supper()
// 修正Sub.prototype.constructor为Sub本身
console.log(Sub.prototype.constructor == Supper)//true
Sub.prototype.showSubProp = function () {
console.log(this.subProp)
}
// 创建子类型的实例
var sub = new Sub()
// 调用父类型的方法
sub.showSupperProp()
// 调用子类型的方法
sub.showSubProp()
方式2: 借用构造函数继承
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age) {
Person.call(this, name, age) // this.Person(name, age)
}
var s = new Student('Tom', 20)
console.log(s.name, s.age)
方式3: 原型链+借用构造函数的组合继承
- 利用原型链实现对父类型对象的方法继承
- 利用call()借用父类型构建函数初始化相同属性
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
Student.prototype.setPrice = function (price) {
this.price = price
}
var s = new Student('Tom', 12, 10000)
s.setPrice(11000)
s.setName('Bob')
console.log(s)
console.log(s.constructor)
四、线程机制与事件机制
1.线程与进程
- 进程:
- 程序的一次执行, 它占有一片独有的内存空间
- 可以通过windows任务管理器查看进程
- 线程:
- 是进程内的一个独立执行单元
- 是程序执行的一个完整流程
- 是CPU的最小的调度单元
- 关系
- 一个进程至少有一个线程(主)
- 程序是在某个进程中的某个线程执行的
2.浏览器内核模块组成
- 主线程
- js引擎模块 : 负责js程序的编译与运行
- html,css文档解析模块 : 负责页面文本的解析
- DOM/CSS模块 : 负责dom/css在内存中的相关处理
- 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
- 分线程
- 定时器模块 : 负责定时器的管理
- DOM事件模块 : 负责事件的管理
- 网络请求模块 : 负责Ajax请求
3. js线程
- JavaScript的单线程,与它的用途有关。
- 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。
- 这决定了它只能是单线程,否则会带来很复杂的同步问题
- js是单线程执行的(回调函数也是在主线程)
- H5提出了实现多线程的方案: Web Workers
- 只能是主线程更新界面
4.定时器问题:
- 定时器并不真正完全定时
- 如果在主线程执行了一个长时间的操作, 可能导致延时才处理
5.事件处理机制
- 代码分类
- 初始化执行代码: 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
- 回调执行代码: 处理回调逻辑
- js引擎执行代码的基本流程:
- 初始化代码===>回调代码
- 模型的2个重要组成部分:
- 事件管理模块
- 回调队列
- 模型的运转流程
- 执行初始化代码, 将事件回调函数交给对应模块管理
- 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
- 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
6.H5 Web Workers
- 可以让js在分线程执行
- Worker
var worker = new Worker('worker.js'); worker.onMessage = function(event){event.data} : 用来接收另一个线程发送过来的数据的回调 worker.postMessage(data1) : 向另一个线程发送数据
- 问题:
- worker内代码不能操作DOM更新UI
- 不是每个浏览器都支持这个新特性
- 不能跨域加载JS
总结
以上就是JS高级的笔记