ES6常用特性汇总

254 阅读9分钟

ES6.png

注:!!!

Promise的总结在另一篇文章:Promise入门到自定义

1.let关键字

1.作用: 与var类似, 用于声明一个变量

2.特点:

  • 在块作用域内有效

  • 不能重复声明

  • 不存在变量提升, 即不能再初始化前使用变量。一定声明后再使用。

  • 全局声明的变量不会挂载到window上,会放在script变量对象(暂存死区)上。

  • 不影响作用域链

3.应用:

  • 循环遍历添加监听

实例:

//对比var
console.log(a); // undefined
var a = 123;
var a = 234;
console.log(a);

//不能重复声明
let star = '罗志祥';
let star = '小猪';

//不存在变量提升
// console.log(b);  //b is not defined
console.log(ausername);  //Cannot access 'ausername' before initialization
let ausername = 'wade';

//在块作用域内有效
//if else while for 
{
    let girl = '周扬青';
}
console.log(girl);


//应用-在块作用域内有效-循环遍历添加监听
//let解决来点击某个按钮, 提示"点击的是第n个按钮"时, 始终i=3的问题
let btns = document.getElementsByTagName('button');
for (let i = 0; i < btns.length; i++) {
    var btn = btns[i];
    btn.onclick = function () {
        alert(i);
    }
}

2.const关键字

1.作用: 定义一个常量

2.特点:

  • 不能修改

  • 声明时必须赋值

  • 标识符一般为大写(潜规则)

  • 在块作用域内有效

  • 不能重复声明

  • 不存在变量提升

3.应用: 一般声明对象类型使用 const,非对象类型声明选择 let

4.注意: 对象属性修改和数组元素变化不会触发 const 错误,因为对象名指向的地址没变!

实例:

实例:
//声明常量
const SCHOOL = '尚硅谷';
//一定要赋初始值
const A; // 报错
//一般常量使用大写(潜规则)
const A = 100;
//常量的值不能修改
SCHOOL = 'ATGUIGU';
console.log(SCHOOL);
//对于数组和对象的元素修改, 不算做对常量的修改, 不会报错,对象名指向的地址没变!
const TEAM = ['UZI','MXLG','Ming','Letme'];
TEAM.push('Meiko');

3.变量的解构赋值(重点!)

1.理解:

  • 从对象或数组中提取数据, 并赋值给变量(多个)

2.对象的解构赋值

let {n, a} = {n:'tom', a:12};

  • 根据key索取value(按需索取)

3.数组的解构赋值

let [a,b] = [1, 'atguigu'];

  • 根据index索取value

4.应用

  • 传入的参数为一个对象,利用函数的形参解构实参

实例:

//对象解构赋值
let obj2 = {username:'kobe', age: 43};
let {username, age} = obj2;  // 解构赋值真正实现的是: 根据key索取value(按需索取)
console.log(username, age);

//数组解构赋值
let arr = [4,5,3];
let num1 = arr[2];
let num2 = arr[1];
console.log(num1, num2);

let [a,b] = arr;  // 根据index索取value
let [,,c] = arr;  // 用,占位取后面的值
console.log(a,b); //4  5
console.log(c);  //3


//应用-利用函数的形参解构实参
function fun({username, age}) {
    // let 形参变量 = 实参;
    // let {username, age} = {username:'kobe', age: 43};
    console.log(username);
    console.log(age);
}
fun(obj2);

4.模板字符串

1.理解: 简化字符串的拼接

  • 模板字符串必须用 `` 包含

  • 变量的部分使用${xxx}定义

实例:

let obj = {username: 'kobe', age: 43};
let str = '我的名字是: ' + obj.username + ', 我的年龄是: ' + obj.age;
console.log(str);
let str1 = `我的名字是:${obj.username}, 我的年龄是:${obj.age}`;
console.log(str1);

5.对象的简化写法

1.省略同名的属性值

2.省略函数的function和冒号:

