数据类型分类
-
基本(值)类型
String,Number,boolean,undefined,null -
对象(引用)类型
Object:任意对象Function:一种特别的对象(可以执行)Array:一种特别的对象(数值下标,内部数据是有序的)
数据类型的判断
-
typeof返回数据类型的字符串表达可以判断
undefined / 数值 / 字符串/布尔值不能判断
null与object -
instanceof返回布尔值 -
===可以判断undefined,null
-
undefined与null的区别?undefined代表定义未赋值null定义并赋值了,只是值为null -
什么时候给变量赋值为
null呢?-
初始赋值,表明将要赋值为对象。
-
结束前,被垃圾回收器回收
-
-
严格区别变量类型与数据类型?
-
数据的类型
-
基本类型
-
对象类型
-
-
变量的类型(变量内存值的类型)
-
基本类型:保存基本类型的数据
-
引用类型:保存的是地址值
-
-
-
什么是数据?
-
存储在内存中代表特定信息的,本质上是0101...
-
数据的特点:可传递,可运算
-
一切皆数据
-
-
内存中所有操作的目标:
-
数据
-
算术运算
-
逻辑运算
-
赋值
-
运行函数
-
-
什么是内存?
-
内存条通电以后产生的可存储数据的空间。(临时的)
-
内存的产生和死亡:内存条(电路板)-->产生内存空间-->存储数据-->断电-->内存空间和数据都消失
-
一块小内存的两个数据
-
内部存储的数据
-
地址值
-
-
-
内存分类
-
栈:全局变量和局部变量(空间较小)
-
堆:对象(空间较大)
-
-
什么是变量?
-
可变化的量,由变量名和变量值组成
-
每个变量都对应的一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据。
-
-
内存,数据,变量三者之间的关系
-
内存用来存储数据的空间
-
变量是内存的标识
-
-
问题:
var a = xxx,a内存中保存的是什么?-
xxx是基本数据,保存的是这个数据
-
xxx是对象,保存的是对象的地址值
-
xxx是一个变量,保存xxx的内存内容(可能是基本数据,也可能是地址值)
-
-
关于引用变量赋值问题
-
n个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据。
-
n个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象。
-
-
问题:在js调用函数时传递变量参数时,是值传递还是引用传递?
-
理解1:都是值(基本值/地址值)传递
-
理解2:可能是值传递,也可能是引用传递(地址值)
-
-
问题:js引擎如何管理内存?
-
内存生命周期
-
分配小内存空间,得到它的使用权
-
存储数据,可以反复进行操作
-
释放小内存空间
-
-
释放内存
-
局部变量:函数执行完自动释放(栈空间)
-
对象:成为垃圾对象-->垃圾回收器回收(堆空间)
-
-
-
什么是对象?
-
多个数据的封装体
-
用来保存多个数据的容器
-
一个对象代表现实世界中的一个事物
-
-
为什么要用对象?
- 统一管理多个数据
-
对象的组成
-
属性:属性名(字符串)和属性值(任意类型)组成。
-
方法:一种特别的属性(属性值是函数)
-
-
如何访问对象内部数据?
-
.属性名:编码简单,有时不能用
-
['属性名']:编码麻烦,能通用
-
-
问题:什么时候必须使用['属性名']的方式?
-
属性名包含特殊字符:-,空格
-
变量名不确定
-
-
什么是函数?
-
实现特定功能的n条语句的封装体
-
只有函数是可以执行的,其他类型的数据不能执行
-
-
为什么要用函数?
-
提高代码复用
-
便于阅读交流
-
-
如何定义函数?
-
函数声明
-
表达式
-
-
如何调用(执行)函数?
-
test():直接调用 -
obj.test():通过对象调用 -
new test():new调用 -
test.call/apply(obj):临时让test成为obj的方法进行调用
-
-
什么函数才是回调函数?
-
自己定义的
-
自己没有调用
-
但最终执行了(在某个时刻或某个条件下)
-
-
常见的回调函数?
-
dom事件回调函数 this ==>发生事件的dom元素
-
定时器回调函数 this==>window
-
ajax请求回调函数
-
声明周期回调函数
-
-
IIFE的理解-
全称:Immediately-Invoked Function Expression 立即调用函数表达式
-
也可以叫:匿名函数自调用
-
-
IIFE的作用-
隐藏实现
-
不会污染外部(全局)命名空间
-
用它来编码js模块
-
-
this是什么?-
任何函数本质上都是通过某个对象来调用的,如果没有直接指定的就是
window -
所有函数内部都有一个变量
this -
它的值是调用函数的当前对象
-
-
如何确定
this的值?test():window
p.test():p
new test():新创建的对象
p.call(obj):obj
-
js一条语句的后面可以不加分号
-
是否加分号是编码风格问题,没有应不应该,只有喜不喜欢
-
在下面2种情况下不加分号会有问题
-
小括号开头的前一条语句
-
中方括号开头的前一条语句
-
-
解决办法:在行首加分号
-
例子:vue.js库
函数的prototype属性
-
每个函数都有一个
prototype属性,他默认指向一个Object空对象(称为:原型对象) -
原型对象中有一个属性
constructor,他指向函数对象 -
给原型对象添加属性(一般都是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
-
每个函数
function都有一个prototype,即显式原型 -
每个实例对象都有一个
__proto__,可称为隐式原型 -
对象的隐式原型的值为其对应构造函数的显式原型的值
-
总结:
函数的
prototype属性:在定义函数时自动添加的,默认值是一个空Object对象。对象的
__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值。程序员能直接操作显式原型,但不能直接操作隐式原型(es6之前) 访问一个对象的属性时,
原型链
-
访问一个对象的属性时,现在自身属性中查找,找到返回。如果没有,再沿着__proto__这条链向上查找,找到返回。如果最终没有找到,返回undefined
-
别名:隐式原型链
-
作用:查找对象的属性(方法)
- 所有函数的
__proto__都是一样的 - 实例对象的隐式原型=构造对象的显式原型
-
函数的显式原型指向的对象默认是空
Object实例对象(但Object不满足) -
所有函数都是
Function的实例(包括Function) -
Object的原型对象是原型链的尽头,Object.__proto__ =null -
读取对象的属性值时:会自动到原型链中查找
-
设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接天界此属性并设置其值。
-
方法一般定义在原型中,属性一般通过构造函数定义在对象本身上。
1.instanceof是如何判断的?
表达式“A instanceof B
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
变量声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到
值:undefined
函数声明提升
通过function声明的函数,在之前就可以直接调用(var定义的不行)
值:函数定义(对象)
代码分类(位置)
全局代码
函数(局部)代码
全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
-
var定义的全局变量==>
undefined,添加为window的属性 -
function声明的全局函数==>赋值(fun),添加为window的方法 -
this==>赋值(window)开始执行全局代码
函数执行上下文(函数调用时产生)
-
在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
-
对局部数据进行预处理
-
形参变量==>赋值(实参)==>添加为执行上下文的属性
-
arguments==>赋值(实参列表),添加为执行上下文的属性 -
var定义的局部变量==>undefined,添加为执行上下文的属性 -
function声明的函数==>赋值(fun),添加为执行上下文的方法 -
this==>赋值(调用函数的对象)开始执行函数体代码
-
在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
-
在全局执行上下文(window)确定后,将其添加到栈中(压栈)
-
在函数执行上下文创建后,将其添加到栈中(压栈)
-
在当前函数执行完后,将栈顶的对象移除(出栈)
-
当所有的代码执行完后,栈中只剩下为window
先执行变量提升,再执行函数提升
递归调用:在函数内部调用自己
作用域的理解
-
就是一块“地盘”,一个代码段所在的区域
-
它是静态的(相对于上下文对象),在编写代码时就确定了
-
分类
-
全局作用域
-
函数作用域
-
没有块作用域(es6有了)
-
-
作用
- 隔离变量,不同作用域下同名变量不会有冲突
作用域与执行上下文
-
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时。
全局执行上下文环境是在全局作用域确定之后,js代码马上执行前创建。
函数执行上下文环境是在调用函数时,函数体代码执行之前创建。
-
作用域是静态的,只要函数定义好了就一直存在,且不会变化。
上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会自动释放。
-
联系
上下文环境(对象)是从属于所在的作用域
全局上下文环境==>全局作用域
函数上下文环境==>对应的函数使用域
作用域链的理解
多个上下级关系的作用域形成的链,它的方向是从下向上的(从内向外)
查找变量时就是沿着作用域链来查找的
查找一个变量的查找规则
在当前作用域下的执行上下文中查找对应的属性,如果还找不到就抛出找不到的异常。
闭包
-
如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(子)函数的变量(函数)时,就产生了闭包
-
闭包到底是什么?
使用Chrome调试查看
理解一:闭包是嵌套的内部函数(绝大多部分人)
理解二:包含被引用变量(函数)的对象(极少数人)
注意:闭包存在于嵌套的内部函数中
-
产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)
-
常见的闭包
-
将函数作为另一个函数的返回值
-
将函数作为实参传递给另一个函数调用
-
-
闭包的作用
-
使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
-
让函数外部都可以操作(读写)到函数内部的数据(变量/函数)
-
-
问题:
- 函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在,存在于闭包中的变量才可能存在
- 在函数外部能直接访问函数内部的局部变量吗?
不能,但是可以通过闭包让外部操作它。
-
闭包的生命周期
-
产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
-
死亡:在嵌套的内部函数成为垃圾对象时
-
-
闭包的应用:定义js模块
具有特定功能的js文件
将所有的数据和功能都封装在一个函数内部(私有的)
只向外暴露一个包含n个方法的对象或函数
模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。
9. 闭包的缺点及解决
-
缺点
-
函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
-
容易造成内存泄露
-
-
解决
-
能不用闭包就不用闭包
-
及时释放
-
内存溢出
-
一种程序运行出现的错误
-
当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
内存泄露
-
占用的内存没有及时释放
-
内存泄露积累多了就会容易导致内存溢出
-
常见的内存泄漏:
-
意外的全局变量
-
没有及时清理的计时器或回调函数
-
闭包
-
创建函数
-
Object构造函数模式套路:先创建空
Object对象,再动态添加属性/方法适用场景:起始时不确定对象内部数据
问题:语句太多
-
对象字面量模式
套路:使用{ }创建对象,同时指定属性/方法
适用场景:起始时对象内部数据时确定的
问题:如果创建多个对象,有重复代码
-
工厂模式
套路:通过工厂函数动态创建对象并返回
适用场景:需要创建多个对象
问题:对象没有一个具体的类型,都是Object类型
-
自定义构造函数模式
套路:自定义构造函数,通过new创建对象
适用场景:需要创建多个类型确定的对象
问题:每个对象都有相同的数据,浪费内存
-
构造函数+原型的组合模式
套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
适用场景:需要创建多个类型确定的对象
原型链继承
-
套路
-
定义父类型构造函数
-
给父类型的原型添加方法
-
定义子类型的构造函数
-
创建父类型的对象赋值给子类型的原型
-
将子类型原型的构造属性设置为子类型
-
给子类型原型添加方法
-
创建子类型的对象:可以调用父类型的方法
-
-
关键
- 子类型的原型为父类型的一个实例对象
function Parent(){ }
Parent.prototype.test = function(){ };
function Child( ){ }
Child.prototype = new Parent( );//子类型的原型指向父类型实例
Child.prototype.constructor = Child
var child = new Child( );
借用构造函数继承(假的):得到属性
-
套路:
-
定义父类型构造函数
-
定义子类型构造函数
-
在子类型构造函数中调用父类型构造
-
-
关键:
- 在子类型构造函数中通用call ()调用父类型构造函数
function Parent(xxx){ this.xxx = xxx}
Parent.prototype.test = function( ){ };
function Child(xxx,yyy){
Parent.call(this,xxx)// 借用构造函数 this.Parent(xxx)
}
var child =new Child('a','b'); //child.xxx为'a',但child没有test()
原型链+借用构造函数的组合继承
-
利用原型链实现对父类型对象的方法继承
-
利用super()借用父类型构建函数初始化相同属性
function Parent(xxx){this.xxx=xxx}
Parent.prototype.test = function(){};
function Child(xxx,yyy){
Parent.call(this,xxx);//借用构造函数 this.Parent(xxx)
}
Child.prototype = new Parent();//得到test()
var child = new Child();//child.xxx为'a',也有test()
new一个对象做了什么?
创建一个空对象
给对象设置__proto__,值为构造函数对象的prototype属性值,this.__proto__=Fn.prototype
执行构造函数体(给对象添加属性/方法)
线程
线程池:保存多个线程对象的容器,实现线程对象的反复利用
-
多线程
-
优点:能有效提升CPU的利用率,创建多线程开销
-
缺点:线程间切换开销,死锁与状态同步问题
-
-
js是单线程还是多线程?
-
js是单线程运行的
-
但使用H5中的
Web Workers可以多线程运行
-
-
浏览器内核由很多模块组成
主线程
-
js引擎模块:负责js程序的编译与运行
-
html,css文档解析模块:负责页面文本的解析 -
DOM/CSS模块:负责dom/ css在内存中的相关处理 -
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
分线程
-
定时器模块:负责定时器的管理
-
事件响应模块:负责事件的管理
-
网络请求模块:负责
ajax请求
-
-
定时器真是定时执行的吗?
-
定时器并不能保证真正定时执行
-
一般会延迟一丁点(可以接受),也有可能延迟很长时间(不能接受)
-
-
定时器回调函数是在分线程执行的吗?
- 在主线程执行的,js是单线程的
-
定时器是如何实现的?
- 事件循环模型
-
alert会暂停当前的主线程的执行,同时暂停计时,点击确定后,恢复程序执行和计时 -
alert是window的方法,在分线程不能调用
-
如何证明js执行时单线程的?
-
setTimeout()的回调函数是在主线程执行的 -
定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
-
-
为什么js要用单线程模式,而不用多线程模式?
-
js的单线程和它的用途有关
-
作为浏览器脚本语言,js的主要用途是鱼用户互动,以及操作
dom -
这决定了他只能是单线程,否则会带来很复杂的同步问题
-
-
代码的分类
-
初始化代码(同步代码):包含绑定
dom事件监听,设置定时器,发送ajax请求的代码 -
回调代码(异步代码):处理回调逻辑
-
-
js引擎执行代码的基本流程
-
先执行初始化代码:包含一些特别的代码 回调函数(异步执行)
-
设置定时器
-
绑定监听
-
发送ajax请求
-
后面在某个时刻才会执行回调代码
-
-
模型的2个重要组成部分:
-
事件管理模块
-
回调队列(起到了一个缓存的作用)
-
-
模型的运转流程
-
执行初始化代码,将事件回调函数交给对象模块管理
-
当事件发生时,管理模块会将回调函数及其数据添加到回调队列中
-
只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行
-
事件轮询(
event loop):从任务队列中循环取出回调函数放入执行栈处理中
-
-
H5规范提供了js分线程的实现,取名为:
Web Workers -
相关API
-
Worker:构造函数,加载分线程执行的js文件 -
Worker.prototype.onmessage:用于接收另一个线程的回调函数 -
Worker.prototype.postMessage:向另一个线程发送消息 -
worker.onMessage=function(event){event.data}:用来接受另一个线程发送过来的数据的回调
-
-
不足
-
worker内代码不能操作DOM(更新UI) -
不能跨域加载js
-
不是每个浏览器都支持这个新特性
-