var 会有一个作用域提升的问题
console.log(a) // undefined
var a = '123';
等同于
var a;
console.log(a) // undefined
a = '123';
等同于
let a;
console.log(a);
a = '123';
变量a被提升到顶部,因为这种bug,let,const等块级作用域诞生了。 let/const 的声明周期和var不一样。
块级作用域(同时被称为词法作用域)存在于
{}
和[]
let禁止重复声明,而var不存在这种xian'z限制。
他必须经过初始化才行
const a; // Uncaught SyntaxError: Missing initializer in const declaration;
非常有意思
console.log(a);
let a = '123'; // reference 引用错误。
对比
console.log(typeof a);
if(true) {
let a = 1;
}
因此,a的值如果处于ley的块级作用域,并且在打印后才赋值,那么是引用错误,但是如果放到块级作用域外面,它是默认从window下面拿值的,因此let之前的块级作用域称之为 Temporal Dead Zone.换句话说,块级作用域内。
众所周知,``代表es6的模板字符串,默认支持换行,但据说,他真正厉害的是模板标签
function passthru (literals, ...substitutions) {
let result = '';
// 根据substitutions的数量来确定循环的执行次数
for (let i = 0; i < substitutions.length; i++) {
console.log(result);
result += literals[i];
result += substitutions[i];
}
result += literals[literals.length - 1];
return result;
}
let count = 10;
let price = 0.25;
let message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message);
函数参数默认值
function func (a=1){
console.log(a);
}
func(); // 1;
由于a默认值等于b,而b命名在a之后,
function t (a = b,b) {
return a + b;
}
console.log(t(1,2)); // 3
console.log(t(1)); // NaN
非常有趣的函数默认参数命名 上面例子,函数参数命名过程
// t(1,2)
let a = 1;
let b = 2;
// t(undefined,1);
let a = b; // 参数为undefined时,使用默认参数。
let b = 1; // 在临死区,b 引用不到,而且在临死区不会去默认指向window对象的属性。
一切正如第一章所说,当a引用b的时候,b在临死区,所有的绑定行为都会报错。
同样非常有意思,函数默认有一条属性,那就是name 打印函数,输出的是函数体,但(函数).name输出的确实他的函数名,或者函数声明。这同样涉及到函数背后所做的事情。
function a(){};
=====输入======
a.name
====等同于======
var temp = new Function();
temp.name // a
temp = null // 销毁
var a = (){};
console.log(a.bind().name) // "bound dosomething";
console.log((new Function()).name) // "anonymous";
function P(name){
this.name = name;
}
var person = new P('peng');
var noPerson = P('peng');
console.log(person) // "[Object object]";
console.log(noPerson) // "undefined";
一般来说,new让函数内部this指向新的对象,并且返回这个新的对象。 js函数有两个不同的内部方法,[[Call]]和[[Constructor]],这两个方法很有意思,当new关键字被调用的时候,执行的是[[Construct]]函数,,他会创作一个通常被称为实例的新对象,然后执行函数体,将this绑定到实例上,如果不通过new关键字调用的话,,就会执行[[Call]],从而直接执行代码中的函数体,,而具有[[Constructor]]方法的函数,被统称为构造函数, 切记,不是所有函数都有[[Construct]]方法,因此不是所有的函数都可以通过new来调用,例如后面本章所说的箭头函数就没有[[Construct]]方法,
function Person (name='peng'){
if(this instanceof Person){
console.log('我被当作构造函数来用');
this.name = name;
} else {
throw new Error('错误,前面要有new')
}
}
new Person();
Person();
看上面例子,首先this会被指向新的对象,如果新的对象被指向的新对象,他的原型中又Person,那么说明你有通过构造函数构造拥有Person原型的对象;如果this的 原型是Person,即this是Person的实例,那么继续执行,如果不是,就抛出错误。由于[[Construct]]方法会创建一个Person的实例 ,并将this绑定到新的实例上面,通常,通过call也可以实现绑定。
function Person (name='peng'){
if(this instanceof Person){
console.log('我被当作构造函数来用');
this.name = name;
} else {
throw new Error('错误,前面要有new')
}
}
var person = new Person();
Person.call((new Person), 'micheal');
上面这种方法同样将this绑定到Person上面。Person.call()时将变量person作为第一个参数传入,相当于在Person函数里面将this设为了person实例。对于函数本身,无法通过区分是否是通过new调用的还是通过Person.call()来调用的。
通过 鉴别new.target可以判断是否是通过new来调用的
function Person (name='peng'){
if(new.target !== undefined){
console.log('我被当作构造函数来用',new.target);
this.name = name;
} else {
throw new Error('错误,前面要有new')
}
}
var person = new Person();
Person.call(person, 'micheal'); // throw error
function PPerson(){
Person.call(this,name);
}
var pperson = new PPerson(); // throw error
if(true){
// es5报错,es6不报错,摸棱两可的属性。
function a(){}
}
所有的函数都会发生提升,但是问题来了,一旦if语句不执行这部分呢??
a('sdf'); // a is not defined
if(true){
function a(){
console.log('a');
}
a('sdf'); // successful;
}
a('sdf'); // a is not defined
神奇的是,函数只在块级作用域内部发生提升
typeof a; // a is not defined
// a('sdf'); // a is not defined
if(true){
a('sdf'); // a
function a(){
console.log('a');
}
a('sdf'); // a
typeof a; // a is not defined
}
// a('sdf'); // a is not defined
typeof a; // a is not defined
但是在非严格模式下,块级函数会被提升到外围函数顶部。
在ECMAScript6中,箭头函数是最有趣的的新特性。与传统函数的不同主要有以下
以上差异产生原因。内部this指向不明确,因而出现箭头函数。
箭头函数同样拥有name属性, 箭头函数的IIFE版本(立即执行函数)需要包一层小括号,而正常函数包不包小括号都可以执行。
let person = (function(name){
return {
getName: function(){
return name;
}
};
})('name');
console.log(person.getName()); // "Nicholas";
箭头函数内的this绑定是JavaScript中最常出现错误的因素,函数体内的this值可以根据函数调用上下文而改变。
let PageHandler = {
id: '123456',
init: function (){
document.addEventListener('click',function (e){
console.log(this); // documet 对象
},false)
},
doSomething: function (type){
console.log(`handling ${type} for`,this.id) // handling undefined for 123456
}
}
PageHandler.init();
PageHandler.doSomething();
上面代码来看,对象PageHandler涉及初衷就是用来处理页面上面的交互,通过init来配置交互,然而实际上普通函数,this指向谁引用的它。this绑定的是目标对象的引用,上面代码中init被调用的引用是document,自然无法执行下去,想要通过bind绑定this的值,是不可能的,箭头函数this指向其外部非箭头函数。
通常在过去,通过bind方法可以显示绑定到对象中,修正这个问题。
let PageHandler = {
id: '123456',
init: function (){
console.log(this); // 指向对象本身
document.addEventListener('click',function (e){
console.log(this); // PageHandler 对象
}.bind(this) , false)
},
doSomething: function (type){
console.log(`handling ${type} for`,this.id) // handling undefined for 123456
}
}
PageHandler.init();
PageHandler.doSomething(); //
但是仔细一看,上面的做法很奇怪,为什么呢?(function(){}).bind(this)
创建了一个新的函数。他的this指向当前对象。为了避免创建额外的函数,下面使用箭头函数。
let PageHandler = {
id: '123456',
init: function (){
console.log(this); // 指向对象本身
document.addEventListener('click', (e)=>{
console.log(this); // PageHandler 对象
} , false)
},
doSomething: function (type){
console.log(`handling ${type} for`,this.id) // handling undefined for 123456
}
}
PageHandler.init();
PageHandler.doSomething(); //
这个时候,this已经成功指向PageHandling本身了,再改一下,外部函数改成箭头函数
let PageHandler = {
id: '123456',
init: ()=>{
console.log(this); // 指向对象本身
document.addEventListener('click', (e)=>{
console.log(this); // window 对象 ,严格模式指向undefined
} , false)
},
doSomething: function (type){
console.log(`handling ${type} for`,this.id) // handling undefined for 123456
}
}
PageHandler.init();
PageHandler.doSomething(); //
是不是很神奇,箭头函数永远指向其外部最近的普通的function的this。如果外部没有普通function,那么this指向window,或者undefined。箭头函数缺少基本的prototype,也就是说箭头函数没有原型链,箭头函数的原则就是即用即弃。, 箭头函数不能使用new来构造,因为他没有[[Construct]]方法,同时也是因为如此,JavaScript引擎可以进一步优化其特定行为。 同时,箭头函数不能通过call(),apply(),bind(),来改变this值。
不多解释,非常强大,
同样也是灰常强大的功能, 为什么这么说呢,这可以有效防止栈溢出。
function tip(){
return newFunc(); // 尾调用
}
在es5引擎中,尾调用的栈同样清晰,创建一个新的栈帧(stack frame),将未完成的函数调用推入栈。但是尾递归的问题就是,当调用栈太多的时候会造成程序性能问题, 在es6引擎中,尾调用被优化,换句话说,es6引擎较少了调用栈的最大长度,如果超出长度,调用栈会先停止,转而去处理栈帧。如果满足以下三个条件,可以被js引擎自动优化调用栈。
事实上尾递归优化发生在引擎后面,递归函数优化最明显,
'use strict';
function t (n, p = 1){
if(n <=1){
return 1*p;
}else{
let result = n + p;
// 优化后
return t(n - 1, result);
}
}
t(12135)
然而在浏览器中打印,依然是zhan'yi'chu栈溢出。目前尚处于审查阶段。,日后再安利一波。
几乎每一种类型的值都是对象,随着js的发展,对象使用率越来越高,因此提升对象使用效率就变得非常重要。 es6也对对象进行了优化。通过许多方式加强对象的使用,通过简单的语法扩展,提供更多操作对象交互的方法,本章详细讲解这些改进。
es6对对象进行了分类,以下4个类别
下面,我们将用这些属于来解释es6定义的各种对象。
通过Object.is()
,我们可以检测两个值是否全等。
console.log(-0 === +0) // true
Object.is(-0,+0); //false
Object.is(NaN,NaN); //true
除了NaN 或者-0这种情况,其他情况Object.is()和===基本一样。
####Object.assign()方法
混合(Mixin)是JavaScript中实现函数混合对象组合的最流行的一种方式,
function minxin(receiver, superlier){
Object.keys(superlier).forEach(function (key){
receiver[key] = superlier[key];
});
return receiver;
}
上面函数会将两个对象混合在一起。因此es6出现了Object.assign这种方法。它可以改变第一个参数那个对象的属性并混入第二个对象的属性。
var t = {}
Object.assign(t,{
a:1
},{
a:2,
b:3
},{
c:5,
d:7,
})
console.log(t); // {a: 2, b: 3, c: 5, d: 7}
Object.assign可以接受任意个数的参数,并且越靠后的权重越高,同时,由于对象是指针关系,为了避免改变第一个参数,造成混乱,我们可以将第一个参数传入{},这样Object.assign()返回的新对象,将不会影响任何对象。注意,get属性不能被复制。
var o = {
get foo(){
return 17;
}
}
var b = {
a:2
}
Object.assign(b,o)
var d = Object.getOwnPropertyDescriptor(b, 'foo'); // undefined
console.log(d.get);
非常有意思的属性,它可以获取对象的某个属性全部的值,
正如之前说的,原型对于js非常重要,es许多改进,最终是为了让他更好用,es6引入了super,使用它可以更便捷的访问对象原型。举个例子,
let person = {
getGreeting(){
return 'Hello!';
}
};
let dog = {
getGreeting(){
return 'Woof!';
}
}
let friend = {
getGreeting(){
return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
}
}
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === person);
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === dog);
super则相当于对象的原型指针,上面例子即使super = Object.getPrototypeOf(this); 当然他必须在简写方式中使用,负责会抛出语法错误。
没啥好说的。
私有属性,外界无法访问。所有的原始值,除了symbol以外,都有各自的字面形式,例如布尔值类型的ture或者类型值42,可以通过Symbol来创建全局一个Symbol,
let fir = Symbol();
let person = {}
person[fir] = 'Nicholas';
console.log(person[fir]) // nicholas
首先安利一波mdn用法如下:第二个参数是可选参数,this,
下面这个对象,forEach的this,作为第二个参数传入,输出结果 1,2
let set = new Set([1,2]);
let processor = {
output(val) {
console.log(val);
},
process(dataSet) {
dataSet.forEach(function(val) {
return this.output(val)
}, this)
}
}
processor.process(set);
下面这个例子,箭头函数从外围的process()函数读取this,其实直接把this当作变量来看待,他就是一个默认存在不需要声明就默认存在的变量,箭头函数内部没有this,所以你在箭头函数里面拿this会直接拿到外部的this。这个解释可以啊,
let set = new Set([1,2]);
let processor = {
output(val) {
console.log(val);
},
process(dataSet) {
dataSet.forEach(val => this.output(val))
}
}
processor.process(set);
new set(arr); // set
[...set(arr)] // arr
谁都知道,对象是引用类型,那么当你new Set(arr)
的时候,set被添加arr数组,当arr=null
的时候,引用对象呗销毁,而new Set()这个值依然保留。换句话说,这是强行引用,
什么是弱引用,就是原来引用的对象被删除了,那么weakSet对象会同步到删除操作,然并卵。。
Map类型是一种储存许多键值对的有序列表,其中键名和对应的值支持所有类型
Weak Set 是若引用Set集合,相对的,Weak Map 是弱引用的Map集合,也是用于储存对象的弱引用,weakMap集合的键名必须是一个对象,如果使用非对象键名回报错,,如果在弱引用之外,不存在其他强引用,,引擎的辣鸡回收机制,会自动回收这个对象,同时移除weak Map集合种的键值对。 weak map是一种储存许多键值对的无序列表,列表的键名必须是非null的对象,键名对应的值应该是可以是任意类型,weak map 接口与map非常相似,,通过set方法设置,通过get方法得到。
let map = new WeakMap();
let element = document.querySelector(".element");
map.set(element,'Original');
let value = map.get(element);
console.log(value); // "Original"
set 集合是一种包含多个非重复性的无序列表,值与值之间的等价性是通过Object.is()
的方法来判断,,若果相同,就过滤,5和’5‘不同,同时,set不是数组的子类,所以你不能通过随机访问集合中的值,只能通过has()方法,检测指定的值是否存在于Set集合中,或者通过size属性查看数量,
weak set集合是一个特殊的set集合,只支持存放弱引用,当其对象的其他强引用被清除的时候,弱引用自然会被清除,
map是多个无序键值对组成的集合,键名支持任意数据类型,与set集合类似,map也是通过Object.is()判断是否重复,他与set的区别是可以通过迭代器循环,
weak map是弱引用,造轮子应该非常适用这种东西。