JavaScript基础+高级(李立超2016)
-
JS简介
-
语言发展:纸带机-汇编语言-现代语言
-
JS起源:诞生于95年,主要是处理网页中的前端验证
- 检查用户输入内容是否符合一定的规则
- 用户名的长度、密码的长度、邮箱的格式
-
Netscape:LiveScript
-
Sun:Java-JavaScript
-
Microsoft:JScript
-
ECMAScript:简称ES,几个公司形成一个组织发布JS标准
-
Chome浏览器,使用V8引擎实现JavaScript
-
JavaScript:
- ECMAScript
- DOM
- BOM
-
特点:
- 解释性语言(不用编译,写完直接运行)
- 动态语言(弱类型语言)
- 基于原型的面向对象
-
-
HelloWorld
- 在script标签中编写
- 浏览器窗口弹出:alert(’HelloWorld’)
- 注释 //
- 页面中输出:document.write(’HelloWorld’)
- 控制台输出:console.log(’HelloWorld’)
-
JS编写位置
- 元素结构中:JS代码可以写到标签的onclick属性中(不推荐)
- script标签中(练习用)
- 外部JS文件中,通过script标签引入(开发用)
- JS代码是按顺序从上到下执行的
-
基本语法
- 注释:单行注释和多行注释 // /* */,可以通过注释对代码进行一些简单的调试
- JS中严格区分大小写
- JS中每一条语句以分号结尾(不写其实也行,但可能出现小问题)
- JS中会忽略多个空格和换行,可以利用空格和换行对代码进行格式化美化
-
字面量和变量
-
字面量:都是不可改变的值
- 例如12345
- 字面量可以直接使用,但一般不会直接使用,而是存入变量中
-
变量:保存字面量,变量的值可以任意改变
- 变量方便使用,开发中用变量保存字面量
-
声明变量:
- var 有变量提升
- let 没有变量提升,有块级作用域,声明的变量可更改
- const 没有变量提升,有块级作用域,声明的变量不可更改且声明时必须有初始值
-
-
标识符
-
JS中所有可以由我们自主命名的都可以称为是标识符
-
变量名、函数名、属性名
-
需要遵守一定的规则
-
标识符可以函数字母、数字、_、$
-
标识符不能以数字开头
-
标识符不能是ES中的关键字或保留字
-
标识符一般都采用驼峰命名法
- 小驼峰:首字母小写,后续单词首字母大写
- 大驼峰:所有单词首字母都大写
-
JS底层保存标识符时实际上采用的Unicode编码
-
-
-
数据类型(基本数据类型和引用数据类型)
-
String字符串
- 在JS中字符串需要使用引号引起来
- 相同类型引号不同嵌套,但是单双引号可以嵌套
- 字符串中可以使用\作为转义字符,表示一些特殊符号
- \n 换行 \t tab键 \’ \” \
-
Number数值
-
Boolean布尔值
-
Null空值
-
Undefined未定义
-
Object对象
-
Symbol
-
BigInt
-
-
162、变量提升与函数提升
1、通过var定义的变量,在定义语句前可以访问到
2、通过function声明的函数,在之前可以直接调用
-
163、执行上下文
1、全局代码和局部代码
2、全局执行上下文(预处理操作)
- 执行全局代码前将window确定为全局执行上下文
- var定义的全局变量添加为window的属性
- function声明的全局函数添加为window的方法
- this为window
- 开始执行全局代码
3、函数执行上下文(函数要执行才会创建空间,预处理操作)
- 执行函数体之前,创建对应的函数执行上下文对象(虚拟封闭的区域,存在于栈中)
- 形参变量,赋值实参,添加为执行上下文的属性
- arguments,赋值实参列表,添加为执行上下文的属性
- var定义的局部变量undefined,添加为执行上下文的属性
- function声明的函数,复制,添加为执行上下文的方法
- this,赋值为调用函数的对象
-
164、执行上下文栈
- 定义函数不产生,调用执行函数时产生执行上下文
- 1、全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象(后进先出)
- 2、在全局执行上下文window确定后,将其添加到栈中(压栈)
- 3、在函数执行上下文创建后,将其添加到栈中(压栈)
- 4、在当前函数执行完后,将栈顶的对象移除(出栈)
- 5、当所有代码执行完后,栈中只剩下window
-
165、题目练习
1、依次输出什么,产生了几个执行上下文?
- 递归调用:在函数内部调用自己
- 栈内有5个执行上下文,依次弹出的过程中又打印了一次
2、先执行变量提升,再执行函数提升
3、undefined
4、报错,c被赋值为1,不能作为函数执行
-
166、复习
-
原型与原型链
-
原型prototype
- 原型对象,是对象Object的实例,即执行了this.prototype = {}
- 执行函数定义和执行函数
-
显式原型和隐式原型
- 构造函数的prototype是显式原型
- 实例的__proto__是隐式原型(this.proto === Fn.prototype)
- 两者相等或指向同一个对象
-
原型链
- 隐式原型指向的链
- 帮助我们查找对象的属性和方法
- 构造函数/原型/实例对象的关系
- 原型继承
-
instanceof
- 实例和构造函数之间的关系
- 沿着原型链查找,判断并返回布尔值
-
-
执行上下文与执行上下文栈
-
变量提升与函数提升
- 变量先提升,函数再提升(变量声明在上面,函数声明在下面)
-
执行上下文(预处理)
- 全局执行上下文和函数执行上下文
- 收集全局变量,函数和this
- 收集形参和局部变量,函数,this和arguments
-
执行上下文栈
- 管理和保存执行上下文对象
- 后进先出
-
练习题
- 有一些迷惑性
-
-
-
167、作用域与作用域链
-
作用域
- 1、一块地盘,代码所在的区域,编码时就确定了
- 2、全局作用域和函数作用域(ES6:块作用域)
- 3、跟执行上下文一样也是n+1个(n指函数,1指全局)
- 4、隔离变量,不同作用域下同名变量不会有冲突
-
作用域与执行上下文
1、函数作用域在函数定义时就已确定;执行上下文是调用函数时代码执行前确定
2、作用域是静态的,定义好就存在;执行上下文是动态的,调用函数存在
-
作用域链
- 嵌套的作用域由内向外的过程
-
-
168、练习
- 全局中定义一个函数,将其传入另一个函数中执行,查找变量查找的是全局中的变量
- 作用域在定义函数时就确定了,而不是在调用函数时确定,所以查找变量是根据定义函数时确定的作用域去查找
-
169、闭包
- 两大神兽:原型和闭包
- 案例:遍历按钮,定义数组变量只计算一次,按钮绑定点击函数,var定义的绑定回调函数执行时for循环已经执行完毕;
- 解决方法:代码用括号括起来作为立即执行函数,传入i作为参数;循环遍历+监听,用闭包实现;ES6中用let定义i,形成块级作用域
-
170、理解闭包(closure)
-
一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包
-
使用chrome调试查看
-
闭包是嵌套的内部函数(大多数人,理解一)
-
闭包是包含被引用变量的对象(极少数人,理解二)
-
闭包存在于嵌套的内部函数中
-
产生闭包的条件:
- 1、函数嵌套
- 2、内部函数引用了外部函数的数据,执行了外部函数(产生外部函数数据)(变量/函数)
- 3、执行函数定义就会产生闭包(不用调用内部函数)
-
-
171、常见的闭包
- 1、将内部函数作为外部函数的返回值
- 调用栈:执行上下文栈,函数定义早已执行,
- 2、将函数作为实参传递给另一个函数调用
-
172、闭包的作用
- 1、使局部变量在函数执行完后,仍然存在于内存中(延长局部变量的生命周期)
- 2、让函数外部可以操作到函数内部的数据
- return返回值保存给变量,变量指向了函数对象
- 函数外部不能直接访问函数内部局部变量,但是通过闭包可以操作
-
173、闭包的生命周期
- 1、产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
- 2、死亡:包含闭包的函数对象称为垃圾对象f=null
-
174、闭包的应用
- JS模块,具有特定功能的JS文件,将所有数据和功能封装在一个函数内部(私有的)
- 1、私有数据
- 2、操作数据的函数
- 3、向外暴露对象(给外部使用的方法)
- window.module = { 方法 }
-
175、闭包的缺点和解决
-
缺点:
- 函数执行完内部的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄漏(内存被白白占用,用不上)
-
解决:
- 能不用闭包就不用
- 及时释放f=null(让内部函数成为垃圾对象,浏览器回收闭包)
-
内存溢出和内存泄漏
-
内存溢出:程序运行时出现的错误
- 程序运行需要的内存超过了剩余内存,就会抛出内存溢出的错误、
-
内存泄漏:占用的内存没有及时释放
- 内存泄漏积累多了就容易导致内存溢出
- 常见的内存泄漏:意外的全局变量;没有及时清理的计时器或回调函数;闭包
-
-
-
176、练习
- var that = this
- 函数嵌套
- 内部函数引用了外部函数的变量
- 调用外部函数才能产生闭包
-
177、对象创建模式
-
Object构造函数模式:
- 先创建空Object对象,再动态创建属性和方法(不确定对象内部数据,但是语句太多)
-
对象字面量模式:
- 使用{}创建对象,同时指定属性和方法(对象内部数据确定,但如果创建多个对象,有重复代码)
-
工厂模式:(不用)
- 通过工厂函数动态创建对象并返回(传入参数,可以创建多个对象,不能区分具体类型,都是Object类型)
-
自定义构造函数模式
- 自定义构造函数,通过new创建对象(传入参数,可以创建多个对象,但每个对象都有相同数据,浪费内存(方法,引用数据))
-
构造函数+原型的组合模式
- 自定义构造函数,属性再构造函数中初始化,方法添加到原型上prototype(可以创建多个类型确定的对象,且不浪费内存,原型上是同样的方法)
-
-
178+179、继承模式
-
原型链继承
- 定义父类型构造函数function Supper() {}
- 定义子类型构造函数function Sub() {}
- 父类型添加属性,原型上添加方法
- 子类型添加属性,原型上添加方法
- 子类型创建实例对象
- 子类型的原型对象成为父类型的实例(Sub.prototype = new Supper())
- 这样,子类型的实例就能访问父类型的原型方法
- 让子类型的原型constructor指向子类型,这样才完整(修正)
- Sub.prototype.constructor = Sub
-
借用构造函数继承(假的)
- 定义父类型构造函数Person () {}
- 定义子类型构造函数Student() {}
- 在子类型构造函数中调用父类型构造Person.call(this, name, age)
-
组合继承(真正使用的)
- 原型链+借用构造函数的组合继承
- 1、利用原型链实现对父类型对象的方法继承
- 2、利用call()实现借用父类型构建函数初始化相同属性
-
-
180、JS高级复习
-
作用域与作用域链
- 作用域:代码定义时即确定了,和调用执行没有关系
- 作用域:代码区域:全局作用域和函数作用域。隔离变量,避免命名污染。
- 作用域嵌套,函数中嵌套函数
- 作用域链:查找变量,由内向外。window.a返回undefined,直接找a报错。n+1个作用域
- 执行上下文:调用函数时产生
-
闭包
-
循环遍历监听事件,按钮属性引用函数,闭包一直未释放
-
函数嵌套
-
内部函数引用外部函数变量(数据)
-
闭包本质上是包含引用变量的对象(极少数人)
-
外部函数调用多少次就有多少个闭包,作用:闭包延长变量生命周期;外部操作内部变量
-
常见闭包
- 将函数作为另一个函数的返回值:return出去
- 将函数作为实参传递给另一个函数调用
-
闭包产生:嵌套内部函数定义代码执行完后就产生了(不是调用);闭包死亡:嵌套的内部函数成为垃圾对象时(f = null)
-
应用:自定义JS模块(JS文件)
- 特定功能的js文件
- 所有数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块使用者,通过模块暴露的对象调用方法来实现对应的功能
-
内存泄漏和内存溢出
- 内存溢出:不断创建数据,当运行需要的内存超过了剩余内存,就会抛出内存溢出的错误
- 内存泄漏:占用的内存没有及时释放;内存泄漏积累多了就容易导致内存溢出
-
-
对象高级
-
对象的创建模式
- Object构造函数模式const obj = {}
- 对象字面量模式const obj = {name: ‘Tom’}
- 构造函数模式function Person(name, age) {}
- 构造函数+原型的组合模式:方法定义到原型对象上
-
继承模式
-
原型链继承:得到方法:在原型对象上定义方法
-
借用构造函数:得到属性:this.xxx = xxx
-
原型链+借用构造函数组合模式:得到属性+得到方法
-
new一个对象后面做了什么:
- 创建一个空对象
- 给对象设置__proto__,值为构造函数的prototype
- 执行构造函数体(给空对象添加属性和方法)
-
-
-
-
181、闭包练习
- 太绕了,恶心,返回的对象中的方法,n引用外部函数的变量,要么一直没变,即没有变量接收并执行方法;要么变量接收了并执行方法传入参数之后,n就变了
-
182、线程机制
-
进程与线程:
- 进程process:程序的一次执行,占有一片独有的内存空间,可以通过任务管理器查看
- 线程thread:进程内的一个独立执行单元,是程序执行的一个完整流程,是CPU最小的调度单元
- 一个程序可以是单进程或多进程,一个进程可以是单线程或多线程的
- 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程,主线程,进程启动后自动创建
- 一个进程内的数据可以供多个线程共享
- 多个进程间数据不能直接共享
- 线程池thread pool:保存多个线程对象的容器,实现线程对象的反复利用
- 多进程与多线程?比较单线程与多线程?
- JS是单线程运行,但是H5中Web Workers可以多线程运行
- H5包含html、css和js中的新语法
- 浏览器是多线程运行的
- 浏览器有单进程的firefox,也有多进程的chrome和新版IE
-
-
183、浏览器内核
-
支撑浏览器运行的最核心程序
-
不同浏览器内核可能不一样
- webkit - Chrome/Safari
- Gecko - firefox
- Trident - IE
- 国内:Trident+webkit
-
内核由很多模块组成
- js引擎模块:负责js程序的编译与运行
- html、css文档解析模块:负责页面文本的解析
- DOM/CSS模块:负责dom/css在内存中的相关处理
- 布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
- 以上都是主线程
- 以下是分线程
- 定时器模块:负责定时器的管理setInterval()
- DOM事件响应模块:负责事件的管理
- 网络请求模块:负责ajax请求
-
-
184、定时器引发的思考
-
定时器真的是定时执行的吗?
- 一般会延迟一丁点(可以接受),如果有长时间工作,可能延迟长时间(不能接受)
-
定时器回调函数是在主线程执行的,js是单线程的
-
定时器是在事件循环模型
-
定时器并不是真正定时
-
-
185、JS是单线程的
-
alert暂停当前主线程执行,也暂停了计时器的计时,点击确定后恢复程序执行和计时
-
代码的分类:
-
初始化代码
-
回调代码
-
先执行初始化代码:包含一些特别的代码
- 设置定时器(到点后执行函数)
- 绑定事件监听(事件触发后执行函数)
- 发送ajax请求
-
后面在某个时刻才执行回调代码
-
回调函数异步执行(初始化代码执行完后才执行)
-
-
为什么js要用单线程模式而不是多线程模式
- 与它的用途有关
- 作为浏览器脚本语言,js的主要用途是与用户互动以及操作DOM
- 如果多线程,某个线程操作了p元素,那么其他线程不允许操作p元素,这个事情会很麻烦,而且代码会复杂很多
- 因此js只能是单线程,要有先后顺序
-
-
186、事件循环
- 三部分:JS堆和调用栈(JS主线程)、WebAPIs(DOM/ajax/setTimeout)(分线程执行,浏览器负责)、回调队列(回调函数待执行)
- 事件轮询模型
- 初始化代码执行完后,才会去回调队列中执行待处理的回调函数,JS是单线程的
- 执行栈、浏览器内核、任务队列、消息队列、事件队列、事件轮询、事件驱动模型、请求响应模型(浏览器和服务器)
-
187、Web Workers
-
斐波那契数列 f(n) = f(n-1) + f(n-2)
-
递归调用:函数内部调用自己return n ≤ 2 ? 1 : f(n-1) + f(n-2)
-
递归效率较低,主线程不断计算,页面无法操作
-
webworkers是H5提供的js多线程解决方案
-
将计算量大的代码交给webworkers运行而不冻结用户界面
-
主线程代码
-
创建worker对象
-
const worker = new Worker(’worker.js’)
-
向分线程发送消息
-
worker.postMessage(number)
-
接收worker传过来的数据函数
-
worker.onmessage = function(event) { alert(event.data) }
-
分线程代码(做长时间的计算)
-
const onmessage = function(event) { const number = event.data; const result = f(number); postMessage(result) }
-
分线程中看不到window,因此不能用window上的alert等方法
-
缺点:
- 慢
- 不能跨域加载JS
- 不是每个浏览器都支持这个新特性
- worker内代码不能访问DOM(更新UI)
-
虚拟路径,域名
-
-
188、复习