阮一峰es6研习

精通javascript

参考文献(两个文档混合看,非常有用)
  1. 阮一峰 es6 :http://es6.ruanyifeng.com/#docs/object
  2. MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Working_with_Objects
  3. webpack打包:https://segmentfault.com/a/1190000006178770#articleHeader3
  4. 廖雪峰的js教程:https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499763408e24c210985d34edcabbca944b4239e20000
  5. over
node.js

javascript : BOM DOM ECMASCRIPT

DOM:节点和属性、增删改查、事件

HTML

CSS

框架:JQuery、Vue、Wepy、Angular

快速学习能力

参考文献:http://www.w3school.com.cn/htmldom/dom_intro.asp

DOM
  1. getElementById(id)
  2. appendChild(node)
  3. removeChild(node)
  4. innerHTML
  5. parentNode
  6. childNodes
  7. attributes
JQuery
  1. jquery 安装

  2. jquery语法

    $(document).ready(function(){
    
    --- jQuery functions go here ----
    
    });
    
  3. jquery 选择器

  4. jquery事件

  5. jquery 效果

  6. jquery HTML处理

  7. over

nodejs支持es6
'use strict'//加上这句声明
$ node test.js
let和const
{
    let b = 100;
    var c = 1;
}
console.log(b);//b is not defined
let命令所在的代码块内有效。
for循环的计数器,就很合适使用let命令。

var array = ['gaolong', 'xiaoming', 'xiaoli'];
for (let i = 0; i < array.length; i++) {}
console.log(i); //i is not defined, i只在for循环体内有效

var array = ['gaolong', 'xiaoming', 'xiaoli'];
var a = [];
for (let i = 0; i < array.length; i++) {
    a[i] = function () {
        console.log(i);
    }
}
a[2](); //此处数组中存放的是函数,所以可以直接执行a[i]();重点比较let i与 var i的不同

let不存在变量提升,var存在。即脚本未执行前var变量已经存在,只是值为undefined.

let暂时性死区,temporal dead zone
var tmp = 123;
if (true) {
    tmp = 'abc'; //referenceError。该区域已经被let 覆盖。
    let tmp; 
}
let不允许在相同作用域内,重复声明同一个变量。
因此,不能在函数内部重新声明参数。
ES5只有全局作用域和函数作用域,没有块级作用域.

因此会出现这两种情况:
第一:内层变量可能会覆盖外层变量。
var tmp = new Date();
function f() {
    console.log(tmp);
    if (false) {
        var tmp = 'hello wolrd';
    }
}
f(); //undefined ,该使用let
'use strict';
if (true) {
  function f() {}
}
f(); // ReferenceError: f is not defined,es6函数在块级作用域内,外部不能使用。
第二:用来计数的循环变量泄露为全局变量。

const
只读常量,不能改变。
'use strict';
const pi = 3.14159;
pi // 3.14159
pi = 3; typeerror: pi is read-only
//如果是常规模式,不会报错,但是赋值无效
//const 一旦声明就必须赋值,const foo,报错
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后使用
const声明的常量,也与let一样不可重复声明。

对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。
const foo = {}
foo.prop = 123;
foo.prop ;// 123
foo = {} //type error ;foo is readonly
const a = [];
a.push('Hello');//ok
a.length = 0; //ok
a = ["schiller"]; //error
如果真的想将对象冻结,应该使用Object.freeze方法。

 全局对象属性
 全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。ES5之中,全局对象的属性与全局变量是等价的。
 var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1

let b = 1;
window.b // undefined
上面代码中,全局变量a由var命令声明,所以它是全局对象的属性;全局变量b由let命令声明,所以它不是全局对象的属性,返回undefined。

