js|es6中涉及的高频知识点🔥🔥

185 阅读6分钟

数组去重

学到2种比较牛X的数组去重方法

不使用新数组+sort

缺点:返回的数组是一个有序数组,这可能并不符合题意,有点不稳定排序的那个意思

const removeDuplicates = (nums) => {
	// 原地排序
	nums.sort()
	// 去重
	let len = 1
	for (let i = 1; i < nums.length; i++) {
		if (nums[i] != nums[i - 1]) {
			nums[len++] = nums[i]
		}
	}
	// 删除重复项
	nums.splice(len)
	return nums
}

// 测试
console.log(removeDuplicates([2, 1, 2, 3, 1, 2, 8, 1, 2, 3]))
// [ 1, 2, 3, 8 ]

// 期待:[ 2, 1, 3, 8 ]

不使用新数组+indexOf

思想:不重复的元素一直往前移动,删除后面那些重复的元素

缺点:返回的数组值的顺序和原数组不一样

const removeDuplicates = (nums) => {
	let len = nums.length - 1
	for (let i = len; i >= 0; i--) {
		if (nums.indexOf(nums[i]) != i) {
			nums[i] = nums[len--]
		}
	}
	// 删除重复项
	nums.splice(len + 1)
	return nums
}
// 测试
console.log(removeDuplicates([2, 1, 2, 3, 1, 2, 8, 1, 2, 3])) // [ 2, 1, 8, 3 ]

// 期待: [ 2, 1, 3, 8 ]

不使用新数组,二次循环

var arr = [1, 2, 3, 4, 5, 6, 4, 3, 8, 1]
// 数组去重:
// 方法8 :for + splice
// 利用 splice 进行切割。
function newArrFn(arr) {
	for (let i = 0; i < arr.length; i++) {
		for (let j = i + 1; j < arr.length; j++) {
			if (arr[i] === arr[j]) {
				arr.splice(j, 1)
				j--
			}
		}
	}
	return arr
}
console.log(newArrFn(arr))

// 方法一:最简单的遍历操作
let arr1 = [1, 2, 3, 1, 2, 8, 1, 2, 3];
let arr2 = [];
for (let i = 0; i < arr1.length; i++) {
    if (arr2.indexOf(arr1[i]) === -1) {
        arr2.push(arr1[i])
    }
}
console.log(arr2); // [ 1, 2, 3, 8 ]
// 方法二:数组方法forEach,主要利用indexof返回第一次找到的下标
let arr1 = [1, 2, 3, 1, 2, 8, 1, 2, 3];
let arr2 = [];
arr1.forEach((ele, index, arr) => {
    arr1.indexOf(ele) === index ? arr2.push(ele) : null;
})
console.log(arr2); // [ 1, 2, 3, 8 ]
// 方法三:利用es6的set方法
let arr1 = [1, 2, 3, 1, 2, 8, 1, 2, 3];
let arr2 = [...new Set(arr1)]
console.log(arr2); // [ 1, 2, 3, 8 ]
// 方法四:Array.from结合set
let arr1 = [1, 2, 3, 1, 2, 8, 1, 2, 3];
let arr2 = Array.from(new Set(arr1));
console.log(arr2); // [ 1, 2, 3, 8 ]
// 方法五:利用reduce将每次的新数组作为回调参数返回
let arr1 = [1, 2, 3, 1, 2, 8, 1, 2, 3];
var arr2 = arr1.reduce((prev, cur) => {
    prev.indexOf(cur) === -1 && prev.push(cur);
    return prev;
}, [])
console.log(arr2); // [ 1, 2, 3, 8 ]

ES6新特性

一、let和const

let和var的作用域不同,let的作用域是块级作用域,var的作用域是全局作用域

let和var对待变量提升不同,let不存在变量提升,必须先定义在使用,var存在变量提升 let定义变量,const定义常量

const在定义的时候,必须赋值,一但赋值,后面值是不可以改变的