实例:

//ES5
let obj = {
    username: username,
    age: age,
    showName: function () {
        console.log(this.username);
    }
};
console.log(obj);
obj.showName();

//ES6
let obj = {
    username,  // 同名的属性可省略 ---> key 和 value一样的时候
    age,
    showName() {  // 省略函数的function和冒号
        console.log(this.username);
    }
};
console.log(obj);
obj.showName();

6.箭头函数(重点!)

1.语法: let fun = 形参 => 函数体

2.形参:

  • 没有参数: () => 函数体

  • 一个参数-括号可省略: i => 函数体

  • 大于一个参数: (i,j) => 函数体

3.重点: 函数体

  • 只有一条语句或表达式的时候: {}可以省略

  • 当有多条语句的时候: {}不能省略

  • 当{}省略的时候会自动return当前语句或表达式的执行结果

  • 只有一条语句且return对象时,省略括号{}会导致问题,因此需要在外面写小括号包裹对象:

    const fn = b => ({a:'22', c: b})

4.特点

  • 箭头函数没有自己的this,不是调用的时候决定的,而是定义的时候决定

  • 扩展理解:

    • 箭头函数定义的时候看外部是否有非箭头函数,如果有,当前箭头函数的this指向与外部函数的this是同一个对象

    • 如果外部没有,this指向的是window

  • 箭头函数不能用作构造函数

  • 不能使用 arguments

实例:

//当箭头函数没有形参的时候, () 不能省略
let fun = () => console.log('fun()');
fun();

//当箭头函数只有一个形参的时候,() 可以省略,也可以不省略
let fun1 = a => console.log('只有一个形参的时候', a);
fun1(123);

//当箭头函数有多个形参的时候,()不能省略
let fun2 = (a, b) => console.log('有多个形参的时候', a, b);
fun2(1,2);

// 当箭头函数的函数体只有一条语句的时候{}可以省略, 当{}省略的时候会自动 return 当前语句或者表达式的结果, 当{}不省略的时候,需要手动指定返回的结果, 否则默认执行 return value: undefined;
let fun3 = () => console.log('函数体只有一条语句的时候'); // 语句的返回结果
console.log(fun3());
// let fun3 = () => 123 // 语句的返回结果
// console.log(fun3());

//当箭头函数的函数体有多条语句的时候,{}不能省略, 当{}不省略的时候,需要手动指定返回的结果, 否则默认执行 return value: undefined;
let fun4 = () => {
    let a = 1;
    console.log('函数体有多条语句的时候', a);
    // return a;
}
console.log(fun4());

//箭头函数的this指向问题 
btn1.onclick = function () {
    console.log(this); // btn1对象
};

btn2.onclick = () => console.log(this); // window

let obj = {
    username: 'kobe',
    test: function () { // this ---> obj
        btn2.onclick = () => console.log(this); // obj
    }
}

let obj = {
    username: 'kobe',
    test:  () => {
        btn2.onclick = () => console.log(this); // window
    }
};
obj.test();

//箭头函数不能用作构造函数
let Person = () => this.name = 'kobe';
let person1 = new Person();

//不能使用 arguments
let fn = () => {
    console.log(arguments);
}
fn(1,2,3);



//案例
//需求1: 点击 div 2s 后颜色变成『粉色』
//获取元素
let ad = document.getElementById('ad');
//绑定事件
ad.addEventListener("click", function(){
    // let _this = this;  //缓存this
    //定时器
    // setTimeout(function () {
    //     //修改背景颜色 this
    //     console.log(this);
    //     _this.style.background = 'pink';
    // }, 2000);
    
    setTimeout(() => {
        this.style.background = 'pink';
    }, 2000);
});


//需求2: 从数组中返回偶数的元素
const arr = [1,6,9,10,100,25];
// const result = arr.filter(function(item){
//     if(item % 2 === 0){
//         return true;
//     }else{
//         return false;
//     }
// });