解构赋值
//注意,解构赋值在node环境中暂且无法运行,具体原因待探究,可以使用新建rn项目 //跑来测试
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
var [a, b, c] = [1, 2, 3];
let [x, y, ...z] = ['a'];
// x = 'a', y = undefined, z = []
如果解构不成功,变量的值就等于undefined。
如果等号右边不是数组,严格的说不是可遍历结构,那将会报错。
//报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的表达式都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)
事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
function* fibs() {
  var a = 0;
  var b = 1;
  while (true) {
   yield a;
    [a, b] = [b, a+b]; 
  }
}
var [first ,second, third, fourth, fifth, sixth] = fibs();
sixth //5
默认值
var [foo = true] = [];
foo //true
ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。
如:
[x, y = 'b'] = ['a']; // x='a', y='b'
[x, y = 'b'] = ['a', undefined]; // x='a', y='b'
var [x = 1] = [null] //x = null

对象的解构赋值
解构赋值还可以用于对象
var {foo, bar} = {foo: 'aaa', bar: 'bbb'};
// foo = 'aaa', bar = 'bbb'
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值,如:
var {foo, bar} = {bar:'aaa', foo: 'bbb'};
//foo = "bbb", bar='aaa'
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
var {foo: baz} = {foo:'aaa', bar:'bbb'}
//baz = 'aaa' //foo  error, foo is not defined
//类似var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
变量的声明和赋值是一体的
对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
let foo;
let {foo} = {foo:1} //erro, duplicate declaration of foo
上面代码中,解构赋值的变量都会重新声明,所以报错了。不过,因为var命令允许重新声明,所以这个错误只会在使用let和const命令时出现。如果没有第二个let命令,上面的代码就不会报错。
let foo;
({foo} = {foo:1}) //ok
//允许嵌套s
var obj = {
      p: [
        "Hello",
        {y: 'world'} 
      ]
    };
 var {p:[x, {y}]} = obj;
//对象的解构也可以指定默认值,默认值生效的条件是,对象的属性值严格等于undefined

字符串的解构赋值
const [a, b,c,d, e] = 'hello';//a ='h', ...
类似数组对象都有一个length
let {length : len} = 'hello';
len // 5
数值和布尔值得解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
 
函数参数的解构赋值
function add([x,y]) {
  return x + y;
} 
var arr = [[1, 2], [3, 4]].map(([a, b]) => a + b);

函数的解构也可以使用默认值
function move ({x = 0, y = 0} = {}) {
  return [x ,y];
}
move({x: 3, y: 8});  //[3,8]
move({x: 3}); //[3,0]
move({}); //[0,0]
move(); // [0,0]

function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
解构赋值用途
1、交换变量的值
[x, y] = [y, x]; 
2、 从函数返回多个值
// 返回一个数组
 
function example() {
  return [1, 2, 3];
}
var [a, b, c] = example();
 
// 返回一个对象
 
function example() {
  return {
    foo: 1,
    bar: 2
  };
}
var { foo, bar } = example();
3、 函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3])
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1})
4、 提取JSON数据
var jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
}
let { id, status, data: number } = jsonData;
 
console.log(id, status, number)
// 42, OK, [867, 5309]
 
5、 函数参数的默认值
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
}; 
6、 遍历Map结构  
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
 
for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world
 
如果只想获取键名,或者只想获取键值,可以写成下面这样。    
// 获取键名
for (let [key] of map) {
  // ...
}
 
// 获取键值
for (let [,value] of map) {
  // ...
}    
7、 输入模块的指定方法    
const { SourceMapConsumer, SourceNode } = require("source-map"); 
字符串的扩展
字符的Unicde表示法
codePointAt() //如果确实要处理字符
String.fromCodePoint()
字符串的遍历器接口
at();
'abc'.at(0) // "a"
'𠮷'.at(0) // "𠮷"
normalize()
includes(), startsWidth(),endsWith()
传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法:
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
//第二个参数表示搜索位置
var s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。

es7提供补齐padStart(), padEnd()
'x'.padStart(5, 'ab').// 'ababx'
'x'.padEnd(4, 'ab').// 'xabab'

模板字符串
标签模板、实例模板:暂不讨论

String.raw();