const 如果定义的是对象,对象内的值发生改变是允许的,因为可以理解成const定义对象的指针地址并没有变

二、模板字符串

在ES6之前处理字符串用到/+,在ES6之后我们这样处理字符串:反问号、${}

let name = '小米';
let seatNumber = "123";
let sex = "女";
let str = "This demonstrates the output of HTML content to the page, including student's" + name + ", " + seatNumber + ", " + sex + " and so on."
console.log(str); // This demonstrates the output of HTML content to the page, including student's小米, 123, 女 and so on.
let name = '小红';
let seatNumber = "123";
let sex = "女";
let str = `This demonstrates the output of HTML content to the page, including student's ${name},${seatNumber},${sex} and so on.`
console.log(str); // This demonstrates the output of HTML content to the page, including student's 小红,123,女 and so on.

三、箭头函数

ES6中箭头函数是函数的一种简写形式,使用括号包含参数,跟随一个=>,后面是函数体

箭头函数的直观特点:

不需要function关键字来声明函数 省略return 继承上下文的this关键字,没有自己的this

// ES5
var add = function (a, b) {
  return a + b;
};
// ES6
var add = (a, b) => { a + b };
// ES5
[1, 2, 3].map(function (x) {
  return x + 1;
})
// ES6
[1, 2, 3].map(x => x + 1);

四、函数参数的默认值

// ES5
function printText(text){
  text = text || 'default';
  console.log(text); // hello 
}
printText("hello")
//ES6
function printText(text = 'default'){
  console.log(text);
}
printText(); // defalut

五、扩展操作符(...)

Spread / Rest 操作符指的是 ...,具体是 Spread 还是 Rest 需要看上下文语境。

当被用于迭代器中时,它是一个 Spread 操作符:

function foo (x,y,z){
  console.log(x,y,z);
}
let arr = [1,2,3];
foo(...arr) // 1 2 3

当被用于函数传参时,是一个 Rest 操作符:

function foo (...args){
  console.log(args);
}
foo(1,2,3); // [ 1, 2, 3 ]

💖补充点东西,之前对扩展运算符

六、支持二进制和八进制字面量

ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者0O 即可将其转换为八进制值,通过在数字前面添加 0b 或者0B即可将其转换为二进制值

let oValue = 0o10;
console.log(oValue); // 8
 
let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2

七、对象和数组解构

// 对象解构
const student = {
  name: 'sim',
  age: 22,
  sex: '男'
}
// ES5
const name = student.name;
const age = student.age;
const sex = student.sex;
console.log(name + ' --- ' + age + ' --- ' + sex);
//ES6
const {name,age,sex} = student;
console.log(name + ' --- ' + age + ' --- ' + sex);
// 数组解构
const student = ['Sam', 22, '男'];
console.log(...student);

八、在对象中使用super方法

var parent = {
  foo() {
    console.log("Hello from the Parent");
  }
}
var child = {
  foo() {
    super.foo();
    console.log("Hello from the Child");
  }
}
Object.setPrototypeOf(child, parent);
child.foo();//Hello from the Parent   Hello from the Child

九、for of 和for in

for...of 用于遍历一个迭代器,如数组:

let arr = [1, 2, 3];
for (let num of arr) {
  console.log(num);
}

for...in 用来遍历对象中的属性:

let stus = ["Sam", "22", "男"];
for (let stu in stus) {
  console.log(stus[stu]);
}

十、ES6中的类

类的本质是一个函数,我们也可以简单的认为类就是构造函数的另一种写法,类也有prototype,通过类new的实例对象也有__proto__,该有的


class student {
  constructor() {
    console.log("I'm a student.");
  }
}
console.log(typeof student); // function 
class student {
  constructor() {
    console.log("I'm a student.");
  }
  study() {
    console.log('study!');
  }
  read() {
    console.log("Reading Now.");
  }
}
console.log(typeof student); // function 
let stu = new student(); // I'm a student.
stu.study(); // study!
stu.read(); // Reading Now.

