使用var
带来的麻烦:javascript
function getClothing(isCold) { if (isCold) { var freezing = 'Grab a jacket!'; } else { var hot = 'It's a shorts kind of day.'; console.log(freezing); } }
运行getClothing(false)
后输出的是undefined
,这是由于执行function
函数以前,全部变量都会被提高
, 提高到函数做用域顶部.java
let
与const
声明的变量解决了这种问题,由于他们是块级做用域, 在代码块(用{}
表示)中使用let
或const
声明变量, 该变量会陷入暂时性死区直到该变量的声明被处理.git
function getClothing(isCold) { if (isCold) { const freezing = 'Grab a jacket!'; } else { const hot = 'It's a shorts kind of day.'; console.log(freezing); } }
运行getClothing(false)
后输出的是ReferenceError: freezing is not defined
,由于 freezing
没有在 else
语句、函数做用域或全局做用域内声明,因此抛出 ReferenceError
。github
关于使用let
与const
规则:spring
let
声明的变量能够从新赋值,可是不能在同一做用域内从新声明const
声明的变量必须赋值初始化,可是不能在同一做用域类从新声明也没法从新赋值.在ES6以前,将字符串链接到一块儿的方法是+
或者concat()
方法,如express
const student = { name: 'Richard Kalehoff', guardian: 'Mr. Kalehoff' }; const teacher = { name: 'Mrs. Wilson', room: 'N231' } let message = student.name + ' please see ' + teacher.name + ' in ' + teacher.room + ' to pick up your report card.';
模板字面量本质上是包含嵌入式表达式的字符串字面量.
模板字面量用倒引号 ( `` )
(而不是单引号 ( '' )
或双引号( "" )
)表示,能够包含用 ${expression}
表示的占位符数组
let message = `${student.name} please see ${teacher.name} in ${teacher.room} to pick up your report card.`;
在ES6中,可使用解构从数组和对象提取值并赋值给独特的变量数据结构
解构数组的值:闭包
const point = [10, 25, -34]; const [x, y, z] = point; console.log(x, y, z);
Prints: 10 25 -34app
[]
表示被解构的数组, x
,y
,z
表示要将数组中的值存储在其中的变量, 在解构数组是, 还能够忽略值, 例如const[x,,z]=point
,忽略y
坐标.
解构对象中的值:
const gemstone = { type: 'quartz', color: 'rose', karat: 21.29 }; const {type, color, karat} = gemstone; console.log(type, color, karat);
花括号 { }
表示被解构的对象,type
、color
和 karat
表示要将对象中的属性存储到其中的变量
let type = 'quartz'; let color = 'rose'; let carat = 21.29; const gemstone = { type: type, color: color, carat: carat }; console.log(gemstone);
使用和所分配的变量名称相同的名称初始化对象时若是属性名称和所分配的变量名称同样,那么就能够从对象属性中删掉这些重复的变量名称。
let type = 'quartz'; let color = 'rose'; let carat = 21.29; const gemstone = {type,color,carat}; console.log(gemstone); 简写方法的名称: const gemstone = { type, color, carat, calculateWorth: function() { // 将根据类型(type),颜色(color)和克拉(carat)计算宝石(gemstone)的价值 } };
匿名函数被分配给属性 calculateWorth,可是真的须要 function 关键字吗?在 ES6 中不须要!
let gemstone = {
type,
color,
carat,
calculateWorth() { ... }
};
for...of
循环是最新添加到 JavaScript 循环系列中的循环。
它结合了其兄弟循环形式 for
循环和 for...in
循环的优点,能够循环任何可迭代(也就是遵照可迭代协议)类型的数据。默认状况下,包含如下数据类型:String
、Array
、Map
和 Set
,注意不包含 Object
数据类型(即 {}
)。默认状况下,对象不可迭代。
for循环
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (let i = 0; i < digits.length; i++) { console.log(digits[i]); }
for
循环的最大缺点是须要跟踪计数器和退出条件。
虽然 for
循环在循环数组时的确具备优点,可是某些数据结构不是数组,所以并不是始终适合使用 loop 循环。
for...in循环
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (const index in digits) { console.log(digits[index]); }
依然须要使用 index 来访问数组的值
当你须要向数组中添加额外的方法(或另外一个对象)时,for...in
循环会带来很大的麻烦。由于 for...in
循环循环访问全部可枚举的属性,意味着若是向数组的原型中添加任何其余属性,这些属性也会出如今循环中。
Array.prototype.decimalfy = function() { for (let i = 0; i < this.length; i++) { this[i] = this[i].toFixed(2); } }; const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (const index in digits) { console.log(digits[index]); }
forEach 循环 是另外一种形式的 JavaScript 循环。可是,forEach() 其实是数组方法,所以只能用在数组中。也没法中止或退出 forEach 循环。若是但愿你的循环中出现这种行为,则须要使用基本的 for 循环。
for...of循环
for...of
循环用于循环访问任何可迭代的数据类型。
for...of
循环的编写方式和 for...in
循环的基本同样,只是将 in
替换为 of
,能够忽略索引。
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (const digit of digits) { console.log(digit); }
建议使用复数对象名称来表示多个值的集合。这样,循环该集合时,可使用名称的单数版原本表示集合中的单个值。例如,
for (const button of buttons) {…}
。
for...of
循环还具备其余优点,解决了 for 和 for...in 循环的不足之处。你能够随时中止或退出 for...of 循环。
for (const digit of digits) { if (digit % 2 === 0) { continue; } console.log(digit); }
不用担忧向对象中添加新的属性。for...of 循环将只循环访问对象中的值。
Array.prototype.decimalfy = function() { for (i = 0; i < this.length; i++) { this[i] = this[i].toFixed(2); } }; const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (const digit of digits) { console.log(digit); }
展开运算符(用三个连续的点 (...
) 表示)是 ES6 中的新概念,使你可以将字面量对象展开为多个元素
const books = ["Don Quixote", "The Hobbit", "Alice in Wonderland", "Tale of Two Cities"];
console.log(...books);
Prints: Don Quixote The Hobbit Alice in Wonderland Tale of Two Cities
展开运算符的一个用途是结合数组。
若是你须要结合多个数组,在有展开运算符以前,必须使用 Array
的 concat()
方法。
const fruits = ["apples", "bananas", "pears"]; const vegetables = ["corn", "potatoes", "carrots"]; const produce = fruits.concat(vegetables); console.log(produce);
Prints: ["apples", "bananas", "pears", "corn", "potatoes", "carrots"]
使用展开符来结合数组
const fruits = ["apples", "bananas", "pears"]; const vegetables = ["corn", "potatoes", "carrots"]; const produce = [...fruits,...vegetables]; console.log(produce);
使用展开运算符将数组展开为多个元素, 使用剩余参数能够将多个元素绑定到一个数组中.
剩余参数也用三个连续的点 ( ...
) 表示,使你可以将不定数量的元素表示为数组.
用途1: 将变量赋数组值时:
const order = [20.17, 18.67, 1.50, "cheese", "eggs", "milk", "bread"]; const [total, subtotal, tax, ...items] = order; console.log(total, subtotal, tax, items);
用途2: 可变参数函数
对于参数不固定的函数,ES6以前是使用参数对象(arguments)处理:
function sum() { let total = 0; for(const argument of arguments) { total += argument; } return total; }
在ES6中使用剩余参数运算符则更为简洁,可读性提升:
function sum(...nums) { let total = 0; for(const num of nums) { total += num; } return total; }
ES6以前,使用普通函数把其中每一个名字转换为大写形式:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) { return name.toUpperCase(); });
箭头函数表示:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map( name => name.toUpperCase() );
普通函数能够是函数声明或者函数表达式, 可是箭头函数始终都是表达式, 全程是箭头函数表达式, 所以所以仅在表达式有效时才能使用,包括:
const greet = name => `Hello ${name}!`;
能够以下调用:
greet('Asser');
若是函数的参数只有一个,不须要使用()
包起来,可是只有一个或者多个, 则必须须要将参数列表放在圆括号内:
// 空参数列表须要括号 const sayHi = () => console.log('Hello Udacity Student!'); // 多个参数须要括号 const orderIceCream = (flavor, cone) => console.log(`Here's your ${flavor} ice cream in a ${cone} cone.`); orderIceCream('chocolate', 'waffle');
通常箭头函数都只有一个表达式做为函数主题:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map( name => name.toUpperCase() );
这种函数表达式形式称为简写主体语法:
可是若是箭头函数的主体内须要多行代码, 则须要使用常规主体语法:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map( name => { name = name.toUpperCase(); return `${name} has ${name.length} characters in their name`; });
const mySundae = new Sundae('Chocolate', ['Sprinkles', 'Hot Fudge']); sundae这个构造函数内的this的值是实例对象, 由于他使用new被调用.
const result = obj1.printName.call(obj2);
函数使用call/apply被调用,this的值指向指定的obj2,由于call()第一个参数明确设置this的指向
data.teleport();
函数是对象的方法, this指向就是那个对象,此处this就是指向data.
teleport();
此处是this指向全局对象,在严格模式下,指向undefined.
javascript中this是很复杂的概念, 要详细判断this,请参考this豁然开朗
对于普通函数, this的值基于函数如何被调用, 对于箭头函数,this的值基于函数周围的上下文, 换句话说,this的值和函数外面的this的值是同样的.
function IceCream() { this.scoops = 0; } // 为 IceCream 添加 addScoop 方法 IceCream.prototype.addScoop = function() { setTimeout(function() { this.scoops++; console.log('scoop added!'); console.log(this.scoops); // undefined+1=NaN console.log(dessert.scoops); //0 }, 500); };
---------- 标题 const dessert = new IceCream(); dessert.addScoop();
传递给 setTimeout()
的函数被调用时没用到 new
、call()
或 apply()
,也没用到上下文对象
。意味着函数内的 this
的值是全局对象,不是 dessert
对象。实际上发生的状况是,建立了新的 scoops 变量(默认值为 undefined
),而后递增(undefined + 1
结果为 NaN
);
解决此问题的方式之一是使用闭包(closure):
// 构造函数 function IceCream() { this.scoops = 0; } // 为 IceCream 添加 addScoop 方法 IceCream.prototype.addScoop = function() { const cone = this; // 设置 `this` 给 `cone`变量 setTimeout(function() { cone.scoops++; // 引用`cone`变量 console.log('scoop added!'); console.log(dessert.scoops);//1 }, 0.5); }; const dessert = new IceCream(); dessert.addScoop();
箭头函数的做用正是如此, 将setTimeOut()
的函数改成剪头函数:
// 构造函数 function IceCream() { this.scoops = 0; } // 为 IceCream 添加 addScoop 方法 IceCream.prototype.addScoop = function() { setTimeout(() => { // 一个箭头函数被传递给setTimeout this.scoops++; console.log('scoop added!'); console.log(dessert.scoops);//1 }, 0.5); }; const dessert = new IceCream(); dessert.addScoop();
function greet(name, greeting) { name = (typeof name !== 'undefined') ? name : 'Student'; greeting = (typeof greeting !== 'undefined') ? greeting : 'Welcome'; return `${greeting} ${name}!`; } greet(); // Welcome Student! greet('James'); // Welcome James! greet('Richard', 'Howdy'); // Howdy Richard! greet() 函数中混乱的前两行的做用是什么?它们的做用是当所需的参数未提供时,为函数提供默认的值。可是看起来很麻烦, ES6引入一种新的方式建立默认值, 他叫默认函数参数: function greet(name = 'Student', greeting = 'Welcome') { return `${greeting} ${name}!`; } greet(); // Welcome Student! greet('James'); // Welcome James! greet('Richard', 'Howdy'); // Howdy Richard!
function createGrid([width = 5, height = 5]) { return `Generates a ${width} x ${height} grid`; } createGrid([]); // Generates a 5 x 5 grid createGrid([2]); // Generates a 2 x 5 grid createGrid([2, 3]); // Generates a 2 x 3 grid createGrid([undefined, 3]); // Generates a 5 x 3 grid createGrid() 函数预期传入的是数组。它经过解构将数组中的第一项设为 width,第二项设为 height。若是数组为空,或者只有一项,那么就会使用默认参数,并将缺失的参数设为默认值 5。 可是存在一个问题: createGrid(); // throws an error
Uncaught TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
出现错误,由于 createGrid()
预期传入的是数组,而后对其进行解构。由于函数被调用时没有传入数组,因此出现问题。可是,咱们可使用默认的函数参数!
function createGrid([width = 5, height = 5] = []) { return `Generating a grid of ${width} by ${height}`; } createGrid(); // Generates a 5 x 5 grid
Returns: Generates a 5 x 5 grid
就像使用数组默认值解构数组同样,函数可让对象成为一个默认参数,并使用对象解构:
function createSundae({scoops = 1, toppings = ['Hot Fudge']}={}) { const scoopText = scoops === 1 ? 'scoop' : 'scoops'; return `Your sundae has ${scoops} ${scoopText} with ${toppings.join(' and ')} toppings.`; } createSundae({}); // Your sundae has 1 scoop with Hot Fudge toppings. createSundae({scoops: 2}); // Your sundae has 2 scoops with Hot Fudge toppings. createSundae({scoops: 2, toppings: ['Sprinkles']}); // Your sundae has 2 scoops with Sprinkles toppings. createSundae({toppings: ['Cookie Dough']}); // Your sundae has 1 scoop with Cookie Dough toppings. createSundae(); // Your sundae has 1 scoop with Hot Fudge toppings.
默认函数参数只是个简单的添加内容,可是却带来不少便利!与数组默认值相比,对象默认值具有的一个优点是可以处理跳过的选项。看看下面的代码:
function createSundae({scoops = 1, toppings = ['Hot Fudge']} = {}) { … } 在 createSundae() 函数使用对象默认值进行解构时,若是你想使用 scoops 的默认值,可是更改 toppings,那么只需使用 toppings 传入一个对象: createSundae({toppings: ['Hot Fudge', 'Sprinkles', 'Caramel']}); 将上述示例与使用数组默认值进行解构的同一函数相对比。 function createSundae([scoops = 1, toppings = ['Hot Fudge']] = []) { … } 对于这个函数,若是想使用 scoops 的默认数量,可是更改 toppings,则必须以这种奇怪的方式调用你的函数: createSundae([undefined, ['Hot Fudge', 'Sprinkles', 'Caramel']]); 由于数组是基于位置的,咱们须要传入 undefined 以跳过第一个参数(并使用默认值)来到达第二个参数。
ES5建立类:
function Plane(numEngines) { this.numEngines = numEngines; this.enginesActive = false; } // 由全部实例 "继承" 的方法 Plane.prototype.startEngines = function () { console.log('starting engines...'); this.enginesActive = true; };
ES6类只是一个语法糖,原型继续实际上在底层隐藏起来, 与传统类机制语言有些区别.
class Plane { //constructor方法虽然在类中,但不是原型上的方法,只是用来生成实例的. constructor(numEngines) { this.numEngines = numEngines; this.enginesActive = false; } //原型上的方法, 由全部实例对象共享. startEngines() { console.log('starting engines…'); this.enginesActive = true; } } console.log(typeof Plane); //function
javascript中类其实只是function, 方法之间不能使用,
,不用逗号区分属性和方法.
静态方法
要添加静态方法,请在方法名称前面加上关键字 static
class Plane { constructor(numEngines) { this.numEngines = numEngines; this.enginesActive = false; } static badWeather(planes) { for (plane of planes) { plane.enginesActive = false; } } startEngines() { console.log('starting engines…'); this.enginesActive = true; } }
- 关键字class带来其余基于类的语言的不少思想,可是没有向javascript中添加此功能
- javascript类实际上仍是原型继承
- 建立javascript类的新实例时必须使用new关键字
使用新的super和extends关键字扩展类:
class Tree { constructor(size = '10', leaves = {spring: 'green', summer: 'green', fall: 'orange', winter: null}) { this.size = size; this.leaves = leaves; this.leafColor = null; } changeSeason(season) { this.leafColor = this.leaves[season]; if (season === 'spring') { this.size += 1; } } } class Maple extends Tree { constructor(syrupQty = 15, size, leaves) { super(size, leaves); //super用做函数 this.syrupQty = syrupQty; } changeSeason(season) { super.changeSeason(season);//super用做对象 if (season === 'spring') { this.syrupQty += 1; } } gatherSyrup() { this.syrupQty -= 3; } } 使用ES5编写一样功能的类: function Tree(size, leaves) { this.size = size || 10; this.leaves = leaves || {spring: 'green', summer: 'green', fall: 'orange', winter: null}; this.leafColor; } Tree.prototype.changeSeason = function(season) { this.leafColor = this.leaves[season]; if (season === 'spring') { this.size += 1; } } function Maple (syrupQty, size, leaves) { Tree.call(this, size, leaves); this.syrupQty = syrupQty || 15; } Maple.prototype = Object.create(Tree.prototype); Maple.prototype.constructor = Maple; Maple.prototype.changeSeason = function(season) { Tree.prototype.changeSeason.call(this, season); if (season === 'spring') { this.syrupQty += 1; } } Maple.prototype.gatherSyrup = function() { this.syrupQty -= 3;
}
super 必须在 this 以前被调用
在子类构造函数中,在使用 this 以前,必须先调用超级类。
class Apple {} class GrannySmith extends Apple { constructor(tartnessLevel, energy) { this.tartnessLevel = tartnessLevel; // 在 'super' 以前会抛出一个错误! super(energy); } }
Object.assign()
方法用于将全部可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
const target = { a: 1, b: 2 }; const source = { b: 4, c: 5 }; const returnedTarget = Object.assign(target, source); console.log(target); // expected output: Object { a: 1, b: 4, c: 5 } console.log(returnedTarget); // expected output: Object { a: 1, b: 4, c: 5 }
语法
Object.assign(target, ...sources)
参数
target
目标对象。
sources
源对象。
返回值
目标对象。
若是目标对象中的属性具备相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将相似地覆盖前面的源对象的属性。
Object.assign 方法只会拷贝源对象自身的而且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],因此它会调用相关 getter 和 setter。所以,它分配属性,而不只仅是复制或定义新的属性。若是合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。 String类型和 Symbol 类型的属性都会被拷贝。 在出现错误的状况下,例如,若是属性不可写,会引起TypeError,若是在引起错误以前添加了任何属性,则能够更改target对象。 注意,Object.assign 不会跳过那些值为 null 或 undefined 的源对象。
const obj = { a: 1 }; const copy = Object.assign({}, obj); console.log(copy); // { a: 1 }
针对深拷贝,须要使用其余办法,由于 Object.assign()
拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
let obj1 = { a: 0 , b: { c: 0}}; let obj2 = Object.assign({}, obj1); console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}} obj1.a = 1; console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}} console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}} obj2.a = 2; console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}} console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 0}} obj2.b.c = 3; console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}} console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}} // Deep Clone obj1 = { a: 0 , b: { c: 0}}; let obj3 = JSON.parse(JSON.stringify(obj1)); obj1.a = 4; obj1.b.c = 4; console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
const o1 = { a: 1 }; const o2 = { b: 2 }; const o3 = { c: 3 }; const obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。
const o1 = { a: 1, b: 1, c: 1 }; const o2 = { b: 2, c: 2 }; const o3 = { c: 3 }; const obj = Object.assign({}, o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } 属性被后续参数中具备相同属性的其余对象覆盖。
const o1 = { a: 1 }; const o2 = { [Symbol('foo')]: 2 }; const obj = Object.assign({}, o1, o2); console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox) Object.getOwnPropertySymbols(obj); // [Symbol(foo)]
const obj = Object.create({foo: 1}, { // foo 是个继承属性。 bar: { value: 2 // bar 是个不可枚举属性。 }, baz: { value: 3, enumerable: true // baz 是个自身可枚举属性。 } }); const copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }
const v1 = "abc"; const v2 = true; const v3 = 10; const v4 = Symbol("foo") const obj = Object.assign({}, v1, null, v2, undefined, v3, v4); // 原始类型会被包装,null 和 undefined 会被忽略。 // 注意,只有字符串的包装对象才可能有自身可枚举属性。 console.log(obj); // { "0": "a", "1": "b", "2": "c" }
const target = Object.defineProperty({}, "foo", { value: 1, writable: false }); // target 的 foo 属性是个只读属性。 Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); // TypeError: "foo" is read-only // 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。 console.log(target.bar); // 2,说明第一个源对象拷贝成功了。 console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。 console.log(target.foo); // 1,只读属性不能被覆盖,因此第二个源对象的第二个属性拷贝失败了。 console.log(target.foo3); // undefined,异常以后 assign 方法就退出了,第三个属性是不会被拷贝到的。 console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。
const obj = { foo: 1, get bar() { return 2; } }; let copy = Object.assign({}, obj); console.log(copy); // { foo: 1, bar: 2 } copy.bar的值来自obj.bar的getter函数的返回值 // 下面这个函数会拷贝全部自有属性的属性描述符 function completeAssign(target, ...sources) { sources.forEach(source => { let descriptors = Object.keys(source).reduce((descriptors, key) => { descriptors[key] = Object.getOwnPropertyDescriptor(source, key); return descriptors; }, {}); // Object.assign 默认也会拷贝可枚举的Symbols Object.getOwnPropertySymbols(source).forEach(sym => { let descriptor = Object.getOwnPropertyDescriptor(source, sym); if (descriptor.enumerable) { descriptors[sym] = descriptor; } }); Object.defineProperties(target, descriptors); }); return target; } copy = completeAssign({}, obj); console.log(copy); // { foo:1, get bar() { return 2 } }
此polyfill不支持 symbol 属性,由于ES5 中根本没有 symbol :
if (typeof Object.assign != 'function') { // Must be writable: true, enumerable: false, configurable: true Object.defineProperty(Object, "assign", { value: function assign(target, varArgs) { // .length of function is 2 'use strict'; if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } let to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { // Skip over if undefined or null for (let nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true }); }