一、ES6 和 JavaScript

ECMAScript、JavaScript、NodeJs,它们的区别是什么?

ECMAScript:简称ES,是一个语言标准(循环、判断、变量、数组等数据类型)

JavaScript:运行在浏览器端的语言,该语言使用ES标准。 ES + web api = JavaScript

NodeJs:运行在服务器端的语言,该语言使用ES标准。 ES + node api = JavaScript

无论JavaScript,还是NodeJs,它们都是ES的超集(super set)

ES6.0: 2015, 从该版本开始,不再使用数字作为编号,而使用年份

二、let和const命令

1、使用var声明变量

(1)允许重复的变量声明:导致数据被覆盖

var a = 100
//一万行代码
var a = 1

(2)变量提升:怪异的数据访问、闭包问题(只有函数级作用域,没有块级作用域)

//先声明一个var a,再执行if-else
if(Math.random()<0.5){
    var a = "abc"
    console.log(a)
}else{
    console.log(a)
}
console.log(a)

(3)全局变量挂载到全局对象:全局对象成员污染问题

var a = 10
console.log(window.a)//10

2、使用let声明变量

块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域。

let命令,用来声明变量。但是所声明的变量,只在let命令所在的代码块内有效。

(1)let声明的变量不会挂载到全局对象

顶层对象

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

ES6 规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
console.log(window.a) // 1

let a = "ll"
console.log(window.a)//undefined
(2)不允许当前作用域范围内重复声明。在块级作用域中用let定义的变量,在作用域外不能访问
console.log(name)//会报错
let name = "ll"
console.log(name)//"ll"
let name = "lk" //报错,检查到当前作用域已经声明过,不允许重复声明
(3)使用let不会有变量提升,因此,不能在定义let变量之前使用它
// var 的情况
console.log(a); // 输出undefined
var a = 2;

// let 的情况
console.log(b); // 报错ReferenceError
let b = 2;

暂时性死区

var a  = 10
if(true){
	a = 5
	let a = 10
}
存在全局变量a,但是if后的{}块级作用域内let又声明了一个局部变量a,导致后者绑定这个块级作用域,所以在let声明变量前,对a赋值会报错。

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access ‘a’ before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。

在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)。在循环中使用let声明的循环变量,在循环结束后会销毁

3、使用const声明常量

constlet完全相同,仅在于const声明一个只读的常量。一旦声明,常量的值就不能改变。

实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:

1. 根据经验,开发中的很多变量,都是不会更改,也不应该更改的。
2. 后续的很多框架或者是第三方JS库,都要求数据不可变,使用常量可以一定程度上保证这一点。
(1)const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const a = 1
a = 3;// TypeError: Assignment to constant variable.

const b;// SyntaxError: Missing initializer in const declaration
(2)常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
const a = {};
// 为 a 添加一个属性,可以成功
a.prop = 123;
console.log(a.prop) // 123

// 将 a 指向另一个对象,就会报错
a = {}; // TypeError: "a" is read-only
(3)命名规范
1. 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割
2. 普通的常量:使用和之前一样的命名即可
(4)在for循环中,循环变量不可以使用常量,for-in循环可以

三、字符串的扩展

1、更好的Unicode支持

早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字。我们将一个16位的二进制编码叫做一个码元(Code Unit)。后来,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)。

一个字符对应一个码点(且每个字符的码点都是唯一的),但一个码点可能需要一个代码单元或者两个代码单元才能表示。

charCodeAt():读取一个码元

例子 𠮷:码点是0x20BB7,UTF-16 编码为0xD842 0xDFB7

const text = "𠮷"; //占用了两个码元(32位)
console.log("字符串长度:", text.length); //2
console.log("使用正则测试:", /^.$/.test(text));//false 读到了两个码元,希望匹配一个 

console.log("得到第一个码元:", text.charCodeAt(0));//55362
console.log("得到第二个码元:", text.charCodeAt(1));//57271

codePointAt():根据字符串码元的位置得到其码点。能够正确处理 4 个字节储存的字符,返回一个字符的码点。

//𠮷:\ud842\udfb7
console.log("得到第一个码点:", text.codePointAt(0));//134071  获取完整的
console.log("得到第二个码点:", text.codePointAt(1));//57271

同时,ES6为正则表达式添加了一个flag: u,如果添加了该配置,则匹配时,使用码点匹配

console.log("使用正则测试:", /^.$/u.test(text));
/**
 * 判断字符串char,是32位,还是16位
 * @param {*} char 
 */
function is32bit(char, i) {
    //如果码点大于了16位二进制的最大值,则其是32位的
    return char.codePointAt(i) > 0xffff;
}

