1、js的数据类型:
分类: 基本类型(5种),引用类型
基本(值)类型 ,保存在栈中:
String:任意字符串
Number:任意数字
boolean:true/false
undefined:undefined
null: null 可以用来判断undefined和null
引用类型,保存在堆中:
Object: 任意对象
Function:一种特殊的对象(可以执行)
Array: 一种特殊的对象(数值下表,内部数据是有序的)
判断:
typeof: 返回数据类型的字符串表达,首字母都是小写。 比如var a; console.log(typeof a) //'undefined ',typeof可以用来判断undefined('undefined')、字符串(‘string’)、bool、函数('function'),但是他不能够准确区分null和object类型还有arrya和object(返回的都是‘object’)
instanceof : 判断对象的具体类型,格式console.log(a instabceof b)用来判断a是否是b 的一个实例,即a是一个实例,b是一个构造函数。比如 var a =[] ;console.log(a instabceof Array)返回true,j即用来判断数组这个对象是否是 数组 === : 通常是通过判断语句的返回值来判断,可以判断null和undefined
2、undefined 和null的区别
undefined: 指的是定义了但是没有赋值,此时为undefined
null: 指的是定义了并且进行了赋值只是赋值为null
*什么时候会用到null类型
*初始赋值时,当你想要定义一个变量赋值为对象时,因为刚开始不知道对象内容可以包含什么时就可以给变量赋值为null,如var a = null 用来告诉别人a是一个对象。
*当不在使用时为了释放内存,让对象变成一个垃圾对象(垃圾对象是指:没有变量引用/指向该对象),可以被垃圾回收机制回收,那么就给变量赋值为null
3、数据、内存、变量
*数据:存储在内存中代表特定信息的东西,本质上是二进制 如 var age = 18
*内存:内存条通电后产生的可存储数据的空间(临时的)
*变量: 可变化的量,用来存储数据。每一个变量都对应一个小内存,变量名用来查找对应的内存,变量值就是内存的数据。
3.1、关于引用变量赋值赋值的问题
*两个引用变量指向同一个对象,通过一个变量修改对象内部的数据,其他的所有变量看到的是修改之后的数据
var obj = {name :‘tom’}
var obj1 = obj2
function fn(funobj) // 此处形参指向 {name :‘tom’},然后又将该对象进行修改
{
funobj.name = 'A'
}
fn(obj1) //此处传入实参,相当于funobj=obj1 ,所以funobj1指向和obj相同的对象
console.log(obj2.name) //A
*2个引用变量指向通过一个对象,让其中一个变量指向另一个变量,另一个引用变量依然指向前一个对象
var a = {age : 12}
var b=a
a = {name : 'BOB', age : '13'} // 此处a被赋值新的对象,那么a中存储的地址也将会发生变化,而b中地址不发生改变。
b.age = 14
console.log(b.age, a.name , a.age) // 14 BOB 13
function fn2 (obj)
{
obj = {age : 15}
}
fn2(a) //此处传入实参,相当于obj=a ,但是又因为obj又被赋值为新的对象那么此时obj和a指向的地址不在是同一个,所以a.age还是13,由于函数运行完函数中的变量会被释放,此时不在有变量指向{age : 15},因此该对象会被垃圾回收机制回收
console.log(a.age) // 13
3.2 、js在调用函数传递变量参数时,是值传递还是引用传递
*理解1:都是值传递,都是传递一个变量中存储的内容,但是该内容分为基本类型的数据,或者是地址(如传递一个变量时)
*理解2: 可能是值传递,也有可能是引用传递(即传递的内容是地址)
3.3、js的内存管理
1、内存的生命周期
*分配内存,得到该位置的使用权
*存储数据,可以进行操作
*释放内存
2、释放内存
*局部变量: 函数执行完自动释放
*对象: 成为垃圾对象然后被垃圾回收机制回收
总结
4、对象
4.1、什么是对象
*多个数据的封装体
*用来保存数据的容器
*一个对象代表一个现实中的一个事物
4.2、为什么要使用对象
*统一管理多个数据
4.3、对象的组成
*属性 :属性名(字符串)和属性值(任意)组成
*方法:一种特别的属性(属性值是函数)
4.4、如何访问对象内部数据
*通过点方法,.属性名:编码简单,但有时不能用
*['属性名']: 编码麻烦,能通用
4.5、什么情况只能使用【'属性名'】
*当属性的属性名中包含特殊字符时: - 空格
*当属性名不确定是,如
var propName = 'name'
var value = 'Tom'
此时如果想在对象中添加{name : Tom}属性并且propName是可变的,就只能通过 obj[propName] = value而不能 通过obj.propName = value来设置此时将会设置为 {propName:Tom}
5、函数
5.1、什么是函数
*函数是实现特定功能的封装体,可以提高代码的复用,更便于阅读
5.2、函数调用方式
*test(): 直接调用
*obj.test(): 通过对象调用
*new test() : new调用
*test.call/apply(obj): 临时让test成为obj的方法进行调用
var fun = function()
{
this.a = 'name'
}
var b = {}
fun.call(b)
console.log(b.a)
5.3、回调函数
*什么是回调函数
自己定义的函数
自己没有调用
但是函数执行了 (在一定条件或者一定或者某个时刻)
*常见的回调函数
dom事件回调函数 ===》 this代表dom元素
定时器回调函数 ===》 this代表window
ajax回调函数
生命周期回调函数(React中)
*同步回调
理解:立即执行,完全执行完才结束,不放入回调队列中
例子 数组遍历,promise的excutor
*异步回调
理解:不会立即执行,会放入回调队列中将来执行
例子:定时器回调/ajax回调/promise成功回调|失败回调
5.4、IIFE (匿名函数自调用)
*作用
隐藏实现、不会污染全局命名空间,用他来编写模块
5.5、this
*什么是this
.任何函数本质上都是通过某一个对象来调用的,如果没有直接指定就是window
所有函数内部都有一个变量this
他的值是调用函数的对象
*如何确定this的指向
test(): window
p.test() : p
new test() : 新建的对象
p.call(obj) :obj
5.6、函数的prototype属性
*每一个函数都有一个prototype属性,他默认指向一个object对象(原型对象),原型对象添加的属性/方法格式为:fun.prototype.test() = function(){},该方法可以通过实例对象访问到
*原型对象中有一个属性为constructor,他指向函数对象
*显示原型和隐式原型
1、每一个函数function都有一个prototype,即显式原型
2、每一个实例都有一个__proto__,可称为隐式原型
3、对象的隐式原型的值为对应构造函数的显式原型的值 :fn.__proto__=Fn.prototype
4、函数的prototype属性:在定义函数是自动添加的,默认值为一个空对象
5、对象的__proto__属性:实例化对象时添加的,默认值为构造函数的prototype
var Fn = function() //默认执行了,this.prototype = {}
{
}
var fn = new Fn() 内部语句 this.__proto__ = Fn.prototype
5.6、原型链
*原型链
*访问一个对象属性时,先在自己的属性中查找,找到返回,如果没有,再沿着__proto__这条链向上查找,找到返回。如果始终没有找到那么返回undefined
*别名:隐式原型链
*作用:查找对象的属性
图片讲解:
左边矩形:栈空间,右边矩形:堆空间
Fn是一个构造函数,fn是Fn的一个实例。
Fn变量中存储的是函数对象的地址,又因为Fn是一个函数对象所以有一个prototype属性指向一个空的对象即Object空对象。
fn变量中存储的是一个Fn的实例对象的地址,Fn实例对象的有一个__proto__属性他指向构造函数的prototype即Object空对象 ,上面语句可以总结为:fn.__proto__=Fn.prototype。
其中Object空对象也是一个实例,他的构造函数为Object函数对象,因此也符合上面的等式即:Object空对象.__proto__=Object函数对象.prototype(Object的原型对象)。所有的对象都是Object函数对象的一个实例
图片讲解:
Object.prototype中添加的属性所有的实例对象,以及构造函数对象都可以访问到
Function.prototype中添加的属性只有构造函数对象才可以访问到
其实每一个函数都是一个实例,相当于var Fn = new Function(),他们的构造函数是 Function , 由于Fn是一个实例因此每一个函数除了有prototype属性外还有__proto__属性,因此有Fn.__proto__=Function.prototype成立,并且所有函数的__proto__都是一样的都是Function.prototype。对于Funtion来说有一个特殊的等式即Function.__proto__=Function.prototype即有Function = new Function()
上面两张图的对应关系:
fn<===>f1;
Fn<===>Foo;
Object空对象(Object函数对象的实例)<===>Foo.prototype(Objetc()的实例);
Obejct函数对象<===>Object()
Object的原型对象<===>Object.prototype
所有的原型对象都是Object的一个实例 函数对象. prototype.__proto__=Object.prototype。但是Object.prototype.__proto__=null
所有的函数对象都是Function的一个实例 函数对象.__proto__=Function.prototype,即Function本身也是本身的实例
5.7、原型链补充
1、函数的显示原型指向的对象默认是空的,但是Object实例对象除了Object之外
console.log(Fn.prototype instanceof Object) //true
console.log(Object.prototype instanceof Object) //false 因为 Object.prototype.__proto__为null
console.log(Function.prototype instanceof Object) //true
2、所有函数都是Function的实例(包含Function)
console.log(Function.__proto__===Function.prototype)
3、Object的原型对象是原型对象链的尽头
console.log(Object.prototype.__proto__) //null
5.8、instanceof是如何判断的
*表达式: A instanceof B
*如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false
5.9、面试题
解析:function A(){}是一个构造函数,因此具有prototype属性指向一个空对象(假设地址为0x123),A.prototype.n=1添加一个属性,n:1. var b = new A()中b是一个实例具有__proto__属性,指向构造函数prototype(地址也为0x123)。下面一条语句又对A.prototype重新赋值,此时A.prototype不在指向地址0x123,此时假设指向0x234,此对象中包含n:2,m:3。对于原来对象即地址0x123由于实例b的__proto__属性指向他所以没有被回收。然后下一条语句又重新建立一个实例对象c,c的__proto__属性指向现在的A.prototype属性所指的对象即地址0x234,因此最后输出{1,undefined,2,3}
可以通过原型链图进行获取答案,结果为a(),报错,a(),b()
5.10、变量声明提升和函数声明提升
1、变量声明提升
*通过var定义的变量,在定义语句之前就可以访问到
*值:undefined
2、函数声明提升
*通过function声明的函数,在之前就可以直接调用
*值:函数定义的对象
例如:
console.log('------') //当代码运行到此处时,window就包含了b值为undefied,fn2函数,fn3值为undefined
console.log(b) //undefined 变量提升
fn2() // ‘fn2函数’ 此处可以调用函数
fn3() //报错 ,此处不可以调用函数
var b =3
function fn2()
{ console.log('fn2函数')}
var fn3= function ()
{ console.log('fn3函数')}
5.11、执行上下文
1、全局执行上下文
*在执行全局代码之前先将window确定为全局执行上下文
*对全局数据进行预处理(在执行代码前的准备工作)
*var定义的全局变量==>undefined,添加为window的属性
*function声明的全局函数==>赋值(fun),添加为window的方法
*this==>赋值(window)
*开始执行全局代码
2、函数执行上下文
*在调用函数时,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟对象,存在于栈中)
*对局部数据进行预处理
*形参变量==>赋值(实参)==>添加为执行上下文的属性
*arguements==>赋值(实参列表)==>添加为执行上下文的属性
*var定义的局部变量==>undefined,添加为执行上下文的属性
*function声明的函数==>赋值(fun),添加为执行上下文的属性
*this==>赋值(调用函数的对象)
*开始执行函数体代码
3、确定有几个执行上下文方法
*n+1,其中n为调用了几次函数,1为window执行上下文
5.12、执行上下文栈
1、在全局代码执行前,js引擎会创建一个栈用来存储管理所有的执行上下文对象
2、在全局执行上下文(window)确定后,将其添加到栈 中(压栈)
3、在函数执行上下文创建后,将其添加到栈中
4、在当前函执行完毕之后,将栈顶的对象移除
5、当所有代码执行完毕之后,栈中只剩下window
5.13、执行上下文,执行上下文栈面试题
1、
2、
输出‘function’,该问题涉及到变量声明提升和函数声明提升的顺序,首先需要知道变量声明提升在函数声明提升之前。因此该代码相当于先声明变量然后在声明函数,又因为变量名相同,所以函数声明会将变量声明覆盖,因此输出function
3、
该代码会运行时,会报错。解析:首先声明变量c,即var c ; 然后在声明函数,即function(){}.
然后在对c进行赋值 c = 1,然后执行c(2),此时的c相当于变量c而非函数c,因此会报错
5.14、作用域链
1、理解
*多个上下级关系的作用域形成的链,它的方向是从内到外
*查找变量时就是沿着作用域链来查找的
2、查找一个变量的查找规则
*在当前的作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
*在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
*再次执行2的相同操作,直到全局作用域,如果还不能找到就抛出异常
3、作用域的作用:隔离变量使不同的作用域内可以使用相同的变量名而不冲突
4、作用域链的作用:查找变量如果找到那么返回,如果找不到那么报错
5.15、作用域和执行上下文的区别和联系
1、区别1,产生的时间
*全局作用域之外,每一个函数会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时。
*全局执行上下文是在全局作用域确定后,js代码马上执行之前创建的
*函数执行上下文是在调用函数时,函数体代码执行之前创建的
2、区别
*作用域是静态的,只要函数定义好了就一直存在,且不会再发生改变
*执行上下文是动态的,调用函数时会创建,函数调用结束时就会自动释放
3、联系
*执行上下文是从属于所在的作用域
*全局执行上下文===>全局作用域
*函数执行上下文===>对应的函数作用域
5.16、作用域面试题
1、
解析:show()函数中执行fn()函数,执行fn()函数时,输出x,首先在fn()函数体中查找x,结果在函数体中未发现x,于是就会跑到上一层中寻找,fn()函数的上一层为全局作用域,在该作用域中查找到x=10,于是会输出10
2、
解析:第一个输出比较好理解,对于第二个,执行obj.fn2()时,首先会在function(){}函数中查找fn2结果为找到,然后会返回上一层作用域,也就是全局作用域中,注意一个对象不是一个作用域,有因为在全局中未找到,所以会报错。如果想要正确输出fn2,可以console.log(this.fn2)
6、闭包
6.1、了解闭包
1、如何产生闭包
*当一个嵌套函数内部(子)函数引用了嵌套函数外部(父)函数的变量/函数时,就会产生闭包
2、闭包到底是什么
*闭包可以通过chrome调试查看
*理解:闭包一个类似于对象且保存在内部函数中的东西,此内部函数引用了外部函数的变量或者函数
*注意:闭包存在于嵌套的函数内部,只要内部函数定义代码执行了(注意不是内部函数执行)就会产生闭包,内部函数定义代码执行几次就会产生几个闭包===外部函数调用几次
3、产生闭包的条件
*函数嵌套
*内部函数引用了外部函数的数据(变量/函数)
*调用了外部函数,内部函数不一定调用
6.2、常见的闭包
1、function fn1 ()
{
/ /此时闭包已经产生了,函数提升,内部函数对象已经创建
var a =2
function fn2 ()
{
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() //3
f() //4
该代码产生了,一次闭包,因为外部函数只执行了一次。
2、function showDalay(msg,time)
setTimeout (function(){
alter(msg)},time) //内部函数引用了外部函数的变量
}
showDelay('wang',2000)
6.3、闭包的作用
1、使用函数内部的变量执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2、让函数外部可以操作(读写)函数内部的数据(变量或者函数)
3、闭包的应用:定义js模块
*具有特定功能的js文件
*讲所有的数据和功能都封装到一个函数内部
*只向外暴露一份包含n个方法的对象或者函数
*模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
参考: https://www.bilibili.com/video/BV14s411E7qf?p=34
6.4、闭包的生命周期
1、产生:嵌套内部函数定义执行完时就产生了
2、死亡:在嵌套的内部函数成为垃圾对象时,即fn2=null时
6.5、闭包的缺点
1、函数执行后,函数内的局部变量没有释放,占用内存时间变长
2、容易造成内存泄漏,因此闭包需要及时释放
3、内存溢出和内存泄漏
*内存溢出:
*一种程序运行出现的错误
*当程序运行需要的内存超过剩余的内存是,就会抛出内存溢出的错误
*内存泄漏
*占用的内存没有得到及时的释放
*内存泄漏积累多了就会导致内存溢出
*常见的内存泄漏问题
*意外的将函数中的局部变量设置为全局变量
*没有及时清理的计数器或者回调函数
*闭包
7、继承
7.1、原型链继承
1、方法
*定义父类型的构造函数
*给父类型的原型添加方法
*定义子类型的构造函数
*创建父类型的对象赋值给子类型的原型 Sub.prototype = new Supper()即子类型的prototype属性是父类型的一个实例
*将子类型的原型的够咱函数设置为子类型 Sub.prototype.constructor=Sub
*给子类型添加方法
*创建子类型的对象;可以调用父类型的方法
为什么需要Sub.prototype.constructor=Sub语句?
如果不添加该语句的话,sub.constructor = Supper。按照本来的理解,实例对象的构 造器应该为构造函数即Sub,但是该例子中却指向了 Supper,这是因为Sub.prototype = new Supper()修改了sub的prototype的指向,但是新的prototype对象(即地址0x567)没有constructor属性,于是他就会按照原型链向上找,最终Supper的实例对象的原型对象上具有constructor属性于是sub.constructor为Supper
7.2、new一个对象所做的事
*创建一个空的{}
*给对象设置__proto__,值为构造函数的prototype属性 this.__proto__ = Fn.prototype
*执行构造函数体(给对象添加方法)