参考文献:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Text_formatting
trim
repeat
normalize
toLowerCase, toUpperCase
match,replace,search
substring,substr
slice
split
contact
fromCharCode,fromCodePoint
startsWith,endsWith,includes
indexOf,lastIndexOf
charAt,charCodeAt,codePointAt
正则扩展
RegExp构造函数
var regex = new RegExp('xyz', 'i');
//等价
var regex = /xyz/i
或者
var regex = new RegExp(/xyz/i);
=> var regex = /xyz/i
es6:
new RegExp(/abc/ig, 'i').flags //'i'

字符串的正则方式:
字符串对象共有4个方法,可以使用正则表达式:match()、replace()、search()和split()。

正则匹配难度比较大,暂时不讨论
数值类型
二进制和八进制的表示
ES6提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
0b111110111 === 503 // true
0o767 === 503 // true
Number.isFinite() , NumberisNaN()
ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值。
Number.isFinite()用来检查一个数值是否非无穷(infinity)。
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
Number.isNaN()用来检查一个值是否为NaN。
它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回false。

Number.parseInt(), Number.parseFloat()
// ES5的写法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

Number.isInteger()
Number.isInteger()用来判断一个值是否为整数。
Number.isInteger(25) // true
Number.isInteger(25.0) // true //js中25和25.0采用同样的存储方式
Number.isInteger(25.1) // false 
Number.isInteger("15") // false
Number.isInteger(true) // false

//js中这样部署 Number.isInteger();
(function (global) {
  var floor = Math.floor,
    isFinite = global.isFinite;

  Object.defineProperty(Number, 'isInteger', {
    value: function isInteger(value) {
      return typeof value === 'number' && isFinite(value) &&
        value > -9007199254740992 && value < 9007199254740992 &&
        floor(value) === value;
    },
    configurable: true,
    enumerable: false,
    writable: true
  });
})(this);

ES6在Number对象上面,新增一个极小的常量Number.EPSILON。
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// '0.00000000000000022204'

//JavaScript能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。
Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER
Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内

Math对象的扩展
Math.trunc() 、 Math.sign() 、Math.cbrt() 、Math.clz32
Math.imul()、Math.fround()、Math.hypot()、
对数方法
Math.expm1()//Math.expm1(x)返回ex - 1,
Math.log1p()、Math.log10()、Math.log2()
三角方法
指数运算符
let a = 2;
a **= 2;
// 等同于 a = a * a;

let b = 3;
b **= 3;
// 等同于 b = b * b * b;

参考文献:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Numbers_and_dates
数学对象Math
Math.PI Math.sin(1.34)
日期对象
处理日期时间的Date对象方法可分为以下几类:

"set" 方法, 用于设置Date对象的日期和时间的值。
"get" 方法,用于获取Date对象的日期和时间的值。
"to" 方法,用于返回Date对象的字符串格式的值。
parse 和UTC 方法, 用于解析Date字符串。

数字时钟
function JSClock() {
  var time = new Date();
  var hour = time.getHours();
  var minute = time.getMinutes();
  var second = time.getSeconds();
  var temp = "" + ((hour > 12) ? hour - 12 : hour);
  if (hour == 0)
    temp = "12";
  temp += ((minute < 10) ? ":0" : ":") + minute;
  temp += ((second < 10) ? ":0" : ":") + second;
  temp += (hour >= 12) ? " P.M." : " A.M.";
  return temp;
}
数组的扩展
扩展运算符 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
复制数组
合并数组
与解构赋值结合
字符串
实现了 Iterator 接口的对象
Map 和 Set 结构,Generator 函数

Array.from() //将类似数组的对象转成真正的数组
let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3,
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from('hello') ;// ['h', 'e'...];

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

Array.of方法用于将一组值,转换为数组。
Array.of(3,11,8) //[3, 11,8];

copyWithin()
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。target、start、end三个参数
[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]