const result = arr.filter(item => item % 2 === 0);
console.log(result);

总结:

  • 箭头函数适合与 this 无关的回调。如:定时器, 数组的方法回调!

  • 箭头函数不适合与 this 有关的回调。如:事件回调, 对象的方法!

7.三点运算符 - rest(可变)参数 / 扩展运算符(拆包)

1.语法: ...变量名

2.应用:

​ 1) rest(可变)参数

  • 用于获取函数的实参

  • 用来取代 arguments, 且比 arguments 更灵活

  • 使用三点运算符收集实参的时候是一个真数组

  • 必须要放到参数最后

​ 2) 扩展运算符(拆包)

  • 作用: 获取数组里的每一项

  • 原理: 三点运算符会调用iterator接口,如果底层有这个接口,那就可以去遍历数组

  • 应用: 将伪数组转换为真数组

实例:

//rest(可变)参数
function fun(a,b, ...values) {
    // arguments用来收集实参的伪数组
    // 伪数组: 有length属性,可以通过下标获取对应的值,但是没有数组的一般方法(push, filter, map)
    console.log(arguments);
    console.log(a);
    console.log(values);
    values.forEach(function (item, index) {
        console.log(item, index);
    });
}
fun(2, 3, 4, 5);


//扩展运算符(拆包)
let arr2 = [2, 3, 4, 5];
let arr3 = [1, ...arr2, 6];
console.log(arr3);  // [1, 2, 3, 4, 5, 6]
// ...运算符去拆包指定的数组
console.log(...arr2);
// 数组克隆 - 深拷贝
console.log([...arr2]);

// ...运算符去拆包不能直接去遍历对象
let obj2 = {name: 'kobe', age: 43};
// console.log(...obj2);  //报错

// ...运算符合并对象/复制对象
let obj3 = {sex: '男', ...obj2};
//构造字面量对象时可以使用...运算符
let obj4 = {...obj2};
console.log(obj3);
console.log(obj4);

//合并的同时改值或加值
let person = {name:'tom', age:18}
let person3 = {...person, name:'jack', address:"地球"}
console.log(person3);

// 将伪数组转为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);

8.形参默认值

1.形参初始值

  • 具有默认值的参数, 一般位置要靠后(潜规则)

2.应用

  • 与解构赋值结合!

实例:

function Point(x = 0, y = 0) {
    //原生实现
    // if(!x && x !== 0){
    //   x = 0;
    // }
    // if(!y && y !== 0){
    //   y = 0;
    // }
    
    this.x = x;
    this.y = y;
}
let point = new Point(22, 45);
console.log(point);

let point2 = new Point();
console.log(point2);


// 形参默认值的类型一般根据需要的实参类型决定
function fun(arr = []) {
    arr.forEach(function (item, index) {
        console.log(item, index);
    })
}
let arr = [1,2,3];
fun(arr);
fun();


// 应用-与解构赋值结合!!!
function connect({host="127.0.0.1", username, password, port}){
    console.log(host)
    console.log(username)
    console.log(password)
    console.log(port)
}
connect({
    host: 'atguigu.com',
    username: 'root',
    password: 'root',
    port: 3306
})
connect({
    username: 'root',
    password: 'root',
    port: 3306
})

9.Symbol

1.理解: ES6提供的一种新的原始(基本)数据类型symbol

回顾:

  • 基本数据类型: String, Number, Boolean, null, undefined, Symbol

  • typeof返回值: string, number, boolean, object, undefined, symbol, function

2.特点

  • Symbol属性对应的值是唯一的,解决命名冲突问题

  • Symbol值不能与其他数据进行计算,包括同字符串拼串

  • for in, for of遍历时不会遍历symbol属性

3.内置Symbol值

  • Symbol.iterator

    • 对象的Symbol.iterator属性,指向该对象的默认遍历器(迭代器)方法

实例:

//使用symbol
let symbol = Symbol();
let symbol2 = Symbol();
console.log(symbol);
console.log(symbol2);
//验证值是唯一的
console.log(symbol === symbol2);
console.log(typeof symbol);

//Symbol函数传参
let symbol3 = Symbol('one');
let symbol4 = Symbol('two');
console.log(symbol3);
console.log(symbol4);
//验证值是唯一的
console.log(symbol3 === symbol4);

//使用symbol当做对象的属性名
let symbol5 = Symbol();
let obj = {};
obj[symbol5] = 'hello';
console.log(obj);

//内置Symbol值: Symbol.iterator
console.log(Symbol.iterator);

10.for...of 与 iterator接口底层实现(难点!)

1.for(let value of target){}循环遍历

1)遍历数组

2)遍历Set

3)遍历Map

4)遍历字符串

5)遍历伪数组

2.iterator接口

  1. 概念
  • iterator 是一种接口机制,为各种不同的数据结构提供统一的访问机制。
  1. 作用
  • 为各种数据结构,提供一个统一的、简便的访问接口。

  • 使得数据结构的成员能够按某种次序排列。

  • ES6创造了一种新的遍历命令 for...of循环,iterator接口主要供for...of调用。

  1. 工作原理
  • 创建一个指针对象(遍历器对象/迭代器对象),指向数据结构的起始位置。

  • 第一次调用next方法,指针自动指向数据结构的第一个成员

  • 接下来不断调用next方法,指针会一直往后移动,直到指向最后一个成员

  • 每调用next方法返回的是一个包含value和done的对象,{value: 当前成员的值,done: 布尔值}

    • value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束。

    • 当遍历结束的时候返回的value值是undefined,done值为true

  1. 原生具备iterator接口的数据结构(可用for...of遍历)

​ 1)Array

​ 2)arguments

​ 3)set容器

​ 4)map容器

​ 5)String ​ ...

注: ...运算符也是调用 iterator 接口来实现遍历!

实例:

let arr = [1,2,3,4,5,1,2,3,4];
let set = new Set(arr);
//遍历set
for(let item of set){
    console.log(item);
}
console.log(...set);

//遍历字符串
let str = 'abcd'
for(let item of str){
    console.log(item);
}

//遍历伪数组
let btns = document.getElementsByTagName('button');
for(let item of btns){
    console.log(item);
}

//验证for...of与...运算符调用iterator接口
let arr = [2,3,4,5];
// Array.prototype[Symbol.iterator] = 1111;
console.log(arr);
for(let item of arr){
    console.log(item);
}
console.log(...arr);


// iterator接口(方法 / api)底层的简单实现 - 手动调用next()移动指针
function iteratorUtil(target) {
    let index = 0; //  标识指针的起始位置
    return { // 生成iterator遍历器对象
        next: function () {
            return index < target.length?{value: target[index++], done: false}:{value: target[index++], done: true}
        }
    }
}
let iteratorObj = iteratorUtil(arr);  // 生成iterator遍历器对象
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());


// iterator接口(方法 / api)底层的完整实现 - 可遍历数组/字符串/对象
function iteratorUtil() {
    //console.log('我的方法被调用了', this);  // this是遍历的目标数组/字符串/对象
    // console.log(target);  // undefined, 因此target传参可去除
    let that = this;  // 缓存this
    let index = 0;  // 标识指针的起始位置
    let keys = Object.keys(that);  // 获取对象中所有key的数组
    if(this instanceof Array || this instanceof String){  // 遍历数组/字符串
        return {  // 生成iterator遍历器对象
            next:  function (){  // 可以使用箭头函数解决this的指向问题
                return index < that.length?{value: that[index++], done: false}:{value: that[index++], done: true};
            }
        }
    }else {  // 遍历对象
        return {  // 生成iterator遍历器对象
            next:  function (){  // 可以使用箭头函数解决this的指向问题
                return index < keys.length?{value: that[keys[index++]], done: false}:{value: that[keys[index++]], done: true};
            }
        }
    }
}
//使用自己iterator接口去实现for...of/...运算符的遍历
Array.prototype[Symbol.iterator] = iteratorUtil;
Object.prototype[Symbol.iterator] = iteratorUtil;