/**
 * 得到一个字符串码点的真实长度
 * @param {*} str 
 */
function getLengthOfCodePoint(str) {
    var len = 0;
    for (let i = 0; i < str.length; i++) {
        //i在索引码元
        if (is32bit(str, i)) {
            //当前字符串,在i这个位置,占用了两个码元
            i++;
        }
        len++;
    }
    return len;
}

console.log("𠮷 是否是32位的:", is32bit("𠮷", 0))
console.log("ab𠮷ab 的码点长度:", getLengthOfCodePoint("ab𠮷ab"))
字符串的遍历器for...of循环遍历
const text = "𠮷as";
for(let i=0;i<text.length;i++)
{
	console.log(text[i]) //� � a s
}
//普通for循环无法正确识别。一次只能读取一个代码单元


//for-of可以正确识别一个完整的符号
const text = "𠮷as";
for(const item of text)
{
	console.log(item) //𠮷 a s
}

2、字符串新增API

以下均为字符串的实例方法

(1)includes():判断字符串中是否包含指定的子字符串,第二个参数指定从哪个位置开始查找,返回true和false

const str = "今天天气很好啊!"
const result1 = str.includes("气")
const result2 = str.includes("气",5)
console.log(result1,result2)

(2)startsWith():判断字符串中是否以指定的字符串开始,也可以传第二个参数

const str = "今天天气很好啊!"
const result = str.startsWith("气")
console.log(result)

(3)endsWith():判断字符串中是否以指定的字符串结尾

const str = "今天天气很好啊!"
const result = str.endsWith("!")
console.log(result)

(4)repeat():将字符串重复指定的次数,然后返回一个新字符串。

const str = "我很帅!"
const result = str.repeat(4)
console.log(result)//我很帅!我很帅!我很帅!我很帅!

//如果repeat的参数是负数或者Infinity,会报错。
const result = 'a'.repeat(Infinity)// RangeError
const result = 'a'.repeat(-1)// RangeError

补充

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。实例方法

(5)padStart():第一个参数是字符串补全生效的最大长度,第二个参数是用来从起始位置补全的字符串。

'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(4) // 'x   '

(6)padEnd() :第一个参数是字符串补全生效的最大长度,第二个参数是用来从结束位置补全的字符串。

'x'.padEnd(5, 'ab') // 'xabab'
'12'.padStart(10, '0') // "0000000012"

注意:省略第二个参数,则默认用空格补全。padEnd方法常用来数字前补零

ES2019对字符串实例新增了trimStart()trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

(7)trimStart()trimEnd():消除头部/尾部空格

const str = '  abc  ';

consst r1 = str.trim() // "abc"
consst r2 = str.trimStart() // "abc  "
consst r3 = str.trimEnd() // "  abc"

注意:除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。浏览器还部署了额外的两个方法,trimLeft()trimStart()的别名,trimRight()trimEnd()的别名。

3-3. [扩展]正则中的粘连标记 几乎用不到

标记名:y

含义:匹配时,完全按照正则对象中的lastIndex位置开始匹配,并且匹配的位置必须在lastIndex位置。

const text = "Hello World!!!"
const reg = /w\w+/y;//以w开头的单词
reg.lastIndex = 6;
//从lastIndex的位置必须匹配,一开始lastIndex为0
console.log(reg.test(text))

3、模板字符串

模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

const text = 
`我很帅
我真的很帅 `;
console.log(text);

如果要在字符串中拼接js表达式,只需要在模板字符串中使用${JS表达式}

const age = 18
console.log(`我今年${age}岁`)
console.log(`计算结果:${1+2+3*2}`)

4、模板字符串标记

在模板字符串书写之前,可以加上标记:

标记名`模板字符串`

标记是一个函数,函数参数如下:

  1. 参数1:被插值分割的字符串数组
  2. 后续参数:所有的插值
const name = "llx"
const age = 18
// 相当于 text = myTag(["我的名字叫:",",我今年","岁"],"llx",18)
var text = myTag`我的名字叫:${name},我今年${age}岁`
function myTag(parts,arg1,arg2){
    console.log(parts)
    console.log(arg1)
    console.log(arg2)
}
console.log(text)//(3) ["我的名字叫:", ",我今年", "岁", raw: Array(3)]
const name = "llx"
const age = 18
var text = myTag`我的名字叫:${name},我今年${age}岁`
// 相当于
// text = myTag(["我的名字叫:",",我今年","岁"],"llx",18)
function myTag(parts){
    console.log(parts)
    //先将arguments转化为真实数组,再除去第一个参数,parts
    const values = Array.prototype.slice.apply(arguments).splice(1)
    console.log(values)
    let str = ""
    for(let i=0;i<parts.length;i++){
        if(parts.length-1 == i){
            str += parts[i]
        }else{
            str += parts[i]+values[i]
        }
    }
    return str
}
console.log(text)