类中的继承和超集

class Phone {
  constructor() {
    console.log("I'm a phone.");
  }
}
class MI extends Phone {
  constructor() {
    super();
    console.log("I'm a phone designed by xiaomi");
  }
}
let mi = new MI();
console.log(mi);//I'm a phone.   I'm a phone designed by xiaomi

extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。
类的声明不会提升,如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个的错误
在类中定义函数不需要使用 function 关键词

十一、Proxy

1、定义

proxy是ES6新增的功能,可以用来自定义对象中的操作。通过es6的表述,可以把proxy理解成拦截器,在操作对象之前,都必须经过拦截器。

2、使用

let p = new Proxy(target, handler)

3、优缺点

优点:解决vue2中通过Object.defineProperty无法监听新增属性无法监听数组长度变化等的问题

缺点:兼容性不好

4、get方法

get方法实现数组的负索引

function createArray(...elements) {
    // 目标对象
    let target = [];
    target.push(...elements);
    // handler 对象
    let handler = {
        get(target, propsKey, receiver) {
            let index = Number(propsKey);
            if (index < 0) {
                //对数组的负索引处理
                propsKey = String(target.length + index);
            }
            return Reflect.get(target, propsKey, receiver);

        }
    }
    return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');

console.log(arr[-2]); // b

5、set方法

set方法实现检验对象内部属性

let handler = {
    get(target, propsKey) {
        invant(propsKey, 'get');
        return target[propsKey];

    },
    set(target, propsKey, value) {
        invant(propsKey, 'set');
        target[propsKey] = value;
        return true;
    }
}
function invant(key, action) {
    // key _prop以字符串的形式体现,key[0]是_
    if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
    }

}
const target = {};
const proxy = new Proxy(target, handler);
console.log(proxy._prop); // 报错 Invalid attempt to get private "_prop" property

模板字符串

一、模板字符串

1、使用${}嵌入变量

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`

2、支持html代码

let list = `
  <ul>
    <li>列表项1</li>
    <li>列表项2</li>
  </ul>
