1. 带var 和不带var的区别
相同点:都放在window(GO)里。
不同点:1.带var 的变量就是声明变量,要变量提升;不带var的变量,没有变量提升。
2.带var的变量不可配置(不能删除),不带var变量可配置(能删除)
在全局作用域下,带var 还是不带var 都是给GO添加了一个属性(也相当于给window),属性名就是此变量,属性值就是变量值。
<body>
<script>
console.log(a); //undefined
var a = 3;
b = 6;
console.log(window.a);//3
console.log("a" in window);//true
console.log(delete window.a);//false
console.log(delete window.b);//true
</script>
</body>
---------------------
变量提升
<body>
<script>
var a;
console.log(a); //undefined
a = 3;
b = 6;
console.log(window.a);//3
console.log("a" in window);//true
console.log(delete window.a);//false
console.log(delete window.b);//true
</script>
</body>
【思考】:下面的答案是什么?
console.log(a); //undefined
var a=3;
console.log(window.a);//3
console.log("a" in window);//true
---------------------------
答案:
<body>
<script>
var a;
console.log(a); //undefined
a = 3;
console.log(window.a);//3
console.log("a" in window);//true
</script>
</body>
【 判断一个对象到底有没有一个属性】:用 “属性名” in 对象名,如果返回值是false 说明就是不存在,如果是true说明就是存在。
obj={"name":"lili"};
console.log(“name” in obj )// true 说明name就是obj的属性
console.log("age" in obj)//false 说明age 不是obj的属性
2. let const && var 的区别
let 和 var 的区别:
1. 在全局上下文中,基于var声明的变量是直接放在GO(window)中,而基于let声明的变量是放在VO(G)中 的,和GO没关系。
2. var存在变量提升,let不存在变量提升。
3. var 允许重复声明,而let是不允许的。
4. let会产生块级上下文,var不会。
5. let有暂时性死区问题:typeof 检测一个未声明的变量,不会报错,结果是undefined。如果在let声明前使用就会报错。let 和 const 的区别:
1. let 和 const声明的都是变量,只不过const声明的变量,不允许重新指向其他的值(和值关联的指针是不能改变的)。
2. const 不能直接写const m,必须给一个初始值,不然会报错。
/**
* let const && var 的区别
*/
var num = 12;
//let vs const
const m = {
name: "cxh"
}
m.name = "zcf"
console.log(m)
//let vs var
console.log(m);//m is not defined
console.log(typeof m)//"undefined"
console.log(typeof a)//报错 未声明前不能使用
let a = 10
暂时性死区的概念:
ES6明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
console.log(a);// ReferenceError: a is not defined
let a=2;
3. 作用域链
3.1 什么是作用域链
1. 在私有作用域中,如果没有定义这个私有变量,就会向上一级作用域进行查找,如果都没有,继续进行查找,直到查找到window为止。
2. 如果此变量声明的时候用了 let、const,就相当于给VO添加一个这样的变量。
3. 如果声明的时候用了var、function 或者压根没声明关键字就相当于给GO添加了一个属性
(也相当于给window添加了一个属性)。
3. 2 作用域链练习题
【思考题】
console.log(b);
function fn(){
b=13;
console.log(b);
}
fn();
console.log(b);
【答案】
console.log(b);//报错,b is not defined
function fn(){
b=13; // 不是私有变量,向上查找,直到window都没有,给window添加一个属性 window.b=13;
console.log(b);//13
}
fn();
console.log(b);// 13
【思考题】
<body>
<script>
console.log(a, b);//undefined undefined
var a = 12,
b = 12;
function fn() {
console.log(a, b);//undefined 12
var a = (b = 13);
console.log(a, b);//13 13
}
fn();
console.log(a, b);//12 13
------------------------------------
变量提升分析
var a,b;
function fn;
console.log(a, b);//undefined undefined
a = 12,
b = 12;
function fn() {
var a ;
console.log(a, b);//undefined 12
a = (b = 13);
console.log(a, b);//13 13
}
fn();
console.log(a, b);//12 13
</script>
</body>
4. 函数底层运行机制
4.1 私有上下文
函数执行,就会立刻形成一个全新的私有上下文,然后进栈执行。
私有上下文,首先会有一个AO(私有变量对象-->形参变量和函数体内声明过的变量),来存储私有变量。
代码执行之前会做哪些事?
- 初始作用域链:<自己的上下文(执行产生的),函数的作用域(创建时候声明的)>
- 初始this
- 初始arguments
- 形参赋值
- 变量提升
- 代码执行
4.2 代码演示
<body>
<script>
var x = [12, 23];
function fn(x) {
x[0] = 100;
x = [100];
x[1] = 200;
console.log(x);
}
fn(x);
console.log(x);
</script>
</body>
--------------------------
答案分析:
<body>
<script>
//EC(g) go: x---0x001[12, 23] [100, 23]
// vo: fn---0x002
var x = [12, 23];
function fn(x) {
//EC(fn)---0x002
//<EC(g),EC(fn)>
//ao: x---0x001[12, 23] [100, 23] x---0x003[100] [100,200]
x[0] = 100;
x = [100];
x[1] = 200;
console.log(x);//[100,200]
}
fn(x);
console.log(x);// [100, 23]
</script>
</body>
5. 练习题(私有变量和全局变量)
5.1 var a=b=13和var a=13,b=13的区别
var a = b = 13
var a = 13;
b = 13;
var a = 13, b = 13;
var a = 13;
var b = 13;
案例1:
<body>
<script>
var a = 12, b = 13, c = 14;
function fn(a) {
console.log(a, b, c);//12 undefined 14
var b = (c = a = 20);
console.log(a, b, c);//20 20 20
}
fn(a);
console.log(a, b, c);//12 13 20
</script>
</body>
-----------------------------
答案分析:
<body>
<script>
//EC(g) go: a 12, b 13, c 14 20
// vo: fn---0x001
var a = 12, b = 13, c = 14;
function fn(a) {
//EC(fn)---0x001
//<EC(g),EC(fn)>
//ao: b a=12 b=20 a=20
console.log(a, b, c);//12 undefined 14
var b = (c = a = 20);
console.log(a, b, c);//20 20 20
}
fn(a);
console.log(a, b, c);//12 13 20
</script>
</body>
案例2:
/*
传进来的参数如果是引用数据类型,在函数里面虽然是私有变量,
但是更改的还是那个引用地址,所以全局下的变量也会发生变化
*/
<body>
<script>
var ary = [12, 13];
function fn(ary) {
console.log(ary);//[12, 13]
ary[0] = 100;
ary = [100];
ary[0] = 0;
console.log(ary);//[0]
}
fn(ary);
console.log(ary);//[100, 13]
</script>
</body>
---------------------------------
答案分析:
<body>
<script>
//EC(g) go: ary---0x001[12, 13] [100, 13]
// vo: fn----0x002
var ary = [12, 13];
function fn(ary) {
//EC(fn)----0x002
//<EC(g),EC(fn)>
//ao: ary---0x001[12, 13][100, 13] ary---0x003[100] [0]
console.log(ary);//[12, 13]
ary[0] = 100;
ary = [100];
ary[0] = 0;
console.log(ary);//[0]
}
fn(ary);
console.log(ary);//[100, 13]
</script>
</body>
6. 上级作用域
上级作用域:
当前函数执行,形成一个私有作用域A,这个A的上级作用域是谁,跟它在哪执行无关,跟它在哪定义(创建)有关系,在哪创建,它的上级作用域就是谁。
简而言之:上级作用域和函数在哪执行无关,和函数在哪创建的有关。
var a=2;
function fn(){
console.log(a);
}
fn();
function sum(){
var a=3;
fn();
}
sum();
----------------------
答案分析:
<body>
<script>
//EC(g) go: a=2
// vo: fn---0x001 sum---0x002
var a = 2;
function fn() {
//EC(fn)---0x001
//<EC(g),EC(fn)>
console.log(a);//2
}
fn();
function sum() {
//EC(sum)---0x002
//<EC(g),EC(sum)>
//ao: a=3
var a = 3;
fn();
}
sum();
</script>
</body>
作用域练习题:
<body>
<script>
var n = 10;
function fn() {
var n = 20;
function f() {
n++;
console.log(n);
}
f();
return f;
}
var x = fn();
x();
x();
console.log(n);
</script>
</body>
---------------------------------
答案分析:
<body>
<script>
//EC(g) go: n=10 , x---
// vo: fn---0x001
var n = 10;
function fn() {
//EC(fn)---0x001
//<EC(g),EC(fn)>
//ao: n=20 21 22 23 f---0x002
var n = 20;
function f() {
//EC(f)---0x002
//<EC(fn),EC(f)>
n++;
console.log(n);//21 22 23
}
f();//EC(f)---0x002
return f;
}
var x = fn();//EC(f)---0x002
x();//EC(f)---0x002()
x();//EC(f)---0x002()
console.log(n);//10
</script>
</body>
【答案】:打印:21、22、23、10
7. 堆栈内存小科普
GC 即 Garbage Collection 垃圾回收
7.1 js中的内存分为:堆内存和栈内存
【堆内存】:只要用来存储引用数据类型的值(对象存的是键值对,函数存的是字符串)。
【栈内存】:存储基础数据类型和供js运行的环境(函数执行)。
7.2 堆栈内存的释放问题
我们每次给变量存值或者执行函数的时候都会占用内存空间,如果一直这样下去,日积月累,电脑总会装不下 的,所以内存是需要释放的。
①堆内存的释放:
常见的浏览器释放方式主要有以下两种:
- 谷歌浏览器是标记清除(Mark-Sweep),此算法分为 标记 和 清除 两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
- ie和火狐等浏览器是采用计数方法,当前作用域中如果一个空间地址被占用一次,就会累加一,如果减少一次占用就会减1,直到0的时候,说明已经没有被占用了,就释放了。
- 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1。
- 如果同一个值又被赋给另一个变量,那么引用数加 1。
- 如果该变量的值被其他的值覆盖了,则引用次数减 1。
- 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存。
堆内存释放:让所有引用这个堆内存的变量赋值为null,堆内存地址不在被占用,浏览器在空闲的时候就会把堆内存释放。
let x = 5;
let fn = function (x) {
return function (y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
let a = 0, b = 0;
let A = function (a) {
A = function (b) {
alert(a + b++)
}
alert(a++)
}
A(1);
A(2);
8. 闭包
8.1 闭包的概念
闭包是指外部变量有权访问内部函数返回的引用类型值。导致内部函数一直占用内存,无法释放。
8.2 闭包的作用
函数执行产生一个私有上下文,首先这里的私有变量被保护起来,不受外界的干扰;而且如果上下文不被释放,则里面的私有变量也会被保存下来,这样其下级上下文就可操作这些值。我们把这种保护+保存的机制,称作为闭包。
总结:
闭包的作用:1.保护 私有变量不被外界所干扰。
2.保存 私有变量的值,供下次调用。
闭包的缺点:导致函数私有执行上下文不被释放,占用内存。解决方法:手动解决,例如:x=null。
function fn(i){
return function (n){
console.log(n+(++i));
}
}
var f=fn(2);
f(3);
fn(5)(6);
fn(7)(8);
f(4)
-------------------
//EC(g) go: f---EC()---0x002
// vo: fn---0x001
function fn(i){
//EC(fn)---0x001
//<EC(g),EC(fn)>
//ao: i=2 3 5 6 7 8
return function (n){
//EC()---0x002 EC()---0x003 EC()---0x004
//<EC(fn),EC()> <EC(fn),EC()> <EC(fn),EC()>
//ao: n=3 ao: n=6 释放 ao: n=8 释放
console.log(n+(++i));//3+3=6 6+6=12释放 8+8=16释放
}
}
var f=fn(2);
f(3);//6
fn(5)(6);//EC()---0x003(6)=12
fn(7)(8);//EC()---0x004(8)=16
f(4)//4+4=8
8.3 闭包在实战中的应用
轮播图,放大镜
8.3.1 闭包之私有变量的保护应用
【jquery】 通过window添加属性暴漏到全局。
(function(){
function jquery(){}
//把jquer 这个方法通过window添加属性暴漏到全局
window.jquery=window.$=jquery;
})()
在使用的时候: jquery() 或者$()
8.3.2 私有变量之保存机制-选项卡案例再忆
【选项卡案例原版】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
ul,
ol {
list-style: none;
}
.main {
width: 500px;
margin: 0 auto;
}
ul>li {
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid #333;
margin-right: 10px;
display: inline-block;
position: relative;
top: 1px;
}
.main>div {
height: 300px;
line-height: 300px;
border: 1px solid #333;
text-align: center;
display: none;
}
.main li.current {
background: darkcyan;
border-bottom-color: darkcyan;
}
.main div.current {
background: darkcyan;
display: block;
}
</style>
</head>
<body>
<div class="main" id="main">
<ul>
<li class="current">音乐</li>
<li>电视</li>
<li>综艺</li>
</ul>
<div class="current">音乐内容</div>
<div>电视内容</div>
<div>综艺内容</div>
</div>
</body>
</html>
<script>
var main = document.getElementById("main");
var lis = main.getElementsByTagName("li");
var divs = main.getElementsByTagName("div");
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function () {
var index = this.index;
change(index);
}
}
function change(index) {
for (var i = 0; i < lis.length; i++) {
lis[i].className = "";
divs[i].className = "";
}
lis[index].className = "current";
divs[index].className = "current";
}
</script>
</body>
</html>
8.4 回忆当初里面的i为啥是3
8.4.1【作用域方式去思考】
当我们触发点击事件的时候,这个函数执行,形成私有作用域, 在这个私有作用域里面,并没有私有变量i,所以就会向上级作用域进行查找,此时上级作用域就是全局作域里面的i,当我们发生点击时间的时候,此时for 循环早已完成,i早就是3。
作用域:
- window 全局作用域
- 函数执行形成私有作用域
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
// 这里的i为啥会变成3?当我们触发点击事件的时候,这个函数执行,形成私有作用域,
// 在这个私有作用域里面,并没有私有变量i,所以就会向上级作用域进行查找,此时上级作用域就是
// 全局作用域里面的i,当我们发生点击时间的时候,此时for 循环早已完成,i早就是3
change(i);
}
}
8.4.2【同步异步事件去思考】
【同步事件】:当一件事件做完之后,再继续下一件事情。
【异步事件】:当一件事件还没有做完,不再等待,直接去做下一个事件。所有的事件都是异步编程。
【举列子】:比如你想吃着泡面去刷剧。你先打开开关去烧水,这个时候我就站在那等,直到水烧好了,把面泡好了,我再去看电视。这个是同步事件 。 我把烧水的开关打开,我就去刷剧了,一边刷剧,一边等着烧水,水烧好了,我再去关掉.... 这个就是异步事件 。
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
change(i);
}
}
for循环是同步事件,执行完i=3;点击事件是异步事件,当我们点击页面上的按钮的时候,这个for循环早已经执行完了。
lis[0].onclick = function () {
change(i);
}
lis[1].onclick = function () {
change(i);
}
lis[2].onclick = function () {
change(i);
}
8.5 解决方法
8.5.1 自定义属性
8.5.2 闭包
for (var i = 0; i < lis.length; i++) {
// 之前i找到的上级作用域是window,现在我们手动增加一层作用域,用一个闭包的形式,里面把点击事件赋值
//给了外面的元素,被占用,形成不销毁的作用域.n是私有变量,当点击页面上的元素的时候,就会找闭包作用域
//中的私有变量n
(function(n){
lis[n].onclick = function () {
change(n);
}
})(i)
}
for (var i = 0; i < lis.length; i++) {
//每次for循环,就给li绑定一个点击事件,并且点击的事件的值是return里面的小函数,形成了不销毁的作用域
当我们点击li的时候,里面的小函数就会执行,变量i就是自执行函数里面的私有变量
lis[i].onclick=(function(i){
return function(){
change(i);
}
})(i)
}
8.5.3 let关键字
for (let i = 0; i < lis.length; i++) {
lis[i].onclick=function(){
change(i);
}
}
块级作用域的语法:使用{} + es6声明的关键字(let,,cont,class)。
1. 在块级作用域外面访问不到里面的变量。
2. 块级作用域也有作用域链(块级作用域也可以进行嵌套)。
{
console.log(a);
let a=8;
console.log(a);
}
console.log(a);
{
let a=2;
{
let b=3;
console.log(a);
}
}
console.log(a);
9. 综合练习题
练习1:
<script>
//EC(g) go: a 12 vo:fn----0x001
console.log(a); //undefined
var a = 12;
function fn() {//EC(fn)---0x001
//<EC(g),EC(fn)>
//ao: a 13
console.log(a);//undefined
var a = 13;
}
fn();//没有return就没有返回值,默认为undefined
console.log(a);//12
</script>
练习2:
<script>
//EC(g) go: a 12 13 vo: fn ---0x001
console.log(a);//undefined
var a = 12;
function fn() {//EC(fn) ---0x001
//<EC(g),EC(fn)>
console.log(a);//私有没有,找上级a=12
a = 13;
}
fn();//没有return就没有返回值,默认为undefined
console.log(a);//13
</script>
练习3:
<script>
//EC(g) vo: fn --- 0x001
console.log(a);//a没有声明,报错
a = 12;
function fn() {
console.log(a);
a = 13;
}
fn();
console.log(a);
</script>
练习4:
<script>
//EC(g) go: foo 1 vo:bar----0x001
var foo = 1;
function bar() {//EC(bar) ---0x001
//<EC(g),EC(bar)>
//ao: foo 10
if (!foo) { //undefined --- 0 ---!0 ----true进入if
var foo = 10;
}
console.log(foo);//10
}
bar();//没有return就没有返回值,默认为undefined
</script>
练习5:
<script>
//EC(g) go: n 0 ,c ---0x002; vo:a---0x001
var n = 0;
function a() {//EC(a)---0x001
//<EC(g),EC(a)>
//ao: n 10 11 12
var n = 10;
function b() {//EC(b) ---0x002
//<EC(a),EC(b)>
n++;//私有没有,找上级n=11 12
console.log(n);//11 12
}
b();//没有return就没有返回值,默认为undefined
return b;//EC(b) ---0x002
}
var c = a();//EC(b) ---0x002
c();EC(b) ---0x002()=12
console.log(n);//0
</script>
练习6:
<script>
//EC(g) go: a 10,b 11, c 12 3, vo:text---0x001
var a = 10,
b = 11,
c = 12;
function text(a) {//EC(text) ---0x001
//<EC(g),EC(text)>
//ao: b 2, a 10 1
a = 1;
var b = 2;
c = 3;
}
text(10);
console.log(a);//10
console.log(b);//11
console.log(c);//3
</script>
练习7:
<script>
//EC(g) go: a
if (!("a" in window)) {
var a = 1;
}
console.log(a);//undefined
</script>
练习8:
<script>
//EC(g) go: a 4; vo: b---0x001
var a = 4;
function b(x, y, a) {//EC(b)---0x001
//<EC(g),EC(b)>
//ao:私有x=1,y=2,a=3 10
console.log(a);//3
//初始 arguments[1,2,3]
arguments[2] = 10;
console.log(a);//10
}
a = b(1, 2, 3);//这里给a赋了值是一个函数,这个函数没有返回值(return)。
console.log(a);//undefined
</script>
练习9:
<script>
//EC(g) go: foo "hello"
var foo = "hello";
(function (foo) {//foo = "hello" EC()---0x001
//ao :foo = "hello"
console.log(foo);//"hello"
var foo = foo || "word";
console.log(foo);//"hello"
})(foo);
console.log(foo);//"hello"
</script>
练习10:
<script>
//EC(g) go: a 9 0 1 0 , f---0x002 ; vo:fn---0x001
var a = 9; //1 0 1 2
function fn() {//EC(fn)---0x001
//<EC(g),EC(fn)>
a = 0;
return function (b) {//EC()---0x002 EC()---0x003 5 5
//<EC(fn),EC()> <EC(fn),EC()>
//ao: b=5 b=5
return b + a++; //5+0 5+0 5+1
};
}
var f = fn();//函数体EC()---0x002
console.log(f(5));//0x002(5)=5
console.log(fn()(5));//EC()---0x003(5)=5
console.log(f(5));//6
console.log(a);//2
</script>
练习11:
<script>
//EC(g) go: ary---0x001 [1, 2, 3, 4] [0, 2, 3, 4], res---
// vo: fn---0x002
var ary = [1, 2, 3, 4];
function fn(ary) {
//EC(fn)---0x002
//<EC(g),EC(fn)>
//ao: ary---0x001 [1, 2, 3, 4] [0, 2, 3, 4] ary---0x003 [0] [100]
ary[0] = 0;
ary = [0];
ary[0] = 100;
return ary;//[100]
}
var res = fn(ary);//res---ary---0x003 [100]
console.log(ary);//[0, 2, 3, 4]
console.log(res);//[100]
</script>
练习12:
<script>
//EC(g) go: f---EC()----0x002
// vo: fn---0x001
function fn(i) {
//EC(fn)---0x001
//<EC(g),EC(fn)>
//ao: i=10 (11) 20 21 30 31 32 (11+1)=12
return function (n) {
//EC()----0x002 EC()----0x003 EC()----0x004
//<EC(fn),EC()> <EC(fn),EC()> <EC(fn),EC()>
//ao: n=20不释放 ao: n=40 释放 ao: n=50 释放
console.log(n + ++i);//20+11 40+21 50+31 30+12
};
}
var f = fn(10);//EC()----0x002
f(20);//31
fn(20)(40);//0x003(40)=61
fn(30)(50);//0x004(50)=81
f(30);//0x004(30)=42
</script>
练习13:
<script>
//EC(g) go: i=10 , f---
// vo: fn ---0x001
var i = 10; 11 12 13 14
function fn() {
//EC(fn) ---0x001
//<EC(g),EC(fn)>
//
return function (n) {
//EC(fn) ---0x002 EC(fn) ---0x003 EC(fn) ---0x004
//<EC(fn),EC()> <EC(fn),EC()> <EC(fn),EC()>
//ao: n=20 ao: n=20释放 ao: n=30 释放
console.log(n + ++i);//11+20 20+12 30+13 30+14
};
}
var f = fn();//f----0x002
f(20);//----0x002(20)=31
fn()(20);//---0x003(20)=32
fn()(30);//---0x004(30)=43
f(30);//44
</script>