String.raw将转义字符原样输出

const text = String.raw`abc\ndef`
console.log(text)

四、函数的扩展

1、 参数默认值

在书写形参时,直接给形参赋值,附的值即为默认值。这样一来,当调用函数时,如果没有给对应的参数赋值(给它的值是undefined),则会自动使用默认值。

//c的默认值是2
function sum(a = 0,b = 0,c = 2){
    return a + b + c;
}
console.log(sum(1,undefined,3))

注意:参数变量是默认声明的,所以不能用letconst再次声明。

对arguments的影响

只要给函数加上参数默认值,该函数会自动变量严格模式下的规则:arguments和形参脱离

比较二者区别

function test(a,b){
    console.log("arguments:",arguments[0],arguments[1])
    console.log("a:",a," ,b:",b)
    a = 3
    console.log("arguments:",arguments[0],arguments[1])
    console.log("a:",a," ,b:",b)
}
test(1,2)
function test(a,b=1){
    console.log("arguments:",arguments[0],arguments[1])
    console.log("a:",a," ,b:",b)
    a = 3
    console.log("arguments:",arguments[0],arguments[1])
    console.log("a:",a," ,b:",b)
}
test(1,2)

暂时性死区

形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区。

function test(a,b = a){
    console.log(a,b)
}
test(1,2)
test(undefined,1)

function test1(a = b,b){
    console.log(a,b)
}
test1(1,2)
test1(undefined,1)//报错 Cannot access 'b' before initialization

2、rest参数

ES6的rest参数用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数会将多余的参数放入数组中。形如:...形参名

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

function test(a,...args){
    console.log(a,args)//1  [2, 3, 4, 5]
}
test(1,2,3,4,5)
function sum(...args){
   let result = 0;
   for(let i=0;i<args.length;i++){
    result += args[i];
   }
   return result
}
console.log(sum(1,2,3,4,5))

注意:一个函数,仅能出现一个剩余参数。一个函数,如果有剩余参数,剩余参数必须是最后一个参数

补充:从 ES5 开始,函数内部可以设定为严格模式。ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

3、展开运算符

使用方式:...要展开的东西

(1)对数组展开 ES6
//克隆数组arr1到arr2
const arr1 = [1,2,3,4,5]
const arr2 = [...arr1]

例子:

function sum(...args){
   let result = 0;
   for(let i=0;i<args.length;i++){
    result += args[i];
   }
   return result
}
function getRandomNumbers(length){
    let arr = [] 
    for(let i=0;i<length;i++){
        arr.push(Math.random())
    }
    return arr
}
const numbers = getRandomNumbers(10)
//展开的内容是numbers数组中的,相当于传递了10个参数
const result = sum(...numbers)
console.log(result)
(2)对对象展开 (ES2017)
const obj1 = {
    name:"lkx",
    age:18,
    address:{
    	country:"山东"
    }
}
//浅克隆
const obj2 = {
    ...obj1,
    age:10
}
//克隆深度对象
const obj3 = {
    ...obj,
    age:10,
    address:{
    	...obj1.address
    }
}

柯里化:用户固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接收剩余的参数

function cal(a,b,c,d){
    return a + b *c -d
}
// 柯里化函数
function curry(func,...args){
    return function(...subArgs){
        const allArgs = [...args,...subArgs]
        if(allArgs.length>=func.length){
            //参数够了
            return func(...allArgs)
        }else{
            //参数不够,继续固定
            return curry(func,...allArgs)
        }
    }
}
const newCal = curry(cal,1,2)
console.log(newCal(2,4))

4、 明确函数的双重用途

ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用

new.target
//该表达式,得到的是:如果没有使用new来调用函数,则返回undefined
//如果使用new调用函数,则得到的是new关键字后面的函数本身
function Person(firstName,lastName){
    //判断是否是使用new的方式来调用的函数
    //过去的判断方式,判断this指向,但是用call和apply可能绕开
    if(this instanceof Person){
        throw new Error("该函数没有使用new创建")
    }
    //es6提供的判断方式
    if(new.target === undefined){
        throw new Error("该函数没有使用new创建")
    }
    this.firstName = firstName
    this.lastName = lastName
    this.fullName = firstName + lastName
}
const p1 = new Person("张","飞")
console.log(p1)
const p2 = Person("张","飞")
console.log(p2)

5、箭头函数