//遍历数组
let arr = [2,3,4,5];
// for of 调用 iterator接口
// 三点运算符 调用 iterator接口
for(let item of arr){
    console.log(item);
}
console.log(...arr);

//遍历对象
let obj = {
    username: 'kobe',
    age: 43
};
// console.log(Object.keys(obj));  // 获取对象中所有key的数组
for(let o of obj){
    console.log(o);
}

//遍历字符串
let str = "abcdef";
for(let s of str){
    console.log(s);
}
console.log(...str);

11.Class类(重点!)

1.通过 class 定义类

2.在类中通过 constructor 定义构造方法

3.通过 new 来创建类的实例

4.通过 extends 来实现类的继承 - 相当于ES5的原型继承

5.通过 super 调用父类的构造方法 - 相当于ES5的构造函数继承

6.使用 static (静态资源修饰符)可以给类对象自身添加属性

7.重写从父类中继承的一般方法 - 参数与方法名必须相同

注:

  1. extends做的事情: 1.子类的原型 成为 父类的实例 2.添加constructor属性,并指向子类本身

  2. super做的事情: 1.调用父类的构造方法 2.改变父类构造方法的this指向为子类的实例

  3. super必须写在子类constructor里的最前面(所有this.操作的前面)

  4. 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。

  5. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。

  6. 类中所定义的方法,都放在了类的原型对象上,供实例去使用。

  7. 类中的方法默认开启了严格模式

实例:

// 定义一个人类: Person
class Person {
    // 静态资源修饰符,使用static可以给 类对象 自身添加属性
    static num = 123;
    //类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为wheel,值为4,
    //等于在构造函数里赋值: this.wheel = 4;
    wheel = 4;

    // 类的构造方法
    constructor(name, age){
        //构造器中的this是谁?—— 类的实例对象
        console.log('--- constructor() ---');
        this.name = name;
        this.age = age;
        //this.wheel = 4;
    }
    // 类的一般方法
    //showInfo方法放在了哪里? —— 类的原型对象上,供实例使用
    //通过Person实例调用showInfo时,showInfo中的this就是Person实例
    showInfo(){ // userInfo
        console.log(this.name, this.age);
    }
}

//Person类自身添加属性
Person.msg = 'Person自身的属性';
console.log(Person.msg);
console.log(Person.num);

//使用类: 同构造函数使用方式一样
let person1 = new Person('kobe', 43);
console.log(person1);
person1.showInfo();

// 定义一个 子类
class Child extends Person {  // extends做的事情: 1.子类的原型 成为 父类的实例 2.添加constructor属性,并指向子类本身
    constructor(name, age, sex) {
        // super做的事情: 1.调用父类的构造方法 2.改变父类构造方法的this指向为子类的实例
        super(name, age);  // super调用父类的构造方法
        this.sex = sex;
    }
    // 父类的方法重写:当父类原型的方法不能满足子类实例需求的时候
    //showInfo方法放在了哪里? —— 类的原型对象上,供实例使用
    //通过Child实例调用showInfo时,showInfo中的this就是Child实例
    showInfo(){
        console.log(this.name, this.age, this.sex);
    }
}

let child1 = new Child('xiaoming', 18, '男');
console.log(child1);
child1.showInfo();

12.Number的方法扩展

1.Number.isFinite() : 判断是否是有限大的数

2.Number.isNaN() : 判断是否是NaN

3.Number.isInteger() : 判断是否是整数

4.Number.parseInt() : 将字符串转换为对应的数值

注: window.parseInt 与 Number.parseInt 功能一样

实例:

console.log(Number.isFinite(123)); // true
console.log(Number.isFinite(NaN)); // false
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN)); //true
console.log(Number.isInteger(22)); //true
console.log(Number.parseInt('123abc')); //123

// 笔试题
let str1 = '23.23';
let str2 = '34.34';
console.log(parseInt(str1) + parseInt(str2)); // 57
console.log(parseInt(str1 + str2)); // 23

13.对象方法扩展

1.Object.is(v1, v2)

  • 判断2个数据是否完全相等(一模一样)

2.Object.assign(target, source1, source2..) (重点!)

  • 将一个或多个源对象的属性复制到目标对象上,目标对象相同属性名的值会被覆盖。此方法会修改目标对象。

3.直接操作 __ proto __ 属性(ES6)

let obj2 = {};
obj2.__proto__ = obj1;

实例:

//特殊值比较
console.log(0 === -0); // true
console.log(NaN === NaN); // false

//Object.is(v1, v2)  看2个值是否完全相等(一模一样)
console.log(Object.is(0, -0)); // false
console.log(Object.is(NaN, NaN)); // true

//Object.assign(target, source1, source2..)
let target = {}; // target
let sources1  = {name: 'kobe'};
let sources2 = {age: 43};
// 克隆/拷贝
let result = Object.assign(target, sources1, sources2);
console.log(result);
console.log(target);

//直接操作 __proto__ 属性
let obj3 = {};
obj3.__proto__ = target;
console.log(obj3);

14.Set 和 Map

1.Set

  1. ES6提供了新的数据结构 Set(集合),无序不可重复的多个值的集合体。

  2. Set的属性和方法:

​ 1- Set() / Set(array) 构造函数

​ 2- size 返回集合的元素个数

​ 3- add() 增加一个新元素,返回当前集合

​ 4- delete() 删除元素,返回 boolean 值

​ 5- has() 检测集合中是否包含某个元素,返回 boolean 值

​ 6- clear() 清空集合,返回 undefined

  1. Set实现了iterator 接口,所以可以使用 扩展运算符... 和 for...of 进行遍历

实例:

//声明 set
let s = new Set();
console.log(s, typeof s);
//传入数组声明 set
let s2 = new Set(['大事儿','小事儿','好事儿','坏事儿','小事儿']);
//元素个数
console.log(s2.size);
//添加新的元素
s2.add('喜事儿');
//删除元素
s2.delete('坏事儿');
//检测
console.log(s2.has('糟心事'));
//清空
s2.clear();
console.log(s2);

// ES6中for...of 来替代 for...in 和 forEach()
for(let v of s2){
    console.log(v);
}

2.Map

  1. ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。

​ 但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

  1. Map也实现了iterator 接口,所以可以使用 扩展运算符... 和 for...of 进行遍历

  2. Map的属性和方法:

​ 1) Map() / Map(二维数组) 构造函数

​ 2) size 返回 Map 的元素个数

​ 3) set(key, value) 增加一个新元素,返回当前 Map

​ 4) get() 返回键名对象的键值

​ 5) has() 检测 Map 中是否包含某个元素,返回 boolean 值

​ 6) clear() 清空集合,返回 undefined

实例:

// 声明 Map
let m = new Map();
// 传入二维数组声明 Map
let map = new Map([[1, 'abc'], ['age', 123]]);
console.log(map);
// 添加元素
m.set('name','尚硅谷');
m.set('change', function(){
    console.log("我们可以改变你!!");
});
let key = {
    school : 'ATGUIGU'
};
m.set(key, ['北京','上海','深圳']);
// size
console.log(m.size);
// 删除
m.delete('name');
// 获取
console.log(m.get('change'));
console.log(m.get(key));
// 清空
m.clear();

// 遍历
for(let v of m){
    console.log(v); // v是一个数组,第一个元素是key,第二个是value
}

15.深拷贝、浅拷贝以及底层实现(重难点!)

