<!--
一般都把JS放到BODY的末尾
1.为啥?
2.放在HEAD中可不可以? 如何放到HEAD中也可以实现出放到BODY末尾的效果?
3.SCRIPT标签中有两个属性:defer / async,这两个属性是做什么的
-->
<!--
1.BODY中编写的都是HTML标签,JS很多时候需要操作这些元素,
首先我们要保证元素加载成功,才可以在JS中获取到,所以我们
通常会把JS放在BODY的末尾。
-->
<!--
2.利用 window.onload = function(){} 代码包裹
-->
<!--
3.async:他是异步加载,不确定何时会加载好;页面加载时,
带有 async 的脚本也同时加载,加载后会立即执行,
如果有一些需要操作 DOM 的脚本加载比较慢时,这样会造成 DOM
还没有加载好,脚本就进行操作,会造成错误。
defer:页面加载时,带有 defer 的脚本也同时加载,加载后会等待
页面加载好后,才执行。
-->
var a = 12;
var b = a;
b = 13;
console.log(a);
var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1);
function sum() {
var total = null;
for (var i = 0; i < arguments.length; i++) {
var item = arguments[i];
item = parseFloat(item);
!isNaN(item) ? total += item : null;
}
return total;
}
console.log(sum(12, 23, '34', 'AA'));
变量提升
/*
* 变量提升:
* =>当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”
*
* =>声明(declare):var a (默认值undefined)
* =>定义(defined):a=12 (定义其实就是赋值操作)
*
* [变量提升阶段]
* =>带“VAR”的只声明未定义
* =>带“FUNCTION”的声明和赋值都完成了
*
* =>变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)
* =>在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” [带VAR/FUNCTION的才是声明]
*
* =>浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了)
*/
console.log(a);//=>undefined
var a = 12;
带VAR和不带的区别
//=>在全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和WINDOW没啥关系)
/*
console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window); //=>TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED in:检测某个属性是否隶属于这个对象
var a = 12;//=>全局变量值修改,WIN的属性值也跟着修改
console.log(a);//=>全局变量A 12
console.log(window.a);//=>WINDOW的一个属性名A 12
a = 13;
console.log(window.a);//=>13
window.a = 14;
console.log(a);//=>14
//=>全局变量和WIN中的属性存在 “映射机制”
*/
//=>不加VAR的本质是WIN的属性
/*
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(window.a);//=>undefined
// console.log('a' in window);//=>false
a = 12;//=>window.a=12
console.log(a);//=>12
console.log(window.a);//=>12
*/
/*var a = 12,
b = 13;//=>这样写B是带VAR的*/
/*var a = b = 12;//=>这样写B是不带VAR的*/
console.log(a, b);//=>undefined undefined
var a = 12,
b = 12;
function fn() {
console.log(a, b);//=>undefined 12
var a = b = 13;
/*var a=13; b=13;*/
console.log(a, b);//=>13 13
}
fn();
console.log(a, b);//=>12 13
作用域链
function fn() {
/*
* 变量提升:无
*/
// console.log(b);//=>Uncaught ReferenceError: b is not defined
b = 13;
//console.log('b' in window);//=>true 在作用域链查找的过程中,如果找到WIN也没有这个变量,相当于给WIN设置了一个属性B (window.b=13)
console.log(b);//=>13
}
fn();
console.log(b);//=>13
关于条件判断下的变量提升
/*
* 变量提升:
* var fn; =>只对等号左边进行变量提升
* sum = AAAFFF111;
*/
sum();
fn();//=>Uncaught TypeError: fn is not a function
//=>匿名函数之函数表达式 (更多采用函数表达式的方式)
var fn = function () {
console.log(1);
};//=>代码执行到此处会把函数值赋值给FN
fn();
//=>普通的函数
function sum() {
console.log(2);
}
/*
* 在当前作用域下,不管条件是否成立都要进行变量提升
* =>带VAR的还是只声明
* =>带FUNCTION的在老版本浏览器渲染机制下,声明和定义都处理,但是为了迎合ES6中的块级作用域,新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似于VAR
*/
/*
* 变量提升
* var a; =>在全局作用域下声明的全局变量也相当于给WIN设置了一个属性 window.a=undefined
*/
console.log(a);//=>undefined
if ('a' in window) {
var a = 100;
}
console.log(a);//=>100
/*
* 变量提升:无
*/
f = function () {return true;};//=>window.f=...(TRUE)
g = function () {return false;};//=>window.g=...(FALSE)
~function () {
/*
* 变量提升:
* function g; //=>g是私有变量
*/
if (g() && [] == ![]) {//=>Uncaught TypeError: g is not a function (此时的g是undefined)
//=>[]==![]:TRUE
f = function () {return false;};//=>把全局中的f进行修改 window.f=...(FALSE)
function g() {return true;}
}
}();
console.log(f());
console.log(g()); //=>Edge、谷歌等高版本浏览器直接报错,IE低版本 false false
/*
* 变量提升:
* function fn;
*/
// console.log(fn);//=>undefined
if (1 === 1) {
console.log(fn);//=>函数本身:当条件成立,进入到判断体中(在ES6中它是一个块级作用域)第一件事并不是代码执行,而是类似于变量提升一样,先把FN声明和定义了,也就是判断体中代码执行之前,FN就已经赋值了
function fn() {
console.log('ok');
}
}
// console.log(fn);//=>函数本身
变量提升机制下重名的处理
/*
* 1.带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样)
*/
/*
var fn = 12;
function fn() {
}
*/
/*
* 2.关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)[不管是变量提升还是代码执行阶段皆是如此]
*/
/*
* 变量提升:
* fn = ...(1)
* = ...(2)
* = ...(3)
* = ...(4)
*/
fn();//=>4
function fn() {console.log(1);}
fn();//=>4
function fn() {console.log(2);}
fn();//=>4
var fn=100;//=>带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
fn();//=>100() Uncaught TypeError: fn is not a function
function fn() {console.log(3);}
fn();
function fn() {console.log(4);}
fn();
ES6中的LET不存在变量提升
/*
* 在ES6中基于LET/CONST等方式创建变量或者函数,不存在变量提升机制
*
* =>切断了全局变量和WINDOW属性的映射机制
*
* =>在相同的作用域中,基于LET不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用LET创建都会报错)
*
* 虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)
*/
// console.log(a);//=>Uncaught ReferenceError: a is not defined
// let a = 12;
// console.log(window.a);//=>undefined
// console.log(a);//=>12
/*let a = 12;
console.log(a);
let a = 13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);*/
/*b = 12;
console.log(b);//=>12
a = 12;//=>Uncaught ReferenceError: a is not defined
console.log(a);
let a = 13;
console.log(a);*/
let a = 10,
b = 10;
let fn = function () {
console.log(a, b);//=>Uncaught ReferenceError: a is not defined
let a = b = 20;
/*
* let a=20;
* b=20; //=>把全局中的 b=20
*/
console.log(a, b);
};
fn();
console.log(a, b);
暂时性死区
/*
var a = 12;
if (true) {
console.log(a);//=>Uncaught ReferenceError: a is not defined
let a = 13;
//=>基于LET创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语法创建的变量,如果是则按照新语法规范来解析,
}
*/
/*
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(typeof a);//=>"undefined" 在原有浏览器渲染机制下,基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回UNDEFINED
*/
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(typeof a);//=>Uncaught ReferenceError: a is not defined
let a;//=>如果当前变量是基于ES6语法处理,在没有声明这个变量的时候,使用TYPEOF检测会直接报错,不会是UNDEFINED,解决了原有的JS的死区
私有变量和全局变量
/*
* 变量提升:
* var a; var b; var c;
* fn = xxx...
*/
var a = 12,
b = 13,
c = 14;
function fn(a) {
/*
* 形参赋值
* a = 12
*
* 变量提升
* var b;
*
* =>在私有作用域中,只有以下两种情况是私有变量
* A:声明过的变量(带VAR/FUNCTION)
* B:形参也是私有变量
*
* 剩下的都不是自己私有的变量,都需要基于作用域链的机制向上查找
*/
console.log(a, b, c);//=>12 undefined 14(C是全局的)
var b = c = a = 20;
/*
var b=20;
c=20; =>把全局的C修改为20
a=20;
*/
console.log(a, b, c);//=>20*3
}
fn(a);//=>把FN执行(小括号中是实参:值) =>执行FN把全局变量A的值12当做实参传递给函数的形参 =>fn(12)
console.log(a, b, c);//=>12 13 20
var ary = [12, 23];
function fn(ary) {
console.log(ary);//=>[12,23]
ary[0] = 100;
ary = [100];//=>创建新数组
ary[0] = 0;
console.log(ary);//=>[0]
}
fn(ary);
console.log(ary);//=>[100,23]
上级作用域的查找
当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和他在哪执行的没有关系,和他在哪创建(定义)的有关系,在哪创建的,它的上级作用域就是谁。
/*
var a = 12;
function fn() {
console.log(a)
//=>arguments:实参集合
//=>arguments.callee:函数本身FN
//=>arguments.callee.caller:当前函数在哪执行的,CALLER就是谁(记录的是它执行的宿主环境),在全局下执行CALLER的结果是NULL
console.log(arguments.callee.caller);
}
function sum() {
var a = 120;
fn();
}
function aa() {
fn();
}
aa(); //=>12
*/
var n = 10;
function fn() {
var n = 20;
function f() {
n++;
console.log(n);
}
f();
return f;
}
var x = fn();//=>21
x();//=>22
x();//=>23
console.log(n);//=>10