ES6 允许使用“箭头”(=>)定义函数。形如:(参数1, 参数2, ...)=>{ //函数体 }

var func = value => value;

// 等同于
var func = function (value) {
  return value;
};

箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数

const printNums = (num1,num2,num3) =>{
    const result = num1 + num2 * num3
    console.log(result)
}

如果参数只有一个,可以省略小括号

const print = num =>{
    num += 1
    console.log(num)
}

如果箭头函数只有一条返回语句,可以省略大括号,和return关键字

//返回true和false
const isOdd = num => num%2 !== 0

注意:由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错
let getObject = id => { id: id, name: "ll" };

// 不报错
let getObject = id => ({ id: id, name: "ll" });

关于this指向

在以往的学习中,对于this指向

1. 通过对象调用函数,this指向对象
2. 直接调用函数,this指向全局对象
3. 如果通过new调用函数,this指向新创建的对象
4. 如果通过apply、call、bind调用函数,this指向指定的数据
5. 如果是DOM事件函数,this指向事件源
//想要在定时器,事件中使用this,需要提前保存起来
const obj = {
    count : 0,
    start:function(){
        let _this = this
        setInterval(function(){
            _this.count++
            console.log(_this.count)
        }, 1000);
    },
    regEvent:function(){
        let _this = this
        window.onclick = function(){
            console.log(_this.count)
        }
    }
}
obj.start()
obj.regEvent()

而箭头函数中的this指向,他是固定的。函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

箭头函数有几个使用注意点。

(1)函数体内的`this`对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用`new`命令,否则会抛出一个错误。
(3)不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。`this`对象的指向是可变的,但是在箭头函数中,它是固定的。

箭头函数中,不存在this、arguments、new.target,如果使用了,则使用的是函数外层的对应的this、arguments、new.target

//箭头函数中的this取决于声明箭头函数的位置
const obj = { 
    count: 0,
    start:function(){
        // let _this = this
        setInterval(() => {
            this.count++
            console.log(this.count)
        }, 1000);
    },
    regEvent:function(){
        window.onclick = ()=>{
            console.log(this.count)
        }
    }
}
obj.start()
obj.regEvent()
const func = ()=>{
    console.log(this)//window
    console.log(arguments)//Uncaught ReferenceError
}
const obj = {
    method:func
}
obj.method(234)
const obj = {
method:function(){
    const func = ()=>{
        console.log(this)//obj
        console.log(arguments)//method的arguments
    }
    func()
}
}
obj.method(234)
应用场景
  1. 临时性使用的函数,并不会可以调用它,比如:
  1. 事件处理函数
  2. 异步处理函数,settimeout,setinterval
  3. 其他临时性的函数
  1. 为了绑定外层this的函数
  2. 在不影响其他代码的情况下,保持代码的简洁,最常见的,数组方法中的回调函数
const numbers = [3,5,6,32,78,24]
const result = numbers.filter(num=>num%2!==0).map(num=>num*2).reduce((a,b)=>a+b)
console.log(result)

6、Function.prototype.toString()

ES2019 对函数实例的**toString()**方法做出了修改。

toString()方法返回函数代码本身,以前会省略注释和空格。

7、catch 命令的参数省略

JavaScript 语言的try...catch结构,以前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。ES2019try...catch,允许catch语句省略参数。

try {
  // ...
} catch (err) {
  // 处理错误
}
//---Es2019以后
try {
  // ...
} catch {
  // ...
}

五、对象的扩展

1、新增的对象字面量语法

(1)成员速写

如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写

const name = "张飞"
const age = 18
const obj = {
   name,
   age
}

(2)方法速写

对象字面初始化时,方法可以省略冒号和function关键字

const obj = {
    sayHellow(){
        console.log("hello world")
    }
}

(3)计算属性名

有的时候,初始化对象时,某些属性名可能来自于某个表达式的值,在ES6,可以使用中括号来表示该属性名是通过计算得到的。

const key = "name"
const name = "lkx"
const obj = {
    [key]:name
}

(4)对象属性的遍历

ES6 一共有 5 种方法可以遍历对象的属性。(在Es6后续中介绍)

(1)for…in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性210,其次是字符串属性ba,最后是 Symbol 属性。

2、链判断运算符和Null 判断运算符

(1)链判断运算符

链判断运算符有三种用法:

1、obj?.prop // 对象属性
2、obj?.[expr] // 同上
3、func?.(...args) // 函数或对象方法的调用

在项目中经常会读取某个对象中的属性。例如:result.message.user.firstName

我们通常的写法为。

const firstName = (result && result.message && result.message.user&& result.message.user.firstName) || '暂无数据';

这样的层层判断非常麻烦,因此ES2020引入了链判断运算符?.,简化上面的写法。

const firstName = result?.message?.user?.firstName || '暂无数据';

使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为nullundefined。如果是的,就不再往下运算,而是返回undefined

(2)Null判断运算符

我们在读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。我们经常用||运算符指定默认值。

const firstName = result.firstName || '暂无数据';

但是属性的值如果为空字符串或false0,默认值也会生效。为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

const firstName = result.firstName ?? '暂无数据';

该运算符一般配合.?使用

const firstName = result?.message?.user?.firstName ?? '暂无数据';

3、Object的新增API

(1)Object.is()

用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点:

  1. NaN和NaN相等
  2. +0和-0不相等
console.log(NaN === NaN) //false
console.log(+0 === -0) //true

console.log(Object.is(NaN,NaN))  //true
console.log(Object.is(+0,-0))  //false
(2)Object.assign(target,source1,source2…)

方法的第一个参数是目标对象,后面的参数都是源对象。将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const obj1 = {
    name:"l"
}
const obj2 = {
    name:"lkx",
    age:18
}
const obj = Object.assign(obj1,obj2)
console.log(obj)
console.log(obj1 === obj) //true

注意:用于混合对象,后面的覆盖前面的,会改变obj1

//通常我们会把第一个参数写为一个空对象
const lastObj = Object.assign({},obj1,obj2)

再注意:方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

(3)Object.getOwnPropertyNames

Object.getOwnPropertyNames方法之前就存在,只不过,官方没有明确要求,对属性的顺序如何排序,如何排序,完全由浏览器厂商决定。

ES6规定了该方法返回的数组的排序方式如下:

  • 先排数字,并按照升序排序
  • 再排其他,按照书写顺序排序
const obj = {
    a:"a",
    b:"b",
    c:"c",
    0:0,
    1:1
}
console.log(Object.getOwnPropertyNames(obj))
//["0", "1", "a", "b", "c"]
(4)Object.setPrototypeOf()和Object.getPrototypeOf()

setPrototypeOf() 该函数用于设置某个对象的隐式原型__proto__

比如: Object.setPrototypeOf(obj1, obj2),
相当于: obj1.__proto__ = obj2

const obj1 = {
   a:1
}
const obj2 = {
   b:2
}
Object.setPrototypeOf(obj1,obj2)
//将obj1的隐式原型改为obj2

Object.getPrototypeOf():该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

(5)Object.keys(),Object.values(),Object.entries() ,Object.fromEntries()

Object.keys():返回一个数组,返回对象的所有属性的键名。(ES5 )

var obj = { a: 1, b: 2 };
Object.keys(obj) // ["a", "b"]

Object.values():方法返回一个数组,返回对象的所有属性的键值。(ES2017)

var obj = { a: 1, b: 2 };
Object.values(obj) // ["1", "2"]

Object.entries():方法返回一个数组,返回对象的所有属性键值对数组。(ES2017)

var obj = { a: 1, b: 2 };
Object.entries(obj) // [["a","1"], ["b","2"]]

Object.fromEntries() :是Object.entries()的逆操作,用于将一个键值对数组转为对象。

var arr = [["a","1"], ["b","2"]]
Object.fromEntries(arr)//{ a: 1, b: 2 }

六、变量的解构赋值

解构:使用ES6的一种语法规则,将一个对象或数组的某个属性提取到某个变量中。解构不会对被解构的目标造成任何影响。

1、对象解构

const user = {
    name:"lkx",
    age:18,
    address:{
        provice:"山东",
        city:"济南"
    }
}
const {name,age,sex,address} = user
console.log(name,age,address) //lkx 18 {provice: "山东", city: "济南"}
在解构中使用默认值
//{同名变量 = 默认值}
const {name,age,address,sex="男"} = user
console.log(name,age,address,sex)//lkx 18 {provice: "山东", city: "济南"} 男

2、非同名属性解构

{属性名:变量名}
const {name,age:nianling=20,address,sex="男"} = user
console.log(name,nianling,address,sex)

3、数组解构

const numbers = ["a","b","c","d"]
const [n0,n1] = numbers
console.log(n0,n1)

const {2:n2,3:n3} = numbers
console.log(n2,n3)

4、参数解构

function print({ name, age, sex, address: {province,city} }) {
    console.log(`姓名:${name}`)
    console.log(`年龄:${age}`)
    console.log(`性别:${sex}`)
    console.log(`省份:${province}`)
    console.log(`城市:${city}`)
}
const user = {
    name: "lkx",
    age: 18,
    sex: "男",
    address: {
        province: "山东",
        city: "济南"
    }
}
print(user)