find的()和findIndex//数组实例的find方法,用于找出第一个符合条件的数组成员
[1, 4, -5, 10].find((n) => n < 0)// -5
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
fill方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2);// ['a', 7, 'c']
数组实例的entries(), keys(), values()

参考文献:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Indexed_collections
var arr = new Array(element0, element1, ..., elementN);
var arr = Array(element0, element1, ..., elementN);
var arr = [element0, element1, ..., elementN];
//创建和填充数组
var emp = [];
emp[0] = "Casey Jones";
emp[1] = "Phil Lesh";
emp[2] = "August West";
引用数组元素
遍历数组
数组方法
contact
var myArray = new Array("1", "2", "3");
myArray = myArray.concat("a", "b", "c"); 

joint 将数组的所有元素连接成一个字符串。
var myArray = new Array("Wind", "Rain", "Fire");
var list = myArray.join(" - "); // list is "Wind - Rain - Fire"

push 在数组末尾添加一个或多个元素,并返回数组操作后的长度。
var myArray = new Array("1", "2");
myArray.push("3"); // myArray is now ["1", "2", "3"]

pop()

shift() 移除数组第一个元素

unshift()

slice从数组提取一个片段,并作为一个新数组返回

reverse() 颠倒数组元素的顺序:第一个变成最后一个,最后一个变成第一个。

sort() 给数组元素排序。

indexOf(searchElement[, fromIndex]) 在数组中搜索searchElement 并返回第一个匹配的索引。

lastIndexOf(searchElement[, fromIndex]) 和 indexOf 差不多,但这是从结尾开始,并且是反向搜索。

forEach(callback[, thisObject]) 在数组每个元素项上执行callback

map(callback[, thisObject]) 在数组的每个单元项上执行callback函数,并把返回包含回调函数返回值的新数组(译者注:也就是遍历数组,并通过callback对数组元素进行操作,并将所有操作结果放入数组中并返回该数组)

filter(callback[, thisObject]) 返回一个包含所有在回调函数上返回为true的元素的新数组(译者注:callback在这里担任的是过滤器的角色,当元素符合条件,过滤
器就返回true,而filter则会返回所有符合过滤条件的元素)。

every(callback[, thisObject]) 当数组中每一个元素在callback上被返回true时就返回true(译者注:同上,every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。

some(callback[, thisObject]) 只要数组中有一项在callback上被返回true,就返回true(译者注:同上,类似every,不过前者要求都符合筛选条件才返回true,后者只要有符合条件的就返回true

reduce(callback[, initialValue]) 使用回调函数 callback(firstValue, secondValue) 把数组列表计算成一个单一值(译者注:他数组元素两两递归处理的方式把数组计算成一个值)
var a = [10, 20, 30];
var total = a.reduce(function(first, second) { return first + second; }, 0);
console.log(total) // Prints 60

多维数组
var a = new Array(4);
for (i = 0; i < 4; i++) {
  a[i] = new Array(4);
  for (j = 0; j < 4; j++) {
    a[i][j] = "[" + i + "," + j + "]";
  }
}
函数的扩展
javascript中函数就是对象。对象是键值对的集合并拥有一个连接到原型对象的隐形连接,对象连接到Object.prototype,函数连接到Function.prototype.
函数是对象。
函数字面量:保留字function 、函数名(可省略,用于递归)、参数、执行域
//ES6函数默认值
var add = function(a, b = 0) {
  return a+b;
}
//与解构赋值默认值结合使用
function foo({x, y = 5}) {
  console.log(x, y);
}
foo({}) // undefined 5
//函数length属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数
//作用域,指定函数参数
function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}
foo()
// Error: Missing parameter
//rest参数
function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}
var a = [];
push(a, 1, 2, 3)
//name属性
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"

