数组去重
学到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声明的变量是不允许改变指针的指向。