数据类型
number
string
boolean
null
undefined
object-
Function
Array
Date
...
隐式转换
变量转换为字符串、===、==(类型不同,尝试转换类型再比较)
类型检测
typeof
instanceof
Object.prototype.toString
constructor
duck type
typeof NaN - number
typeof null - object
typeof不能用来判断null与object object与array
undefined代表定义了但未赋值,null代表定义且赋值了,只是值为null。
什么时候需要赋值为null:初始赋值为null,表明将要赋值为对象;需要释放空间时,将变量赋值为null。
判断对象的类型更常使用instanceof操作符,obj instanceof Object,判断左边对象的原型链上是否有右边的属性,返回true或false。不同window或iframe间的对象类型检测不可用。
Object.prototype.toString.apply([]); === "[object Array]";
Object.prototype.toString.apply(function(){}); === "[object Function]";
Object.prototype.toString.apply(null); === "[object Null]";
Object.prototype.toString.apply(undefined); === "[object Undefined]";
回调函数
什么函数才是回调函数:自己定义的;没调用;但最终执行了。
常见的回调函数:dom事件回调函数;定时器回调函数;ajax请求回调函数;生命周期回调函数。
IIFE
立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。 immediately-invoked Function Expression
IIFE形式的函数调用:
(functionfoo(){
vara=10;
console.log(a);
})();
函数的声明和IIFE的区别在于,在函数的声明中,我们首先看到的是function关键字,而IIFE我们首先看到的是左边的(。也就是说,使用一对()将函数的声明括起来,使得JS编译器不再认为这是一个函数声明,而是一个IIFE,即需要立刻执行声明的函数。
两者达到的目的是相同的,都是声明了一个函数foo并且随后调用函数foo。
为什么需要IIFE:
如果只是为了立即执行一个函数,显然IIFE所带来的好处有限。
实际上,IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。
对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。
https://blog.csdn.net/mooncom/article/details/78532970
函数中的this
https://www.cnblogs.com/pssp/p/5216085.html
函数高级
原型(prototype)
每个函数都有一个prototype属性,它默认指向一个object空对象(原型对象)
所有函数都是Function的实例,包括Function本身
Object的原型对象是原型链尽头,Object.prototype.proto ==>null
原型对象中有个属性constructor,他指向函数对象(Date.prototype.constructor===Date)
可给原型对象添加属性(一般是方法),用来给实例对象使用
function Fun(){
//prototype属性在定义函数时自动添加 默认为空
//this.prototype = {}
}
Fun.prototype.test = function() {
console.log('test')
} //给原型添加方法
var fun = new Fun() //创建对象时自动添加__proto__属性,默认为构造函数的prototype
fun.test() //通过实例调用原型的方法,输出test
显式原型与隐式原型
显式原型:每个函数都有个prototype,即显式原型
隐式原型:每个实例对象都有一个proto,即隐式原型
function Fun(){
}
var fun = new Fun()
// Fun.prototype===fun.__proto__
//即对象隐式原型的值等于构造函数的显式原型的值
可以直接操作显式原型,但在es6之前不能直接操作隐式原型
原型链
https://www.bilibili.com/video/av41708223/?p=17
原型链的本质即 隐式原型链,用来查找对象的属性
*访问一个对象的属性时,先在自身属性中查找,找到返回
*如果没有 再沿着proto这条链向上查找,找到返回
*最终没找到 返回undefined
原型链的属性问题:
function Fun(){
}
Fun.prototype.a = 'a'
var f1 = new Fun()
console.log(f1.a) //输出a
但一般不会在原型内添加属性
instanceof
a instanceof b 如果b函数的显式原型对象在a对象的原型链上返回true
https://www.bilibili.com/video/av41708223/?p=20
面试题:
https://www.bilibili.com/video/av41708223/?p=21
变量提升与函数提升
变量提升:通过var定义的变量在定义语句前就可以访问到,只是值为undefined
函数提升:通过function声明的函数,在定义前就可以访问使用
执行上下文
代码分类:全局代码;局部代码
全局执行上下文:1,在执行全局代码前将window确定为全局执行上下文,2,对全局数据进行预处理(var定义的全局变量添加为window属性,function声明的全局函数添加为window方法);3,开始执行
1.在全局代码执行前,js引擎会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文后(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window
执行的先后顺序:
function a(){}
var a
console.log(typeof a) //输出Function
//先执行变量提升,再执行函数提升
var c = 1
function c(c) { console.log(c)}
c(2) //结果会报错
闭包
<body>
<button>1</button>
<button>2</button>
<button>3</button>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0; i < btns.length; i++)
{
(function (i) {
var btn = btns[i];
btn.onclick = function(){
alert(i+1);
}
})(i)
}
</script>
</body>
但一个嵌套的内部函数引用了嵌套的外部函数的变量(或函数)时,就产生了闭包。
什么是闭包:可以通过Chrome调试查看1.闭包是嵌套的内部函数 2.包含被引用变量的对象。
产生闭包的条件:函数嵌套;内部函数引用了外部函数的数据(变量/函数)
常见的闭包
1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用
闭包的作用:
1.使用函数内部的变量在函数执行完之后,仍然存活在内存里,延长了局部变量的生命周期。因为在函数执行完之后,函数内部声明的变量一般不存在了。
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)因为函数外部不能直接访问内部函数的变量
function f1(){
//此时闭包就已经产生
//函数提升,内部函数对象已经创建
var a = 2;
function f2() {
a++;
console.log(a);
}
return f2; //将f2暴露出去,使f1可访问
}
var f = f1();
f(); //输出3
f(); //输出4
//f = null; 闭包死亡(闭包的函数对象成为垃圾对象)
f()一直存在,指向f2的闭包,a的值一直存在。
闭包的生命周期:
产生:在嵌套的内部函数定义的时候就产生了(不是在调用时)
死亡:在嵌套的内部函数成为垃圾对象时
闭包的应用(实现自定义js模块)
(function () {
//私有数据
var msg = "myFun";
function fun1() {
console.log("fun1: "+ msg);
}
function fun2() {
console.log("fun2: "+ msg);
}
//向外暴露对象(给外部使用的方法)
window.myfun1 = {
fun1: fun1,
fun2: fun2
}
})()
在使用时,先引入js文件
<script>
myfun1.fun1();
myfun1.fun2();
</script>
闭包的缺点及解决
缺点:
函数执行完之后,函数内的局部变量没有释放,占用内存时间会变长
容易造成内存泄漏
解决:
能不用闭包就不用;及时释放
内存溢出:
·一种程序运行出现的错误
·当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
内存泄漏:
·占用的内存没有及时释放
·内存泄漏积累多了就容易导致内存溢出
常见的内存泄漏:
意外的全局变量
没有及时清理的计时器或回调函数
闭包
function f1() {
var a = 1;
function f2() {
console.log("f2: "+ a);
}
return f2;
}
var f = f1();
f();
f = null; //需要加这一句 释放一直存在的a
面试题:
var name = "the window";
var obj = {
name: "my object",
getName: function () {
return function(){
return this.name;
}
}
};
alert(obj.getName()); //显示getName()整体
alert(obj.getName()()); //显示“the window”
var name2 = "the window";
var obj2 = {
name2: "my object",
getName: function() {
var that = this;
return function() {
return that.name2;
}
}
}
alert(obj2.getName()()); //显示“my object”
-
function fun(n,o) {
console.log(o)
return {
fun: function(m) {
return fun(m,n);
}
}
}var a = fun(0); //undefined 此时闭包里 n的值为0
a.fun(1); //0 此时闭包里为fun(1,0) 所以输出0
a.fun(2); //0
a.fun(3); //0var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
var c = fun(0).fun(1); //undefined 0
c.fun(2); //1
c.fun(3); //1
面向对象高级
1.创造函数模式
先创建空的object对象,再动态添加属性方法
适用:起始时不确定对象内部数据
2.创建字面量模式
使用{}创建对象,同时指定属性/方法
适用于起始时对象内部数据是确定的,如果创建多个对象,有重复代码
3.工厂模式
通过工厂函数创建对象并返回,需要创建多个对象时使用,对象没有一个具体的类型,都是object类型。该模式并不常用。
原型链继承
1.定义父类型构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.创建父类型的对象赋值给子类型的原型
5.将子类型原型的构造属性设置为子类型
6.给子类型原型添加方法
7.创建子类型的对象,可以调用父类型的方法
关键:子类型的原型为父类型的一个实例对象
//父类型
function Supper() {
this.supperValue = 'supperValue'
};
Supper.prototype.showSupperValue = function() {
console.log(this.supperValue)
};
//子类型
function Sub() {
this.subValue = 'subValue'
};
//子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper();
Sub.prototype.showSubValue = function() {
console.log(this.subValue)
};
var sub = new Sub();
sub.showSupperValue(); //输出supperValue
sub.showSubValue(); //输出subValue
借用构造函数继承
1.定义父类型构造函数
2.定义子类型构造函数
3.在子类型构造函数中调用父类型构造,通过call()调用
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, price) {
Person.call(this, name, age);
this.price = price;
}
var s = new Student('zhang', 20, 9000);
console.log(s.name,s.age,s.price);
浏览器内核
支撑浏览器运行的最核心的程序,不同的浏览器内核不一样,浏览器内核由很多模块组成
Chrome,Safari:webkit
Firefox:Gecko
IE:Trident
360,搜狗等国内浏览器:Trident+webkit
内核的模块
js引擎模块:负责js程序的编译与运行
HTML css文档解析模块:负责页面文本的解析
DOM/CSS模块:负责dom/css在内存中的相关处理
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
定时器模块:负责定时器的管理
事件相应模块:负责事件的管理
网络请求模块:负责ajax请求
线程机制与事件机制
进程:程序的一次执行,它占有一片独有的内存空间;可以通过Windows任务管理器查看进程
线程:是进程内的一个独立的执行单元;是程序执行的一个完整流程;是CPU的最小的调度单元
应用程序必须运行在某个进程的某个线程上;
一个进程中至少有一个运行的线程:主线程,进程创建后自动创建;
一个进程中也可以同时运行多个线程,程序是多线程运行的;
一个进程内的数据可以供其中的多个线程直接共享;
多个线程之间的数据是不能共享的;
线程池thread pool:保存多个线程对象的容器,实现线程对象的反复利用。
何为多进程与多线程:
多进程运行:一个应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时有多个线程运行
比较单线程与多线程:
多线程能提高CPU的利用率,但增加了开销,会出现死锁与状态同步问题。单线程顺序编程简单易懂,缺点是效率低。
js是单线程运行的,但使用h5中的web workers可以多线程运行。
浏览器运行是多进程运行的。单进程:Firefox,老版本IE;多进程:Chrome,新IE。
定时器
定时器并不能保证真正定时,一般会延迟一点点。
定时器回调函数是在主线程执行的,js是单线程。
如何证明js是单线程执行的?
【alert会暂停当前主线程的执行,同时暂停计时,点击确定后恢复。】
setTimeout()的回调函数是在主线程运行的,定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行。
为什么js要用单线程模式而不用多线程模式?
js的单线程与他的用途有关。作为浏览器语言,js的主要用途是与用户互动以及操作dom,这决定了他只能是单线程,否则会带来很复杂的同步问题。
代码的分类:初始化代码;回调代码。
js引擎执行代码的基本流程:
先执行初始化代码,包含一些特殊的代码;
*设置定时器
*绑定监听
*发送ajax请求
后面在某个时刻才会执行回调代码
setTimeout(function () {
console.log('aaaaaaa')
}, 2000);
console.log('初始化');//先输出该语句,再执行定时器
网友评论