//箭头函数
var sum = (c, d) => {return c+d};
方法调用模式:当一个函数是一个对象的属性时
var myObject = {
  value: 0,
  increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
  }
};
myObject.increment(); 
alert(myObject.value);// 1;
函数调用模式:当一个函数并非一个对象的属性时。
当函数以此模式调用时,this被绑定到全局对象。这是语言设计上的一个错误。正确的设计应该是当内部函数被调用时,this应该仍然绑定到外部函数的this变量。
var sum = add(3,4);
//解决的办法是:如果该方法定义一个变量并给它赋值this,那么内部函数就可以通过这个变量访问到外部函数的this.
//给myObject增加一个double方法。
myObject.double = function () {
  var that = this;//如果不这么赋值会发生意想不到的错误,内部函数无法访问到外部的this,内部函数访问的是全局的this,就有bug.
  var helper = function () {
  that.value = add(that.value, that.value);
 }
  helper();//以函数形式调用helper.
};
myOjbect.double(); //myOjbect.value = 2, 否则是1.

构造器调用模式:
var Quo = function (string) {//创建一个Quo名的构造器函数。并构造一个带有status属性的对象。
    this.status = string;
 } 
Quo.prototype.get_status = function() {//给Quo的所有实例提供一个方法。
     return this.status;
};
var myQuo = new Quo("confused"); //new 构造一个Quo实例
alert(myQuo.get_status());

Apply调用模式
javascript是函数式的面向对象的语言,所以函数可以拥有方法。
apply方法让我们构建一个参数数组并用其去调用函数,apply方法接收两个参数,一个是将被绑定给this的值,第二个是参数数组。
var array = [3, 5];
var sum = add.apply(null, array);//sum = 8
var statusObject = {
  status: 'A-OK',
};
var status = Quo.prototype.get_status.apply(statusObject); //status : 'A-OK'

函数参数:arguments数组是默认的。
返回值:
异常:

给函数添加方法:Function.prototype.method = function() {}

递归:


参考文献:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Functions
值传递和引用传递 ---- 函数重要知识点
function map (f, a) {
    var result = [];
    for (let i = 0; i < a.length; i++) {
        result[i] = f(a[i]);
    }
    return result;
}

var f = function(x) {
    return x*x*x;
}

var numbers = [0,1,2,5,10];
var cube = map(f,numbers);
console.log(cube);
for(let value  in numbers) {
    console.log(numbers[value]);
}

函数作用域
递归
嵌套函数和闭包
闭包
函数参数
箭头函数
this

ES6函数

箭头函数:
var f = v => v, 等同于: var f = function(v) {return v;};
var geetTmpItem = id => ({id:id, name: 'Tmp'});//返回对象时,用圆括号括起来
const full = ({first, last}) => first + '' + last;//变量解构
var sum = (num1, num2) => { return num1 + num2; }
对象
//null表示没有对象,即该处不应该有值
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
Object.getPrototypeOf(Object.prototype)// null

//undefined表示"缺少值",就是此处应该有一个值,但是还没有定义
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var  o = new Object();
o.p // undefined
var x = f();
x // undefined

对象有时也被叫作关联数组
var myCar = new Object();
myCar.make = "Ford"
或者
myCar["model"] = "Mustang";

//枚举属性
function showProps(obj,objName) {
    var result = "";
    for(var i in obj) {
        if (obj.hasOwnProperty(i)) {
            result += objName + "." + i + "=" + obj[i] + "\n";
        }
    }
    return result;
}

var myCard = new Object();
myCard.make = "Ford";
myCard.model = "Mystang";
myCard.year = 1969;
console.log(showProps(myCard, "myCard"))

Object.keys(o)
Object.getOwnPropertyNames(o)
//使用对象初始化器初始化一个对象
var obj = { property_1:   value_1,   // property_# 可以是一个标识符...
            2:            value_2,   // 或一个数字...
           ["property" +3]: value_3,  //  或一个可计算的key名... 
            // ...,
            "property n": value_n }; // 或一个字符串
//使用构造函数初始化对象
function Car(make, model, year) {
    this.make = make ;
    this.model = model;
    this.year = year;
}
var mycard_1 = new Car("eagle", "talon",1993)

