执行上下文与执行上下文栈
变量提升与函数提升
1.变量的声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到,值为:undefined
2.函数声明提升
通过function声明的函数,在之前就可以直接调用,值为:函数定义(对象)
3.问题:变量提升和函数提升是如何产生的?
<script>
var a = 3;
function fn(){
console.log(a);
var a = 4;
}
fn();
console.log(b);
fn2();
var b = 3;
function fn2(){
console.log("fn2()");
}
var fn3 = function(){
console.log("fn3()");
};
</script>
执行上下文
1.代码分类(位置)
2.全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
-对全局数据进行预处理
- var 定义的全局变量–>undefined,添加为window的属性
- function声明的全局函数–>赋值(fun),添加为window的方法
- this–>赋值(window)
开始执行全局代码
函数执行上下文
在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
对局部数据进行预处理
- 形参变量–>赋值(实参)–>添加为执行上下文的属性
- arguments–>赋值(实参列表),添加为执行上下文属性
- var 定义的局部变量–>undefined,添加为执行上下文的属性
- function声明的函数–>赋值(fun),添加为执行上下文的方法
- this–>赋值(调用函数的对象)
- 开始执行函数体代码
执行的时候,如果设置断点,可以看到,functiona2()是被跳过的,因为一开始执行了,就不会再执行了。
执行上下文栈
1.在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完之后,栈中只剩下window
执行上下文对象,是在调用bar的时候产生的,一共n+1次,是指n次调用function+1次调用window 当前执行的一定是栈顶的,执行完栈顶,再向下执行。
执行上下文测试题
作用域与作用域链
作用域
作用域与执行上下文
作用域与执行上下文的区别: 区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时,
- 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
- 函数执行上下文是在调用函数时,函数体代码执行之前创建
区别2
- 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
- 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境–>全局作用域
- 函数上下文环境–>对应的函数作用域
作用域链
作用域测试题
闭包
引入
闭包的概念
闭包的作用
闭包的生命周期
闭包的应用
需要先创建一个js模块,把这些代码放在js中 通过return 向外暴露,前面的doSomething 是一个字符串(就是外面调用方法的时候的方法名) 后面的doSomething是一个函数名 通过匿名函数自定义向外暴露 Q:怎么样向外暴露,把想暴露的属性添加到window中 这个“实参”写window,实现代码的压缩(比如我可以直接写一个w)
闭包的应用
闭包的缺点
内存溢出与内存泄露
闭包测试题
测试题1: ()()代表访问里面的函数体 测试题2:
函数高级总结
原型与原型链:
所有函数都有一个特别的属性:
prototype : 显式原型属性
所有实例对象都有一个特别的属性:
__proto__ : 隐式原型属性
显式原型与隐式原型的关系
函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
原型对象即为当前实例对象的父对象
原型链
所有的实例对象都有__proto__属性, 它指向的就是原型对象
这样通过__proto__属性就形成了一个链的结构---->原型链
当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
执行上下文与执行上下文栈:
变量提升与函数提升
变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
函数提升: 在函数定义语句之前, 就执行该函数
先有变量提升, 再有函数提升
理解
执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
执行上下文栈: 用来管理产生的多个执行上下文
分类:
全局: window
函数: 对程序员来说是透明的
生命周期
全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
函数 : 调用函数时产生, 函数执行完时死亡
包含哪些属性: 全局 :
用var定义的全局变量 ==> undefined
使用function声明的函数 ==> function
this ==> window
函数
用var定义的局部变量 ==> undefined
使用function声明的函数 ==> function
this ==> 调用函数的对象, 如果没有指定就是window
形参变量 ==> 对应实参值
arguments ==> 实参列表的伪数组
执行上下文创建和初始化的过程
全局:
在全局代码执行前最先创建一个全局执行上下文(window)
收集一些全局变量, 并初始化
将这些变量设置为window的属性
函数:
在调用函数时, 在执行函数体之前先创建一个函数执行上下文
收集一些局部变量, 并初始化
将这些变量设置为执行上下文的属性
作用域与作用域链:
理解:
作用域: 一块代码区域, 在编码时就确定了, 不会再变化
作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
分类:
全局
函数
js没有块作用域(在ES6之前)
作用
作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
作用域链: 查找变量
区别作用域与执行上下文
作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
联系: 执行上下文环境是在对应的作用域中的
闭包:
理解:
当嵌套的内部函数引用了外部函数的变量时就产生了闭包
通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
闭包作用:
延长局部变量的生命周期
让函数外部能操作内部的局部变量
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();
f();
闭包应用:
模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
循环遍历加监听
JS框架(jQuery)大量使用了闭包
缺点:
变量占用内存的时间可能会过长
可能导致内存泄露
解决: 及时释放:f = null; //让内部函数对象成为垃圾对象
内存溢出与内存泄露:
内存溢出
一种程序运行出现的错误
当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
内存泄露
占用的内存没有及时释放
内存泄露积累多了就容易导致内存溢出
常见的内存泄露:
意外的全局变量
没有及时清理的计时器或回调函数
闭包
对象高级
对象创建模式
Object构造函数模式
对象字面量
工厂模式
自定义构造函数模式
构造函数+原型的组合模式
继承模式
原型链继承
借用构造函数继承
组合继承
- call方法那个地方相当于this.person(name,age){…}但是这句话是不能写的, 因为一开始是没有person函数的、、
- call代表的是借用执行,this来执行person,this指的是new出来的S
对象高级总结
对象的创建模式:
var obj = {};
obj.name = 'Tom'
obj.setName = function(name){this.name=name}
var obj = {
name : 'Tom',
setName : function(name){this.name = name}
}
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function(name){this.name=name;};
}
new Person('tom', 12);
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){this.name=name;};
new Person('tom', 12);
继承模式:
原型链继承
function Parent(){}
Parent.prototype.test = function(){};
function Child(){}
Child.prototype = new Parent();
Child.prototype.constructor = Child
var child = new Child();
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()
function Parent(xxx){this.xxx = xxx}
Parent.prototype.test = function(){};
function Child(xxx,yyy){
Parent.call(this, xxx);
}
Child.prototype = new Parent();
var child = new Child();
进程与线程
- 进程:程序的一次执行,它战友一片独有的内存空间
- 线程:是进程内一个独立的执行单元,是程序执行的一个完整流程,是CPU最小 的调度单元
- 一个进程里面,一个线程:单线程,多个进程:多线程
- 程序运行在某个进程某个线程上
浏览器内核
定时器引发的思考
1.定时器真的是定时执行的吗?
- 定时器不能保证真正定时执行
- 一般会延迟一点点,也有可能延长很长时间
2.定时器回调函数是在分线程执行的吗? 在主线程执行的,js是单线程的
加上长时间执行那个程序之后,定时器执行的时间就说不准了
js是单线程执行的
加上了一个时间为0的(初始化代码->回调代码) 1.如何证明js执行是单线程的?
- setTimeout()的回调函数是在主线程执行的
- 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
2.为什么js要用单线程模式,而不是用多线程模式?
- JavaScript的单线程,与它的用途有关
- 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM
- 这决定了它只能是单线程,否则会带来很复杂的同步问题
3.代码的分类:
4.js引擎执行代码的基本流程
- 先执行初始化代码:包含一些特别的代码 回调函数(异步执行)
异步执行指的是某些函数代码,必须在所有的初始化代码执行完之后才执行 - 设置定时器
- 绑定事件监听
- 发送ajax请求
- 后面在某个时刻才会执行回调代码
事件循环模型
- stack栈,栈中存放的对象
- 定时器肯定不会有问题的,到了一定的时间点,就会把回调函数放在下面的callback队列当中(待执行),如果前面初始化代码执行很长时间,回调函数就会延迟等待。
- 初始化代码执行完了之后才有可能执行回调代码
H5 Web Workers多线程
未使用H5线程 主线程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
window.onload=function(){
var input = document.getElementById("number");
var btn = document.getElementById("btn");
btn.onclick = function () {
var number = input.value;
var worker=new Worker("./script/worker.js");
worker.postMessage(number);
console.log("主线程向分线程发送的数据: "+number);
worker.onmessage=function(event){
console.log("主线程向分线程返回的数据 "+event.data);
console.log(this);
}
}
}
</script>
</head>
<body>
<input type="text" name="" id="number" placeholder="数值">
<button id="btn">返回结果</button>
</body>
</html>
分线程
function fab(n){
return n<=2 ? 1 : fab(n-1)+fab(n-2);
}
var onmessage=function(event){
var number=event.data;
console.log("分线程接受主线程发送的数据: "+number);
var result=fab(number);
postMessage(result);
console.log("分线程计算完毕,返回结果 "+result);
}
|