判断数据类型
1.typeof关键字,返回的是数据类型的字符串表达
var a;
console.log(typeof a);//输出'undefined'
注意:null、array、object返回object,function返回function
2.istanceof,检查一个对象是否是另一个对象的实例即a是否是b的实例,Object、Function、Array
a instanceof b,a是实例,b是构造函数
如果B函数的显示原型对象(B.prototype)在A对象的原型链上,返回true
3.===
console.log(a===undefined); //true
console.log(typeof a=='undefined');//true
var a=null; a===null;返回true;
undefined和null的区别?
underfined代表定义了没有赋值,null代表定义并赋值了,只是值为null
什么时候给变量赋值null?
将要赋值为对象前,赋值null
或者想让对象被垃圾回收,赋值null
栈里存:全局变量和局部变量,函数名
堆里存:对象,函数
注意:n个引用变量指向同一个对象,通过一个变量修改对象的内部数据,其他所有变量看到的是修改后的数据
2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象。
var a={ name:'lkq'}
var b=a;
a={ name:'zyy',age: 13}//相当于创建一个新对象,让a指向这个对象
console.log(b.name,a.name,a.age); // 'lkq' 'zyy' 13
var a={age:12};
a={name:'zyy',age:13};
function fn(obj){
obj={age:15};//这里相当于又创建一个新的对象,让a指向这个对象,但是这个obj是局部变量,函数执行完毕后,被删除,相 //当于没有这一步,如果obj.age=15;就可以输出15,相当于直接在原对象上修改值
}
fn(a);
console.log(a.age);//输出13
在js调用函数时传递变量参数是值传递还是引用传递?
可能值传递,可能引用传递;
var a=2;
function fn(a){
a=a+1;
}
fn(a);
console.log(a);//输出2
原理同上;
JS引擎怎么管理内存?
1.内存的生命周期:
分配内存空间,得到它的使用权
存储数据,反复操作
释放当前内存空间
2.释放内存
局部变量,函数执行完自动释放
对象:成为垃圾对象---》垃圾收集齐回收
全局变量不会释放
怎么访问对象内部数据?
.属性名:写法简单,有时候不能用
['属性名'],写法复杂,通用
什么时候必须用【‘属性名’】?
1.属性名包含特殊字符,比如"-" " ",p.content-type='';这样是不行的,因为属性名带"-"。
2.属性名不确定。比如var a='hello';var sex='男'; p.a=sex; 这样不行
怎么调用函数?
1.test():直接调用
2.obj.test():通过对象调用
3.new test():new 调用
4.test.call(obj) 或test.apply(obj):
补充:使用工厂方法创建对象
把属性传过去,返回一个对象;使用工厂方法创建的对象,使用的构造函数都是Object
缺点:使用工厂方法创建的对象,使用的构造函数都是Object。我们想让Person类的对象是Person这种。
function createPerson(name,age,gender){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.gender=gender;
return obj;
}
var p=createPerson("...","....","....");
slice和splice
slice:提取数组中指定的元素
语法:arrObject.slice(start,end)
start:必须。从何处选取,如果负数,从数组尾部开始算起的位置。即-1,最后一个元素。
end:可选。如果没有这个参数,就选从start到数组结束的全部元素,如果参数是负数,从数组的尾部算起的位置。
返回一个[start,end)的新数组
splice:删除数组中的指定元素
语法:arrObject.splice(start,num,....)
start:表示开始位置的索引
num:表示删除的数量
第三个及以后的参数,可以传一些新的元素,这些元素将自动插入到开始位置索引的前面
会影响到原数组,返回删除后的结果
arr=["孙悟空","猪八戒","唐僧"];
var result=arr.splice(1,1,"牛魔王","红孩儿"); ---删除第1个元素,并把这两个插入到1之后
返回“孙悟空”,“牛魔王”,“红孩儿”,“唐僧”
call和apply
这两个方法都是函数对象的方法,需要通过函数对象调用。
调用call()和apply()可以修改函数执行调用对象的this
object.call(obj,2,3)
参数一:this指向的对象
参数二、三,....:传入的参数
object.apply(obj,[2,3])----需要将实参封装到数组里传递
回调函数
什么是回调函数?我定义的函数,但是我没有调用,它却执行了。
比如:
document.getElementById('btn').οnclick=function(){-------dom事件回调函数
alert();
}
setTimeout(function(){-----定时器回调函数
alert();
},2000);
------还有ajax请求回调函数、声明周期回调函数
超时定时器
setTimeout(函数,时间)
IIFE
(立即执行函数表达式)
函数定义完,立刻被调用,这种函数叫立即执行函数,只会执行一次。
(function(){----匿名函数自调用
var a=3;
console.log(a + 3);---------写在外面会产生全局变量,写在里面不会产生,里面可以定义a,外面也可以,在相同作用域互不影响
})()
好处:隐藏实现;不会影响外部(全局)的命名空间;用它来编写js模块
原型对象
使用构造方法,里面写个函数。那么每调用一次构造方法创建出对象,就创建出一个新的方法,即使这个方法都是一样的。
------优化:将方法定义在全局作用域中,但是会污染全局作用域的命名空间。而且定义在全局中不安全。
我们创建的每一个函数,解析器都会向函数中添加一个属性,叫prototype;
这个属性对应一个对象,这个对象就是原型对象;
当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,这个隐含的属性指向该构造函数的原型对象。
我们可以通过__proto__来访问该属性。
function MyClass(){
}
var mc=new MyClass();
console.log(mc.__proto__);//==mc.protytype
原型对象就相当于一个公共的区域,所有同一类的实例都可以访问到这个原型对象。
我们可以将对象中共有的内容,同一设置到原型对象中;
MyClass.prototype.a=123;
当我们访问对象的一个属性或方法时,它会先在对象自身(mc)中寻找。如果有就直接使用,如果没有就去原型对象寻找,如果找到就直接使用。如果没有就去原型的原型中找,直到找到Object对象的原型,如果Object中都没有就返回undefined。
mc.hasOwnProperty("age");//用这个方法能判断对象自身是否有这个属性
例题:
function A(){
}
A.prototype.n=1;
var b=new A();
A.prototype={
n:2,
m:3
}
var c=new A();
console.log(b.n,b.m,c.n,c.m);// 1,undefined,2,3
执行上下文
全局执行上下文
1.在执行全局代码前,将window确定为 全局执行上下文
2.对全局数据进行预处理:
var定义的全局变量---->undefined,添加为window的属性
function声明的全局函数------>赋值,添加为window的方法
this---------->赋值为window
3.开始执行全局代码
函数执行上下文
1.在调用函数,准备执行函数体之前,创建函数执行上下文对象(虚拟的存在于栈中)
2.对局部数据进行预处理
形参---->赋值(实参)----->添加为执行上下文的属性
arguments----->赋值(实参列表),添加为执行上文的属性
var 定义的局部变量----->undefined,添加为执行上文的属性
function声明的函数---->赋值(fun),添加为执行上文的方法
this------>赋值(调用函数的对象)
3.开始执行函数体代码
执行上下文栈
1.在全局代码执行前,JS引擎创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将它添加到栈中
3.在函数执行上下文创建后,将其添加到栈中
4.当前函数执行完后,栈顶对象出栈
5.所有代码执行完后,栈中只剩下window
案例:
var a=10;-------产生window执行上下文
var bar=function(x){
var b=5;
foo(x+b);// 这里执行不了,因为foo=,是赋值语句,只会把var foo提前,函数未执行
---------------产生执行上下文对象
}
var foo=function(y){
var c=5;
console.log(a+c+y);
}
bar(10);----------产生执行上下文对象
作用域
有全局作用域、函数作用域、块作用域(ES6)
作用:隔离变量,不同作用域下同名变量不会冲突
注意:作用域在函数定义时就已经确定了。每个函数都会创建自己的作用域
区别:
1.作用域在定义时就确定了,全局执行上下文在全局作用域确定之后,js执行前创建,函数执行上下文是在调用函数时,函数代码执行前创建
2.作用域是静态的,定义好就不会再变化。执行上下文是动态的,调用函数时创建,调用结束上下文环境被释放。
3.上下文对象从属于作用域
作用域链
嵌套的作用域产生由内向外的过程。
比如:
输出c,先看当前作用域有没有,有输出,
看b当前作用域没有,去上一级外部看,有,输出
看a当前作用域没有,外部没有,最外部,有,输出
如果到最外层作用域都没有,就报错
闭包
闭包的代码:
function fn1(){
var a=2;
function fn2(){
console.log(a);
}
}
fn1();
-------这里fn2里有a的undefined值,这就是闭包
如何产生闭包?
当一个嵌套的内部函数引用了嵌套的外部函数数据(变量或函数)时,而且执行了外部函数,就产生了闭包
闭包是什么?
我们可以通过chorme调试方式来查看,
闭包就是存在于嵌套的内部函数中,包含的被引用变量的对象(即上例的a)
常见的闭包
1.将函数作为另一个函数的返回值
function fn1(){
var a=2;-------执行到这的时候,已经有闭包了,因为有函数提升,fn2提到上面去了
function fn2(){
a++;
console.log(a);
}
return fn2;
}
var f=fn1();----怎么看产生几个闭包?看调用了几次外部函数,这次产生一个闭包
f();---输出3---因为返回值是fn2,所以这里会直接进入a++这一行
f();---输出4
fn1();----产生的第二个闭包
2.将函数作为实参传给另一个函数调用
function show(msg,time){
setTimeout(function(){
alert(msg);----闭包产生是因为这里引用了msg
},time)
}
show('marry',2000);
闭包的作用
1.使函数内部变量在函数执行完后,仍然存活在内存中——延长了局部变量的生命周期
2.让函数外部能操作(读写)到函数内部的数据(变量、函数)
闭包的生命周期
闭包的产生:在嵌套内部函数定义执行完时产生
闭包的死亡:在嵌套内部函数称为垃圾对象时
闭包的应用:
定义JS模块
JS模块是:具有特定功能的JS文件,将所有的数据和功能封装到一个函数内部,只向外暴露一个包含n个方法的对象或函数
只需要通过模块暴露的对象调用方法来实现对应的功能。
方法一:不推荐
JS文件中:
function myModule(){
var msg='hello world';
function doSomething(){
console.log('doSomething()'+msg.toUpperCase());
}
function doOtherthing(){
console.log('doOtherthing()'+msg.toLowerCase());
}
return {
doSomething:doSomething,
doOtherthing:doOtherthing
}
}
Script中
<script src="myModule.js" type="text/javascript" ></script>
<script>
var module=myModule();
module.doSomething();
module.doOtherthing();
</script>
方法二:推荐
(function(){
var msg='hello world';
function doSomething(){
console.log('doSomething()'+msg.toUpperCase());
}
function doOtherthing(){
console.log('doOtherthing()'+msg.toLowerCase());
}
window.myModule2={
doSomething:doSomething,
doOtherthing:doOtherthing
}
})()
<script src="myModule2.js" type="text/javascript" ></script>
<script>
myModule2.doSomething();
myModule2.doOtherthing();
</script>
闭包的缺点
1.函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
2.容易造成内存泄漏
解决:
能不用闭包就不用
及时释放,让内部函数对象成为垃圾对象
内存溢出
程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误。
内存泄露
占用的内存没有及时释放,比如闭包、没有及时清理计时器或回调函数、全局变量
内存泄露多了就容易内存溢出了。
面向对象高级
对象创建模式
1.用Object构造函数创建(适用:起始的时候不确定对象内部的数据, 缺点:语句太多)
var p=new Object();
p.name='zyy';
p.setName=function(name){
this.name=name;
}
2.字面量方式(适用:起始的时候确定对象内部的数据, 缺点:有重复代码)
var p={
name:'zyy',
setName:function(name){
this.name=name;
}
};
3.工厂模式(适用:需要创建多个对象,缺点:对象没有一个具体的类型,都是Object类型)
通过工厂函数动态创建对象
function createPerson(name,age){
var obj={
name:name,
age:age,
setName:function(){
this.name=name;
}
}
return obj;
}
var p=createPerson('zyy',18);
4.自定义构造函数,new的方式创建(适用:创建多个类型确定的对象, 缺点:每个对象都有相同的数据,浪费内存)
function Person(name,age){
this.name=name;
this.age=age;
this.setName=function(name){
this.name=name;
}
}
var p=new Person('zyy',18);
5.构造函数+原型(适用:需要创建多个确定类型的对象)
自定义构造函数,属性在函数中初始化,方法添加到原型上、
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.setName=function(name){
this.name=name;
}
var p=new Person('zyy',18);
原型链的继承
1.定义父类型的构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.将子类型的原型prototype指向父类型的实例对象,父类型的实例能看到原型对象
5.将子类型原型的构造属性设置为子类型
6.给子类型的原型添加方法
7.创建子类型的对象:可以调用父类型的方法
关键是:子类型的原型为父类型的一个实例对象
function Father(){
this.Fpro='Father property';
}
Father.prototype.showFprototype=function(){
console.log(this.Fpro);
}
function Son(){
this.Spro='Son property';
}
Son.prototype=new Father();
Sub.prototype.constructor=Sub;//让子类原型的构造函数指向子类型
Son.prototype.showSprototype=function(){
console.log(this.Spro);
}
var son=new Son();
son.showFprototype();
组合继承
利用原型链实现对父类型对象的方法继承
用call()借用父类型构造函数初始化相同属性
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.setName=function(){
this.name=name;
}
function Student(name,age,price){
Person.call(this,name,age);
this.price=price;
}
Student.prototype=new Person();
Student.prototype.constructor=Student;
Student.prototype.setPrice=function(price){
this.price=price;
}
线程机制和事件机制
进程与线程
注意:
应用程序必须运行在某个进程的某个线程上
一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
一个进程中也可以同时运行多个线程
一个进程内的数据可以供多个线程直接共享
多个进程之间的数据不能直接共享
线程池:保存多个线程对象的容器,实现线程对象的反复利用
问题
什么是多进程?什么是多线程
多进程:一个应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时有多个线程运行
比较一下单线程和多线程运行,有什么区别?
多线程:
优点:提高CPU的利用率
缺点:创建多线程需要开销;线程间的切换需要开销;死锁和同步的问题
单线程:
优点:顺序执行
缺点:效率低
JS是单线程还是多线程?
JS是单线程运行的,但是H5中的Web Workers可以多线程运行
浏览器是单线程还是多线程?单进程还是多进程?
多线程;有的是单进程(火狐、老版IE),有的是多进程(chrome、新版IE)
浏览器内核
浏览器内核是浏览器运行的最核心的程序。
不同浏览器内核可能不一样,内核由很多模块组成。
内核由很多模块组成:
JS引擎模块:负责JS程序的编译与运行-----------主线程(js引擎也是程序,只是用来解析我们的代码,js引擎在浏览器内部)
html、css文档解析模块:负责页面文本的解析-----------主线程(读取文本,拆解)
DOM/CSS模块:负责dom/css在内存中的相关处理-----------主线程
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)-----------主线程
定时器模块:负责定时器的管理--------分线程
事件响应模块:负责事件的管理--------分线程
网络请求模块:负责ajax请求--------分线程
问题:
定时器真的是定时执行的吗?
不一定,一般会延迟一点,也可能延迟很长时间
定时器回调函数是在分线程执行的吗?
在主线程执行,JS是单线程的,
定时器怎么实现的?
事件循环模型,初始化代码(放到栈中)执行完才执行回调代码(放到回调队列)
Web Worker
html5提供了js分线程的实现,即web workers。我们可以将计算量大的代码交给Web Worker(分线程)运行而不冻结用户界面。
但是子线程完全受主线程控制,且不得操作DOM,所以这个新标准也没有改变js单线程的本质。
使用:
1.创建分线程执行的js文件
2.在主线程中的js中发消息并设置回调
var worker=new Worker("worker.js");---创建一个Worker对象并向它传递在新线程中执行的脚本URL
worker.onmessage=function(event){---接收worker传来的数据
console.log(event.data);
}
worker.postMessage("hello world");-----向worker发送消息
案例:比如在文本框输入数字,点击按钮,弹出斐波那契数列的第几个数
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script>
var input=document.getElementById('number');
document.getElementById('btn').οnclick=function(){
var number=input.value;
var worker=new Worker("worker.js");
worker.onmessage=function(event){
console.log('主线程接收分线程返回的数据'+event.data);
alert(event.data);
}
worker.postMessage(number);
consolo.log('主线程向分线程发送数据'+number);
}
</script>
worker.js文件中的代码:
function fibonacci(n){
return n<=2?1:fibnonacci(n-1)+fibnonacci(n-2);
}
var onmessage=function(event){
var num=event.data;
console.log('分线程接收到主线程发送的数据' +num);
var reslt=fibonacci(num);
postMessage(result);
console.log('分线程向主线程返回数据'+result);
}
相关API:
Worker:构造函数,加载分线程执行的js文件
Worker.prototype.onmessage:用于接收另一个线程的回调函数
Worker.prototype.postMessage:向另一个线程发送消息
缺点:
慢;不是每个浏览器都支持这个新特性;worker不能访问dom