//使用Object.create方法
//为对象类型定义属性,其中Car为构造函数
Car.prototype.color = null;
car1.color = "black";

//定义方法
objectName.methodname = function_name;

var myObj = {
  myMethod: function(params) {
    // ...do something
  }
  // 或者 这样写也可以  
  myOtherMethod(params) {
    // ...do something else
  }
};

get 和 set方法
删除属性
比较对象

ES6对象方法简写
let birth = '2000/01/01';
const Person = {
  name: '张三',
  //等同于birth: birth
  birth,
  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }
};

Object.is()
Object.assign() 浅拷贝、同名属性替换、数组处理、取值函数的处理
1)给对象添加属性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}
Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};
3)克隆对象
4)合并多个对象

//属性的可枚举性和遍历
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
属性的遍历

//__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf() 
// es6 的写法
const obj = {
  method: function() { ... }
};
obj.__proto__ = someOtherObj;

// es5 的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

//super关键字

//Object.keys(),Object.values(),Object.entries()

//对象的扩展运算符
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

Class类
1.ES5写法
function Point(x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.toString = function() {
    return '(' + this.x + ',' + this.y + ')';
};
console.log(Point.prototype);[ 'toString' ]

2.ES6写法
class Point {
    constructor(x, y) {//默认方法
        this.x = x;
        this.y = y;
    }
    toString() {
        return '(' + this.x + ',' + this.y + ')';
    }
    get prop () {//定义getter和setter函数
        return 'shit-get';
    }
    set prop(value) {
        console.log('setter'+value);
    }
    static classMethod() {//静态方法
        return 'Hello, world';
    }
}
var point = new Point(8,0);
console.log(point.toString());//(8.0)
console.log(typeof point);//object
console.log(typeof Point);//function
console.log(Point.classMethod());//调用静态方法
类的所有方法都定义在类的prototype属性上面,在类的实例上面调用方法,其实就是调用原型上的方法,如下
point.constructor = Ponit.prototype.constructor//true

3.类的内部所有定义的方法,都是不可枚举的(non-enumerable),这与ES5不一致。
console.log(Point.prototype);// Point{}
console.log(Object.getOwnPropertyNames(Point.prototype));//[ 'constructor', 'toString' ]
console.log(point.hasOwnProperty('x'));//true
console.log(point.hasOwnProperty('toString'));//false
console.log(point.__proto__.hasOwnProperty('toString'));//true
4.同ES5,类的所有实例共享一个原型对象
point1.__proto__ = point2.__proto__
可以通过__proto__为Class添加方法。
point.__proto__.printName = function(){};
//同
Point.prototype.printName = function(){};

以上更改类慎用

5.class继承
class ColorPoint extends Point{}
子类必须在constructor方法中调用super方法,否则新建实例会报错。

6.类的prototype和__proto__
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A{} class B extends A{}
B.__proto__ === A;//true
B.prototype.__proto__ === A.prototype;//true
Object.getPrototypeOf方法可以用来从子类上获取父类:
Object.getPrototypeOf(B) == A//true

7.super关键字
super这个关键字,有两种用法,含义不同。
(1)作为函数调用时(即super(...args)),super代表父类的构造函数。
(2)作为对象调用时(即super.prop或super.method()),super代表父类。注意,此时super既可以引用父类实例的属性和方法,也可以引用父类的静态方法。

子类继承父类,必须调用super,且需要在this前调用

8.实例的__proto__属性
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。

9.ES6允许原生构造函数的继承
class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}
但是es5是不能够直接继承原生构造函数的。
Boolean()/Number()/String()/Array()/Date()/Function()/RegExp()/Error/Object()

9.Class的getter、setter
写法见上2。

10.class的generator函数
class Foo {
    constructor(...args) {
        this.args = args;
    }
    * [Symbol.iterator]() {
        for (let arg of this.args) {
            yield arg;
        }
    }
}
for (let x of new Foo('hello', 'world')) {
    console.log(x);
}
如果某个方法之前加上星号(*),就表示该方法是一个Generator函数。
Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器.

