第一部分:基础总结深入
1.1 数据类型
-
基本类型(值类型)
String:任意的字符串 Number:任意的数字 Boolean:true、false Null:null Undefined:undefined
-
对象(引用类型)
Object: 任意对象都是Object类型 Function:一种特别的对象(特别在哪?可以调用执行) Array:
-
判断
typeOf undefined、数值、字符串、布尔值
instanceOf 专门用来判断对象的具体类型:函数?数组?
== === 可以判断undefined、null的数据类型
1.2 数据类型相关问题
1、null和undefined区别
var a
console.log(a) //undefined
a=null
console.log(a) //null
2、什么时候给变量设值为null
var b=null //初始赋值为null,表名将要赋值为对象
b=[1,2,3,4]
b=null //释放数组所占用的内存,让b指向的对象成为垃圾对象(被垃圾回收器回收)
console.log("b",b)
3、严格区分变量类型和数据类型
-
数据的类型
基本类型 对象类型
-
变量的类型
变量本身没有类型 基本类型:保存的是基本类型的数据 引用类型 :保存的是地址值 var c={} console.log(typeof c) //object var d=function(){} console.log(typeof d) //function
1.3 数据、变量、内存三者之间的关系
什么是数据?
存储在内存中代表特定信息的,本质上是0101....
数据特点:可传递、可运算
一切皆数据
内存中所有操作的目标:数据
什么是内存
内存条通电后产生的可存储数据的空间
内存产生和死亡:内存条===》通电===》产生内存空间===》存储数据===》处理数据===》断电 ===》内存空间和数据都消失
一块内存有两个数据
1、内部存储的数据
2、地址值数据
内存的分类
栈:全局变量和局部变量
堆:对象
什么是变量
可变化的量,变量名和变量值组成
每个变量都定义一块小内存
变量名用来查找对应的内存
变量值就是内存中保存的数据
var age=18
数据、变量、内存关系?
内存是用来存储数据的空间
变量是内存的标识,变量名查找内存空间
关于赋值和内存的问题
var a=XXX,a内存中到底保存的是什么
XXX是基本数据,保存的就是这个数据
XXX是对象,保存的是对象的地址
XXX是变量 保存的可能是基本类型数据,也可能是地址
var b='abc'
var b=function(){}
var a=b
关于引用变量的赋值问题
var obj1={name:'TOM'}
var obj2=obj1 //将obj1内存的内容保存在obj2中,只不过obj1内存的内容是地址值
obj1.name='Jack'
console.log(obj2.name) //jack
两个引用变量指向同一个对象,其中一个变量修改了对象内部的数据,另一个对象看到的是修改之后的数据
var A={name:'vina'}
function fn(obj){
obj.name='bbb'
}
fn(A)
console.log(A) //{ name: 'bbb' }
var A={name:'vina'}
function fn(obj){
obj={name:'ppp'}
}
fn(A)
console.log(A) //{ name: 'vina' }
在js调用函数传递变量参数是,是值传递还是引用传递
理解1: 都是值传递,只是这个值有两种情况,一个基本类型值,一个地址值
理解2:可能是值传递,也可能是引用传递,只是看你怎么理解吧地址值传递给形参算是值传递还是引用传递,
var a=3
function fn(a){
a=a+1
}
fn(a)
console.log(a) //3
var obj={a:3}
function fn(obj){
obj.b=4
}
fn(obj)
console.log(obj) //{ a: 3, b: 4 }
JS引擎如何管理内存
-
内存生命周期
分配小内存空间,得到使用权 存储数据,可以反复进行操作 释放空间
-
释放内存
全局变量:是不会被释放的
var obj={} obj=null
局部变量:函数执行完自动释放
//执行完fn,b的内存空间自动释放,b所指向的对象是在后面的某个时刻,由垃圾回收器释放回收 function fn(){ var b=4 }
1.4 对象
1、什么是对象?
多个数据的封装体
用来保存多个数据的容器
一个对象代表现实世界中的一个事务
var obj= {
name:'Tom',
age:13,
setName:function(name){
this.name=name
}
setAge:function(age){
this.age=age
}
}
2、为什么要用对象?
对象是用来管理多个数据
obj.name='vina'
3、对象的组成?
属性:属性名(字符串)和属性值(任意类型)组成
(在语言的设计的时候允许属性名不写单引号、双引号,但是并不能改变属性名是字符串的事实)
方法:一种特别的属性(属性值是方法)
4、如何访问对象内部的数据?
对象.属性名(编码简单,有时不能用)
对象[age](编码麻烦,通用)
1、当属性名有横杠连接的时候,必须用 对象[age]
p['content-type']='text/json'
console.log( p['content-type'])
2、变量名不确定(变量名是一个变量)
var propName='myAge'
var value=18
p.propName=value(错误写法❌)
p[propName]=value(✅)
p.setName('richur')
console.log(obj.name,obj[age])
第二部分:函数高级
2.1 函数
1、什么是函数?
实现特定功能的n条语句的封装体
只有函数是可以执行的,其他类型的数据是不能执行的
2、为什么要用函数
提高代码复用
便于阅读交流
体现的就是封装的思想
3、如何定义函数
函数声明
function fn(){
console.log("aaa")
}
表达式
var fn2=function(){
console.log("fn2")
}
4、如何调用函数
test() //直接调用
obj.test() //通过对象调用
new test() new调用
test.call/apply(obj) //相当于obj.text() 这种调用方式只是临时让test成为obj的方法进行调用
var obj={}
function test2(){
this.age=12
}
test2.call(obj)
console.log(obj) //{ age: 12 }
// 可以让一个函数成为指定任意对象的方法进行调用
2.2 回调函数
1.什么函数才是回调函数
特点:
你定义了
你没有调用
但是他执行了
2.常见的回调函数
DOM事件回调
定时器回调函数
ajax请求回调函数
生命周期回调函数
2.3 IFEE
全称 immediately-Invoked Function Expression 立即回调函数表达式 作用:隐藏实现、不会污染外面的命名空间,用它编写js模块
(function(){ //匿名函数自调用
console.log("aaaa")
})()
为什么还要用函数呢?不直接输出console.log("aaaa")
隐藏实现
不会污染外面的命名空间
;(function(){
var a=1
function test(){
console.log(++a)
}
window.$=function(){ //向外暴露一个全局函数
return {
test:test
}
}
})()
$().test() //$是一个函数,$执行后返回的是一个对象
2.4 this
1.this是什么?
- 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this,他的值是调用函数的当前对象
2.如何确定this
-
this() :window
-
p.test() :新创建的对象
-
p.call(obj) :obj
3.1 原型与原型链
1、原型
-
任何函数都有一个prototype属性,它默认指向Object空对象(被称为:原型对象)
typeof Date.prototype //"object",有一堆属性, function fun(){ } console.log(fun.prototype) //默认指向一个Object空对象(没有我们的属性) fun.prototype.test=function(){} console.log(fun.prototype)
-
原型对象中有一个属性constructor,他指向函数对象
console.log(Date.prototype.constructor===Date) //true console.log(Date.prototype.constructor===fun) //true
-
给原型对象添加属性(一般是方法===》实例对象可以访问)
fun.prototype.test=function(){ console.log("test") } //创建实例f1 var f1=new fun() f1.test()
2、显式原型和隐式原型
-
每个函数都有一个prototype,是在定义函数时默认添加的,即显式属性,默认指向一个空的Object对象
function Fn(){ //内部语句:this.prototype={} } console.log(Fn.prototype)
-
每个实例对象都有一个__proto__,是在创建对象时添加的,可以称为隐式原型,默认指向一个空的Object对象
var fn=new Fn() //内部this.__proto==Fun.prototype console.log(fn.__proto__)
-
对象的隐式原型的值为其对应构造函数显式原型的值
console.log(Fn.prototype===fn.__proto__) //true
-
通过实例对象调用test方法
//程序员可以直接操作显示原型,但是不能直接操作隐式原型(ES6之前) Fn.prototype.test()=function(){ console.log('test') } fn.test()
3、原型链(隐式原型链)
当我们去访问一个对象的属性的时候,首先根据这个属性找到属性值,属性值可以为函数。
作用:查找对象属性(方法) 查找过程:现在自身属性中查找,找到就返回,如果没有,再沿着__proto__这条原型链查找,找到返回,如果最终没有找到,返回undefined
- 原型链
Object.prototype.proto //null,所以Object的原型对象就是尽头
- 创建对象
两种创建实例对象方法,实例对象都有隐式原型属性,隐式原型属性指向的是Object原型
var o1=new Object()
var o2={}
- 定义function
function Foo(){ }
var Foo=new Function()
Function=new Function() //只有这个显示原型等于它的隐式原型
//所有函数的__proto__都是相等的,因为都是new Function产生的
首先创建函数对象Foo,函数对象有一个显示原型属性,默认是一个空的Object对象. 函数对象也是Function的实例对象,Function是函数类型
-
原型继承
构造函数的实例对象自动拥有构造函数原型对象的属性(方法) 利用的就是原型链
复习:
1、 函数的显式原型指向的对象:默认是空的Object实例对象(但是不适用于Object这个函数)
Fn.prototype instanceof Object //true
Object.prototype instanceof Object //false,只有Object是个例外 Function.prototype instanceof Object //true
2、
-
Function是new Function()创建的,所以Function是自身的属性
-
所有函数都是Function的实例(包括Function本身),没有例外
Function.__proto__ ===Function.prototype
-
Object的原型对象是原型链的尽头
Object.prototype.__proto__ ===null
3、
-
读取对象的属性值的时候:会自动查找原型链中查找
-
设置对象的属性值,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并且设置值
-
方法一般定义在原型中,属性一般通过构造函数定义在对象本身上 (因为每个对象方法一样,属性值不一样)
4、探索instanceOf
instanceOf是如何判断的?
-
表达式: A instanceOf B
-
如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false
B是函数,有显式原型 A是实例,有隐式原型,实例对象可以沿着__proto__这条原型链
function Foo(){}
var f1=new Foo()
console.log(f1 instanceof Foo) //true
console.log(f1 instanceof Object) //true
console.log(Object instanceof Function) //true
console.log(Object instanceof Object) //true
console.log(Function instanceof Function) //true
console.log(Function instanceof Object) //true
function Foo(){}
console.log(Object instanceof Foo) //false
上图可以看出Object有双重身份,可以一步到原型链尽头,也可以两步,通过Function到尽头
console.log(Object instanceof Function) //true
console.log(Object instanceof Object) //true
5、面试题
A.prototype={n:2,m:3} //重新给function.prototype赋值
3.2 执行上下文和执行上下文栈
1、变量提升和函数提升
变量提升
通过var定义(声明)的变量在定义语句之前就可以访问到,只是值为undefined
var a=3
console.log(3) //undefined
函数提升
通过function声明的函数,在之前就可以直接调用
值:函数定义
f1() //不可调用,变量提升
f2() //可调用,函数提升
var f1=function(){
console.log("f1")
}
function f2(){
console.log("f2")
}
2、执行上下文
-
在执行全局代码前把window确定为全部执行上下文
-
对全局数据进行预处理
-
var 定义的全局变量=undefined,添加到window属性
-
function声明的全局函数==》赋值,添加未window的方法
-
this=window
函数执行上下文
*在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
*对局部数据进行预处理
1、形参变量赋值为实参数据,添加未执行上下文的属性
2、arguments代表所有的实参列表,函数体执行之前arguments已经在了
3、var定义的局部变量提升赋值为undefined,添加未执行上下文的属性
4、function声明的全局函数==》赋值,添加未执行上下文的属性
5、this指定为调用函数的对象,直接调用就是window
3、执行上下文栈
*在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
*在全家有执行上下文(window)确定后,将其添加到栈中
* 在函数执行上下文创建后,将其添加到栈中(压栈)
* 当前函数执行完,将栈顶的对象移除
当所有的代码执行完,栈中只剩下window
4、面试题
console.log('gb:'+i)
var i=0
foo(1)
function foo(i){
if(i==4){
return
}
console.log('fb:'+i)
foo(i+1)
console.log('fe:'+i)
}
gb:undefined
fb:1
fb:2
fb:3
fe:3
fe:2
fe:1
变量提升和函数提升先执行哪一个?
先执行变量提升,再执行函数提升
function a(){
}
var a;
typeof a //function
if(!(b in window)){
var b=1
}
console.log(b) //undefined
var c=1
function c(c){
console.log(c)
}
c(2) //报错,c不是一个函数,原因是变量和函数都提升了,赋值=1在后面,所以c不是function
3.3 作用域和作用域链
作用域
作用:隔离作用域,在不同作用域中,同名变量不会冲突
console.log('------',c) //不报错,undefined,说明下面不存在块作用域
if(true){
var c=3
}
console.log('------',c)
作用域、和执行上下文区别
区别
1、全局作用域未每个函数都创建自己的作用域,作用域在定义的时候已经确定的,而不是在函数调用时
全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建的
函数执行上下文是在调用函数时,函数体执行之前创建
2、作用域是静态的,只有函数定义好了就一直存在,而且不会再变化
上下文环境是动态的,调用函数时创建的,函数调用结束后上下文环境就会被释放
联系
执行上下文是从属于所在的作用域
作用域链
理解:
多个上下级的关系的作用域形成链,它的方向是从下向上的(从内到外)
查找变量时沿着作用域链来查找
查找变量的规则
在当前作用域的执行上下文中查找对应的属性,如果找到直接返回,否则进入2
在上一级作用域的执行上下文忠查找对应的属性,如果有直接返回,否则进入3
再执行2的相同操作,知道全局作用域,如果还找不到就抛出找不到的异常
3.4 闭包
<!-- 点击按钮 -->
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script>
var btns=document.getElementsByTagName('button') //btns是一个伪数组
//遍历加监听
// for(var i=0,length=btns.length;i<length;i++){
// var btn=btns[i]
// btn.onclick=function(){
// console.log("我被点击啦",i)
// }
// }
// 第一种解决方法:闭包
for(var i=0,length=btns.length;i<length;i++){
(function(i){
var btn=btns[i]
btn.onclick=function(){
console.log("我被点击啦",i)
}
})(i)
}
</script>
1.如何产生闭包?
1. 当已和嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
2. 调用外部函数
2. 闭包到底是什么?
理解1:闭包是嵌套的内部函数(大多数人)
理解2:包含被引用变量(函数)的对象(极少数人)
注意:闭包存在于嵌套的内部函数中
3.产生闭包的条件
将函数作为另一个函数的返回值
将函数作为实参传递给另一个函数调用
````
function fn1(){
var a=2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f() //3
f() //4
fn1()
//整个过程中产生了几个闭包? 判断几个闭包,主要看外部函数执行了几次
```
4.闭包作用
1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长局部变量的生命周期)
2.在函数外部能直接访问团函数内部的局部变量吗?通过闭包,我们可以函数外部操作函数内部数据
3.函数执行完后,函数内部的变量是否还存在?一般不存在,存在于闭包中的变量才有可能存在
```
function fn1(){
//此时闭包已经产生了,因为内部函数提升已经创建了
var a=2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f() //3 //f指向了fn2的返回值
f() //4
```
5.闭包的生命周期
产生:在嵌套内部函数定义执行完就产生了(不是在调用)
死亡:再嵌套的内部函数成为垃圾对象时
function fn1(){
//此时闭包已经产生了,因为内部函数提升已经创建了
var a=2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f() //3 //f指向了fn2的返回值
f() //4
f=null //闭包死亡(包含闭包的函数对象成为了垃圾对象)
function fn1(){
//此时闭包已经产生了,因为内部函数提升已经创建了
var a=2
fn2= function(){
a++
console.log(a)
} //在这里闭包产生了
return fn2
}
var f=fn1()
f() //3 //f指向了fn2的返回值
f() //4
f=null //闭包死亡(包含闭包的函数对象成为了垃圾对象
6.闭包的应用
具有特定功能的js文件
将所有的数据和功能都封装在一个函数内部(私有)
只向外暴露已和包含n个方法的对象
模块的使用者通过模块暴露的对象调用方法来实现相应的功能
方法1:
function myModule(){
var msg="Hello world"
function something(){
console.log("something"+msg.toLowerCase())
}
function otherthing(){
console.log("otherthing"+msg.toUpperCase())
}
//向外暴露对象(给外部使用的方法)
return {
something:something,
otherthing:otherthing,
}
}
//外部访问(必须先执行函数)
var fn=myModule()
fn.something()
fn.otherthing()
方法2
(function (){
var msg="Hello world"
function something(){
console.log("something"+msg.toLowerCase())
}
function otherthing(){
console.log("otherthing"+msg.toUpperCase())
}
//向外暴露对象(给外部使用的方法)
window.module2= {
something:something,
otherthing:otherthing,
}
})()
//外部使用(使用起来更方便)
module2.something()
module2.otherthing()
7.闭包的缺点
函数执行完后,函数内的局部变量没有释放,占用内存的时间长
容易造成内存泄漏
解决
能不用闭包就不用
及时释放
8.内存溢出和内存泄漏
内存溢出
一种程序运行出现的错误
当程序运行需要的内存超出了剩余的内存时,就抛出内存溢出的错误
var obj={}
for(var i=0;i<10000;i++){
obj[i]=new Array(100000)
}
内存泄漏
占用的内存没有及时释放
意外的全局变量
没有及时清理的计时器或者回调函数
闭包
//意外的全局变量
function fn(){
a=3
console.log(a)
}
9.面试题
var name='The Window'
var object={
name:'My Object',
getNameFunc:function(){
return function(){
return this.name
}
}
}
console.log(object.getNameFunc()()) //The window
var name2='The Window'
var object2={
name2:'My Object',
getNameFunc:function(){
var that=this;
return function(){
return that.name2
}
}
}
console.log(object2.getNameFunc()()) //My Object
第三部分: 面向对象高级
3.1 对象创建模式
第一种写法:
-
套路:先创建空Object对象,再动态添加属性/方法
-
适用场景:起始是不确定对象的内部数据
-
问题:语句太多
var p=new Object() p.name='vina' p.age=13 p.setName=function(name){ this.name=name } p.setName('Jack') console.log(p.name,p.age) //Jack 13
第二种写法
-
套路:使用{}创建对象,同时指定属性和方法
-
适用场景:起始是确定对象的内部数据
-
问题:如果创建多个对象,代码很重复
var p={ name:'Vina', age:13, setName:function(name){ this.name=name } } p.setName('Jack') console.log(p.name,p.age) //Jack 13
第三种写法:
- 套路:使用工厂函数创建对象并返回
- 适用场景:需要创建多个对象
- 问题:对象没有一个具体的类型,都是Object类型
var obj={
name:name,
age:age,
setName:function(name){
this.name=name
}
}
return obj
}
var p1=createPerson('Tom',13)
var p1=createPerson('Vina',18)
第四种写法:
- 套路:自定义构造函数,通过new创建对象
- 适用场景:需要创建多个类型片确定的对象
- 问题:每个对象都是相同的数据,浪费内存
//定义人类型
function Person(name,age){
this.name=name
this.age=age
this.setName=function(name){
this.name=name
}
}
var p1=new Person('Tom',12)
p1.setName('Jack')
console.log(p1.name,p1.age)
//定义学生类型
function Student(name,age){
this.name=name
this.price=price
this.setName=function(name){
this.name=name
}
}
第五种写法:
- 套路:自定义构造函数,属性在函数中初始化,方法添加到原型中
- 适用场景:需要创建对个类型的确定对象
- 问题:
this.name=name
this.age=age
}
Person.prototype.setName=function(name){
this.name=name
}
var p1=new Person('Tom',23)
var p2=new Person('Jack',24)
分析:因为方法是写在原型里的,每个对象里只有一般属性,方法在原型里,指向同一个对象,节省内存
3.2 继承模式
方式1
套路
* 定义父类型构造函数
* 给父类型的原型添加方法
* 定义子类型的构造函数
* 创建父类型的对象赋值给子类型的原型
* 将子类型原型的构造属性设置为子类型
* 给子类型原型添加方法
* 创建子类型的对象:可以调用父类型的方法
关键
子类型的原型未父类型的一个实例对象
//父类型
function Supper(){
this.supProp='Supper property'
}
Supper.prototype.showSupperProp=function(){
console.log(this.supProp)
}
//子类型
function Sub(){
this.subProp='Sub property'
}
// var sub=new Sub()
// sub.showSupperProp( ) //sub.showSupperProp is not a function, 如果能调用就表示继承了父亲的方法
// sub.toString() //这个可以,是因为原型对象是一个object实例对象,toString在object原型上
//showSupperProp在Supper的原型上,如果想让sub能看到showSupperProp,必须让sub成为Supper的实例,Supper的实例就能看到原型上的方法,目的达成
Sub.prototype=new Supper()
Sub.prototype.constructor=Sub //让子类型的原型的Constructor指向子类型
Sub.prototype.showSubProp=function(){
console.log(this.subProp)
}
var sub=new Sub()
sub.showSupperProp() //Supper property
方式2
借用构造函数继承(假的继承)
套路:
定义父类型构造函数 定义子类型构造函数 在子类型构造函数中调用父类型构造
关键:
在子类型构造函数中通用super() 调用父类型构造函数
function Person(name,age){
this.name=name
this.age=age
}
function Student(name,age,price){
Person.call(this,name,age) //相当于this.Person(name,age),相当于Student借用了Person的构造函数,但是没有继承的
//this.name=name
//this.age=age
this.price=price
}
var s=new Student('Tom',20,14000)
console.log(s.name,s.age,s.price)
方式3:组合继承
原型链+借用构造函数的组合继承
- 1、利用原型链实现父类型片对象的方法继承
- 2、利用super()借用父类型构建函数初始化相同属性
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.name=name
// this.age=age
this.price=price
}
Student.prototype=new Person() //为了子类型能够看到父类型的方法 //目的为了得到方法
Student.prototype.constructor=Student //修正constructor属性
Student.prototype.setPrice=function(price){
this.price=price
}
var s=new Student('Tom',24,15000)
s.setName('Bob')
s.setPrice(1600)
console.log(s.name,s.age,s.price) //Bob 24 1600
第三部分:线程机制与事件机制
3.1 进程与线程
进程:程序一次执行,他占有了一片独有的内存空间 可以通过windows任务管理器查看
线程:是进程内的一个独立执行单元 是程序执行的一个完整流程 是CPU的最小的调度单元
如果一个进程里只有一个线程,这是单线程的
如果一个进程里只有多个线程,这是多线程的
- 我们应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以供其多个线程直接共享
- 多个线程之间的数据是不能直接共享的
- 线程池:保存多个线程对象的容器,实现线程对象的反复利用
何为多进程?
一个应用程序可以同时启用多个实例运行
何为多线程?
一个进程内,同时有多个线程运行
比较单线程和多线程运行
* 单线程
优点:
顺序编程简单易懂
缺点:
效率低
* 多线程
优点:
能有效提高CPU的利用率
缺点:
创建多线程开销
线程间切换开销
死锁与状态同步
JS是单线程还是多线程?
js是单线程运行的
但是使用H5的web workers可以多线程运行
浏览器运行是单线程还是多线程?
都是多线程运行的
浏览器运行是多进程还是单进程?
有的是单进程 firefix、老版本IE
有的是多进程 chrome、新版IE
如何查看浏览器是单进程还是多进程?
3.2 浏览器内核
什么叫内核
支撑浏览器运行最核心的程序
不同浏览器内核不同
内核由多个模块组成
3.3 定时器引发的思考?
定时器真的是定时执行的嘛?
定时器并不能保证真正的定时执行
一般会延迟一丁点,也可能延迟很长时间