`;
console.log(list); // 正确输出,不存在报错

3、支持运算

function add(a, b) {
  const finalString = `${a} + ${b} = ${a+b}`
  console.log(finalString)
}
add(1, 2) // 输出 '1 + 2 = 3'

二、判断字符、字符串存在的方法

1、indexOf

const son = 'haha'
const father = 'xixi haha hehe'
console.log(father.indexOf(son)); // 5

2、includes

const son = 'haha'
const father = 'xixi haha hehe'
console.log(father.includes(son)); // true

3、startWith

const son = 'haha'
const father = 'xixi haha hehe'
console.log(father.startsWith(son)); // false

4、endWith

const son = 'haha'
const father = 'xixi haha haha'
console.log(father.endsWith(son)); // true

5、repeat

const son = 'haha'
const father = son.repeat(3)
console.log(father); // hahahahahaha

解构赋值

一、数组解构

1、条件

数组解构以元素的位置决定

2、举例

const [a, b, c] = [1, 2, 3];
// a 1
// b 2
// c 3

二、对象解构

1、条件

对象的解构是严格按照属性名来解构的

2、举例

const stu = {
  name: 'Bob',
  age: 24
}
const {name,age} = stu;
// name:"Bob"
// age:24 
// *****************即使是换了位置也不会影响解构结果******************
const {age,name} = stu;
// name:"Bob"
// age:24

3、解构深层嵌套

const school = {
    classes: {
        stu: {
            name: 'Bob',
            age: 24,
        }
    }
}
const { classes: { stu: { name } } } = school;
console.log(name); // Bob

扩展运算符

一、对象扩展运算符

1、拷贝对象

let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }

扩展运算符拷贝的只有一层的话,完成的是深拷贝

上述代码等价于下面的代码

let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }

2、合并对象

let bar = {a: 1, b: 2};
let baz = {...bar, ...{a:2, b: 4}};  // {a: 2, b: 4}

源对象中出现同名属性,会覆盖目标对象的属性;源对象中未出现的属性,会做为新的属性给到目标对象

二、数组扩展运算符

1、将数组转换为参数序列

console.log(...[1, 2, 3])
// 1 2 3

2、复制数组

const arr1 = [1, 2];
const arr2 = [...arr1];

3、合并数组

const arr1 = ['two', 'three'];
const arr2 = ['one', ...arr1, 'four', 'five'];
// ["one", "two", "three", "four", "five"]

4、和解构赋值结合,生成数组

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

扩展运算符和解构赋值结合的话,扩展运算符只能放在最后,不然会报错

5、字符串转数组

[...'hello']    // [ "h", "e", "l", "l", "o" ]

6、Iterator 接口对象转数组

// arguments对象
function foo() {
  const args = [...arguments];
}

7、和Math结合获取结果

const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
Math.max(...numbers); // 9

箭头函数

一、箭头函数和new

1、new一个对象过程

(1)创建一个空对象

var obj = new Object();

(2)将空对象的__proto__指向构造函数的原型

obj.__proto__ = Person.prototype;

(3)将构造函数的this指向创建的新对象

Person.apply(obj);

(4)执行构造函数内部的代码(给新对象添加属性)

(5)如果构造函数返回非空对象,就返回该对象,否则返回新对象

return obj

2、手写一个new函数

// 手写一个new函数
var myNew = function () {
    // 创建一个空对象
    var obj = new Object();
    // 返回第一个参数做构造函数  [].shift.call(arguments)删除并拿到arguments的第一项
    var Constructor = [].shift.call(arguments);
    // 将空对象的__proro__指向构造函数的原型
    obj.__proto__ = Constructor.prototype;
    // 调用构造函数 将obj作为this,arguments作为参数
    var result = Constructor.apply(obj, arguments);
    console.log('result', result);
    console.log('obj', obj);
    // 构造函数返回一个对象,直接返回,如果不是就返回obj
    return typeof result === 'object' ? result : obj;
}
function Person(name, age) {
    this.name = name;
    this.age = age;
}
let person = myNew(Person, '张三', 18)
console.log(person); //Person { name: '张三', age: 18 }

3、箭头函数为什么不可以new

箭头函数没有自己的this,没有自己的arguments,那就无法完成new一个对象的过程。

二、箭头函数和普通函数的区别

1、 箭头函数没有自己的this

2、箭头函数没有自己的arguments

3、箭头函数的this指向不能改变

4、箭头函数不能做构造函数,也就是不能new

5、箭头函数没有prototype

6、箭头函数不能做Generator函数,不能使用yield关键字

7、call、apply、bind都无法改变this 的指向

三、箭头函数的this指向

1、箭头函数this的指向

箭头函数的this指向它所在上下文的this,也就是继承外部作用域的this

2、例题1

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

箭头函数属于对象obj的b属性,obj对象{}无法成为独立的作用域,所以箭头函数的this还是指向全局作用域的window对象。

3、例题2

利用Babel转义理解箭头函数

// ES6 
const obj = { 
  getArrow() { 
    return () => { 
      console.log(this === obj); 
    }; 
  } 
}
// ES5,由 Babel 转译
var obj = { 
   getArrow: function getArrow() { 
     var _this = this; 
     return function () { 
        console.log(_this === obj); 
     }; 
   } 
};

转义之后,getArrow()方法很明显形成闭包,内部变量可以访问外部变量,所以obj.getArrow()()会输出true

let、const、var的区别

1、块级作用域

let和const有块级作用域,var不存在块级作用域

块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量

2、变量提升

var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。

3、给全局添加属性

浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

4、重复声明

var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

5、暂时性死区

在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

6、初始值设置

在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

7、指针指向

let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

参考👀

www.jianshu.com/p/ac1787f6c…