11.Class的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
代码见上2。
父类静态方法可以被子类继承,且可以从super对象调用。

12.Class静态属性和实例属性
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
只有之中方法定义可行。es6类内部不能定义静态属性。
目前,es6的实例属性也只能定义在方法或者constructor方法里。es7允许。

13.newTarget属性
14.Mixin模式
Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。它在ES6的实现如下。
class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}

15.Object.getPrototypeOf方法可以用来从子类上获取父类。
Iterator和 for…of循环
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Promise对象
1.Promise含义、基本用法
var promise = new Promise(function(resolve, reject) {
    console.log('fuck');
    var shit = 'g';
    if ('st') {
        resolve('shit');
    }else {
        reject('go');
    }
});
console.log(promise);
promise.then(function(value) {//使用、success、resolved
    console.log('we did it.');
}, function(error) {//failure、rejected
});
Promise对象三种状态:Pending、Resolved、Rejected
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
console.log(Object.getOwnPropertyNames(promise.__proto__));
//[ 'constructor', 'chain', 'then', 'catch' ]

2.Promise对象实例
var getJSON = function(url) {
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

    function handler() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

3.Promise.prototype.then()
console.log(Object.getOwnPropertyNames(promise.__proto__));//[ 'constructor', 'chain', 'then', 'catch' ]
then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  //...
});
面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

4.Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
getJSON("/posts.json").then(function(posts) {
  //...
}).catch(function(error) {
    // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});//如果状态变为Rejected则会调用catch方法指定回调函数。处理错误。

5.Promise.all()
用于将多个Promise实例包装成一个新的Promise实例。只要数组中其中一个状态被reject,则返回一个reject实例。
var promises = [1,3,4,5,90,8].map(function(id) {
  return getJSON("/post/" + id + ".json");
});
Promise.all(promises).then(function(post){
//...
}).catch(function(reason){
  /...
});

6.Promise.race()
同上、方法
7.Promise.resolve()
该方法将现有对象转化为Promise对象。
Promise.resolve('foo')
//等价于
new Promise(resolve => resolve('foo'));
8.Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。用法与上面一致
var p = Promise.reject('出错了');
=> var p = new  Promise((resolve, reject) => reject('出错了'));
p.then(null, function(s) {
  console.log(s);
});//出错了
9.done()和finally()

Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误.
10.应用
const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    var image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};
async函数与Promise、Generator函数一样,是用来取代回调函数、解决异步操作的一种方法。它本质上是Generator函数的语法糖。async函数并不属于ES6,而是被列入了ES7,但是traceur、Babel.js、regenerator等转码器已经支持这个功能,转码后立刻就能使用。
Set和Map结构
1.Set,不会重复添加元素
var s = new Set();
[1,2,1,5,5,8,6,2,7].map(x=>s.add(x));
for(let i of s){log(i)}//1,2,5,6,7,8
2.常用操作
var set = new Set([1,2,3,4,4]);
[...set];//[1,2,3,4]
set.size; 4
[...new Set(array)];//去除数组重复成员
向Set加入值的时候,不会发生类型转换,5和'5'不同,NaN等于自身
两个对象总是不等:set.add({}); set.size =1,set.add({}),set.size=2;

2.属性和方法
属性:constructor、size
方法:操作方法和遍历方法
get(key);
set(key,value);
add(value)
delete(value)
has(value)
clear()
Array.from(set)=>array
keys();
values();
entries();
forEach();set.forEach((value, key)=>console.log(value*2));
扩展运算符内部用for...of循环,所以也可以用于Set结构,数组的map和filter方法也可以用于Set了。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// [1, 2, 3, 4]
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// [2, 3]
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// [1]
3.WeakSet
4.Map
JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是只能用字符串当作键。这给它的使用带来了很大的限制.
Map也是键值对,但是键的范围不限于字符串,各种类型的值都可以当做对象。
var m = new Map();
var o = {p: 'Hello world'};
m.set(o, 'content');
m.get(o);//"content"
m.has(o)//true
m.delete(o)//true
m.has(o)//false
var map = new Map([['name', '张三'], ['title', 'Author']]);//接受数组做参数
3.与其他类型的互相转化
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap];// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
转为对象:
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

