一.简介
ECMAScript与JavaScript的关系:ES(ECMAScript)是JS的标准,JS是ES的实现。
European Computer Manufactures Association欧洲计算机制造联合会。
编辑器(VS Code 、Atom、Sublime)或IDE(集成开发环境)(Webstorm)
运行代码环境:浏览器(最新的chrome)或Node.js环境(越新越好,拿不到BOM 和DOM,例如window.xxx和document.xxx不可用)
注意:BOM 浏览器对象模型,DOM 文档对象模型
二.let
使用var声明或不声明直接使用变量时,本质上是往window对象上添加了属性。
let与var的主要区别:
①let声明的变量旨在当前(块级)作用域{}(对象的{}不算)内有效。
②let声明和const声明的变量不能被重复声明。
③let不存在变量提升,不会把声明提前。
1.块级作用域
ES6之前的作用域:全局作用域、函数作用域、eval作用域、ES6块级作用域.
块级作用域(通俗的讲,就是一对花括号中的区域{....}):
①if(){}
②switch(){}
③for(){}
④try{}catch(err){}
⑤{}
{
var a=1;
let b=2;
}
console.log(a);
console.log(b);
块级作用域可以嵌套:
{
//块级作用域1
let a=1;
{
//块级作用域2
console.log(a);
let b=2;
}
console.log(b);
}
2.暂存死区
如果块级作用域中存在let或者const声明的变量,这个变量一开始就会形成一个封闭的作用域,形成暂存死区。
在for中使用let时,for后面的括号内算是一个作用域,括号后面的{}算是括号作用域的子作用域。类似作用域的嵌套,第一层是括号内的,第二层是花括号内的{ { } }。
let b = 14;
{
console.log(b);//报错。不会沿着作用域链查找,因为该作用域有了let声明的变量b,把该针对b变量,把该作用域封闭了,然后let又不会变量提升,提前声明,所以不存在b,所以报错。
let b =5;
}
3.常见面试题
面试题:生成十个按钮,每个按钮点击的时候分别弹出1-10。(闭包)
传统解决方法是使用闭包:
如果这里把自执行函数去掉,那所有的按钮打印结果都是循环之后的i的值为11。有了自执行的函数,它就会形成一个独立的函数作用域,函数作用域中i作为参数传入,就能获取每次循环的i值。
var i=0;
for(i=1;i<=10;i++){
(function (i) {
var btn=document.createElement('button');
btn.innerText=i;
btn.onclick=function(){
alert(i);
}
document.body.appendChild(btn);
})(i);
}
使用let解决:
直接用let声明i,for循环中只用写执行步骤,不用写自执行函数。这里相当于块级作用域的嵌套,{}内可以访问嵌套上级()里let声明的i。
for (let i = 1; i <= 10; i++) {
var btn = document.createElement("button");
btn.innerText = i;
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
}
三.const
1.常量–不可改变的量
const c="我是常量”
常量声明的时候必须要赋值
与let类似的特性:不能重复声明,不存在变量提升,只在当前块级作用域内有效。
与let的区别:
常量一旦声明就不可改变,但常量如果为引用类型的时候,不能保证不可变。
实质上,const只能保证常量的地址不变,不能保证地址上的内容发生改变。
常量声明后不能被修改
const NAME="小明”;
NAME="小红”;//报错
常量为引入类型,可以修改该引用类型,const可以保证地址不变。
const xiaoming={
age:14,
name:'小明'
};
console.log(xiaoming.age);//14
xiaoming.age=22;
console.log(xiaoming.age);//22
xiaoming={};//报错
怎么防止常量为引用类型的时候能被修改的情况:Object.freeze(引用类型常量);
//把对象冻住,让它失去可以修改的能力
const xiaoming={
age:14,
name:'小明'
};
Object.freeze(xiaoming);
console.log(xiaoming.age);//14
xiaoming.age=22;
console.log(xiaoming.age);//14
xiaoming={};//报错
2.拓展:ES6之前怎么声明常量
1,var xx = xx;//假装是常量。
2,Obejct.defineProperty(对象名,‘属性名’,{ value: ‘小明’, writable: false });//往对象上添加属性,可添加描述,如能不能枚举,修改,配置。
var CST={};
Object.defineProperty(CST,'BASE_NAME',{
value:'小明',
writable:false//不可修改
});
Object.seal(对象名);//防止对象被扩展,但是可以修改原有属性。
var CST={a:1};
Object.defineProperty(CST,'a',{
writable:false//不可修改
});
Object.seal(CST);
两个结合。完成常量的效果。
封装一个freeze:
1.遍历属性和方法
2.修改遍历到的属性的描述
3.Object.seal()进行包装
使用for-in遍历对象的时候,会将原型上的方法也遍历一边,所以需要obj.hasOwnProperty(属性);//返回true的时候就是自身拥有的属性,而不是原型上拥有的属性。
Object.defineProperty(Object,'freezePolyfill',{
value:function(obj){
var i;
for(i in obj){
if(obj.hasOwnProperty(i)){
if (obj[i] instanceof Object){
Object.freezePolyfill(obj[i]);
}
Object.defineProperty(obj, i , {
writable: false
});
}
}
Object.seal(obj);
}
});
const a={
name:"xiaoming",
age:"18",
information:{
sex:"male"
}
}
console.log(a);
Object.freezePolyfill(a);
a.information.sex="female";
console.log(a);
如上当有一个对象的元素是一个对象的时候,此时使用自定义的冻结方法,
就需要在方法内部对这个对象的属性进行判断看他是否是一个对象。
在使用typeof obj[i]==="object"失效的时候,可以使用obj[i] instanceof Object,
在最后使用递归继续调用方法的时候使用Object.objfreeze(obj[i])进行传入,而不是传入i