1、数据类型:

  • 数据分为基本的数据类型(String, Number, boolean, Null, Undefined)和对象数据类型

    • 基本数据类型

      特点: 存储的是该对象的实际数据

    • 对象数据类型

      特点: 存储的是该对象在堆中的引用,真实的数据存放在堆内存里

2、复制数据

  • 基本数据类型存放的就是实际的数据,可直接复制
let number2 = 2;
let number1 = number2;
  • 克隆(拷贝)数据: 对象/数组

​ 1- 区别: 浅拷贝/深度拷贝

​ 判断: 拷贝是否产生了新的数据,还是拷贝的是数据的引用

​ 知识点: 对象数据存放的是对象在堆内存的引用,直接复制的是对象的引用

let obj = {username: 'kobe'}
let obj1 = obj;  // obj1 复制了obj在堆内存的引用

​ 注: 判断深拷贝还是浅拷贝: 修改拷贝之后的数据(引用数据)会不会影响原数据!

​ 2- 常用的拷贝技术

​ 1). arr.concat(): 不传参, 数组浅拷贝

​ 2). arr.slice(): 传参0, 数组浅拷贝

​ 3). JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据

​ 4). Object.assign(target, source1, source2..): 浅拷贝

​ 5). 扩展运算符是浅拷贝

​ 注:

​ 1. 浅拷贝包含函数数据的对象/数组

​ 2. 深拷贝包含函数数据的对象/数组

实例:

1.实现检测数据类型的方法

2.实现深拷贝: 对象,数组 - 递归(重点!)

let obj = {username: 'kobe'};
console.log(obj.toString()); // [object Object]
let arr = [1,2,3];
// 数组中重写了Object原型的toString方法
console.log(arr.toString()); // 1,2,3

console.log(Object.prototype.toString.call(arr)); // [object Array]
// 可用于检测数据类型
console.log(Object.prototype.toString.call(null).slice(8, -1)); //截取类型
// 实现检测数据类型的方法(重点!)
function checkoutType(target) {
    return Object.prototype.toString.call(target).slice(8, -1);
}

// for...in
let obj = {username: 'kobe',  age: 43};
for(let item in obj){
    console.log(item);
}
// for in 遍历数组可以获取数组的下标
for(let item in arr){
    console.log(item);
}

// 深度拷贝引入
// obj保存的是对象的内存地址
let obj = {username: 'kobe',  age: 43};
let obj2 = obj;  // obj保存的内存地址给了obj2, 引用传递
obj2.username = 'wade'
console.log(obj.username);

// 基本数据类型保存的是值本身
let num = 123;
let num2 = num;  // 值传递,修改新的数据不会影响原数据
num2 = 234;
console.log(num);


//拷贝: 深拷贝/浅拷贝, 判断深拷贝还是浅拷贝: 修改拷贝之后的数据(引用数据)会不会影响原数据
let obj = {username: 'kobe',  age: 43};
let obj2 = {};
obj2.username = obj.username;
obj2.age = obj.age;
obj2.username = 'wade';
console.log(obj.username);

// arr.concat(): 数组浅拷贝
let arr = [1, 2, 3, {username: 'kobe'}];
let testArr = [4,5];
let arr2 = [];
// arr2 = arr.concat(arr2);
console.log(arr2);

arr2 = arr.concat();
console.log(arr2);
arr2[3].username = 'wade';
console.log(arr, arr2);

// arr.slice(): 数组浅拷贝
let arr3 = arr.slice(0);
arr3[3].username = 'duncan';
console.log(arr3, arr);


// JSON.parse(JSON.stringify(arr/obj)) 将json数据 和 原生的js对象/数组相互转换
//json数据: json对象,json数组
let obj = {username: 'kobe',  age: 43, sex: {option1: '男', option2: '女'}};  // 不能处理函数的拷贝
let obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2);
obj2.username = 'wade';
obj2.sex.option1  = '混合';
console.log(obj2, obj);