Module
1.前言
JavaScript语言一直没有分模块的东西,社区解决方案是CommonJS和AMD两种。
静态优化、运行时加载。
es5、CommonJS模块
let {stat, exits, readFile } = require('fs');
上述代码中实质上是整体加载fs模块生成一个对象,然后从对象中读取三个方法。为运行时加载。
es6、Module
import {stat, exits, readFile} from 'fs';
上述代码的实质是从fs中加载3个方法,其他方法不加载,为编译时加载。

2.use strict
严格模式主要有以下限制。

变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀0表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)

3.export命令
export 可输出函数、类、变量
export var firstName = 'Mike';
export default Home;
export {firstName, lastName, year};
export function mutiply(x, y) {
  return x * y;
}
export {f};//f为function
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1(){...}
export {v1 as streanV1};
另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);上面代码输出变量foo,值为bar,500毫秒之后变成baz

4.import
import {firstName, lastName, year} from './profile';
import {lastName as surname} from './profile';//使用as关键字重命名

5.模块的整体加载
//circel.js
export function area(){}
export function name(){}
//main.js
import {area, name} from './circle'
整体加载:使用 * as关键字
import * as circle from './circle'
console.log(circle.area);

6.export default命令
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
export default function() {log('foo')}; //export_default.js
import  customname from './export_default'
 customName();//'foo'
 
7.模块的继承

Generator函数
1.属性
Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
function* helloworldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}
var hw = helloworldGenerator();
hw.next();//hello,done
hw.next();//world;
hw.next();//ending
hw.next();//undefined
2.for...of循环
for(let v of helloworldGenerator()) {
  console.log(v);//1 2 3 4 5
}

webpack入门
参考文献:https://segmentfault.com/a/1190000006178770#articleHeader3c
1、webpack作用
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用

2、webpack安装
//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack

3、正式使用
在项目根目录创建package.json 文件 或者 在终端通过npm init创建pakage.json

4、创建app和public文件夹

5、正式使用webpack
# {extry file}出填写入口文件的路径,本文中就是上述main.js的路径,
# {destination for bundled file}处填写打包文件的存放路径
# 填写路径的时候不用添加{}
webpack {entry file} {destination for bundled file}
# webpack非全局安装的情况
node_modules/.bin/webpack app/main.js public/bundle.js

6、通过配置文件使用webpack
在项目根目录下创建webpack.config.js
module.exports = {
  entry:  __dirname + "/app/main.js",//已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public",//打包后的文件存放的地方
    filename: "bundle.js"//打包后输出文件的文件名
  }
}

7、更快捷打包方式
{
  "name": "lib",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack",
    "server": "webpack-dev-server --open"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^3.11.0"
  }
}

8、使用webpack构建本地服务器
全局安装
npm install -g webpack-dev-server
在终端输入npm run server 即可打开浏览器查看结果

9、Loaders
10、Babel安装和配置
# npm一次性安装多个依赖模块,模块之间用空格隔开.非全局安装
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react

#npm install --save react react-dom
npm install --save react react-dom

Babel其实可以完全在 webpack.config.js 中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。我们现在的babel的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc里的babel配置选项)

11、一切皆模块
webpack提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同,css-loader使你能够使用类似 @import 和 url(...)的方法实现 require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

12、CSS module
被称为CSS modules的技术意在把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。Webpack对CSS模块化提供了非常好的支持,只需要在CSS loader中进行简单配置即可,然后就可以直接把CSS的类名传递到组件的代码中,这样做有效避免了全局污染

module.exports = {

    ...

    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true, // 指定启用css modules
                            localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
                        }
                    }
                ]
            }
        ]
    }
};

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容