// Object.assign(target, source1, source2..)  浅拷贝
let obj = {username: 'kobe',  age: 43, sex: {option1: '男', option2: '女'}};
let obj2 = {};
Object.assign(obj2, obj);
obj2.username = 'wade';
obj2.sex.option1  = '混合';
console.log(obj2, obj);


// 扩展运算符是浅拷贝
//构造字面量对象时可以使用扩展运算符...
let person = {name:'tom', age:18, ss:{s:11}}
let person2 = {...person}
//console.log(...person); //报错,展开运算符不能展开对象
person2.name = 'jerry';
person2.ss.s = 22;
console.log(person2);
console.log(person);


//实现深拷贝: 对象,数组 (重点!!!)-使用了递归
function checkoutType(target) {
    return Object.prototype.toString.call(target).slice(8, -1);
}
function clone(target) {
    let result; // 最终加工拷贝完的数据
    //  检测数据类型 - 判断拷贝的数据是对象 || 数组 || 其他(基本数据类型,函数)
    let targetType = checkoutType(target);
    if(targetType === 'Array'){
        result = [];
    }else if(targetType === 'Object'){
        result = {};
    }else {
        return target;
    }
    // 拷贝
    // arr = [1,2,3] ====> []arr2
    // obj = {username: 'kobe'} ===> {}obj2
    for(let item in target){
        // item: 对象(key), 数组(index)
        // target[item] 都可以获取对应的value
        let value = target[item];
        // arr2[item] = arr[item]
        // 判断是否是引用数据类型
        if(checkoutType(value) === 'Object' || 'Array'){
            result[item] = clone(value);
        }else {
            result[item] = value;
        }
    }
    return result;
}

let obj = {username: 'kobe', age: 43, sex: ['男', '女']};
let obj2 = clone(obj);
obj.username = 'wade';
console.log(obj, obj2);
obj2.sex[0] = '混合';
console.log(obj, obj2);

16.ES7 - 指数运算符(幂)

1.指数运算符(幂): **

  • 等同于Math.pow()

实例:

console.log(3 ** 3); // 27

17.面试题

//1
var obj = {n: 1};
var obj2 = obj;
obj2.n = 2;
console.log(obj.n); //2

//2
function fn1(a) {
	a.n = 3;
}
fn1(obj);
console.log(obj.n); //3

//3
function fn2(a) {
    a = {n: 4}
}
fn2(obj);
console.log(obj.n); //3


//4
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.n, b.n); //2  1
console.log(a.x, b.x); //undefined  {n: 2}

注: 操作对象属性的优先级高于给对象本身赋值!
    a.x = a = {n: 2};
    //上面的分解动作
    a.x = {n: 2};
    a = {n: 2};


//5
var x = 10;
function fn() {
    console.log(x);
}
function show(f) {
    var x = 20;
    f();
}
show(fn); //10


//6
var fn = function () {
    console.log(fn);
}
fn(); //function


//7
var obj = {
    fn2: function () {
        console.log(this.fn2); //function
        console.log(fn2); //报错
}
obj.fn2();


//8
var a = 2;
function fn() {
    console.log(a);
    var a = 3;
}
fn(); //undefined

function fn2() {
    console.log(a)
    a = 3;
fn2() //2

    
//9
function b() {}
var b;
console.log(typeof b); //function

    
//10
var c = 1;
function c(c) {
    console.log(c);
    var c = 3;
}
console.log(c); //1
c(2); //报错

    
//11
var name = 'wor1d!'
;(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log(name); //Jack
    } else {
        console.log(name)
    }
})()

    
//12
var a = 6;
setTimeout(function () {
    console.log(0);
    alert(a);
    a = 666;
}, 0);
console.log(1);
a = 66;
// 1  0  66

    
//13
function fn1() {
	var a = 2;
    function fn2 () {
        a++;
        console.log(a);
    }
    return fn2;
}
var f = fn1();
f(); //3
f(); //4