Skip to main content

3 posts tagged with "es6"

View All Tags

· 5 min read

对于面向对象的编程语言这一说法,准确来说应该是 支持使用面向对象程序设计思想进行编程的编程语言,而这一思想的本质其实是抽象。从这点出发,其实 JavaScript 就是一门面向对象的语言,但是其他面向对象的语言(比如 Java、C++)是基于 class 来实现的,而 JavaScript 是基于原型来实现"

class

es6 新增了 class,下面创建一个关于 Animal 的类

class Animal {
constructor(name) {
// 这里面的this会指向实例
this.name = name;
}
static parentName = "Animal"; // 静态属性
static getParentName() {
// 1.静态方法的this只会指向父类
// 2.可以通过父类调用
// 3.静态方法不能被实例继承,但可以被子类继承
console.log(this.parentName);
return this.parentName;
}
getName() {
console.log(this.name);
return this.name;
}
}

实现 class

其实 es6 的 class 就是一个语法糖,是基于 js 构造函数和原型去实现的,实现代码如下:

function createClass(Constructor, protoProps, staticProps) {
if (protoProps) {
Object.defineProperties(Constructor.prototype, protoProps);
}
if (staticProps) {
Object.defineProperties(Constructor, staticProps);
}
return Constructor;
}
function Animal(name) {
// 实例属性赋值
this.name = name;
}

// 在构造函数的原型对象上挂载实例方法
createClass(
Animal,
{
getName: {
value: function getName() {
console.log("调用实例方法,获取实例属性", this.name);
return this.name;
},
},
},
{
getParentName: {
// 在构造函数上挂载静态属性和方法
value: function getParentName() {
console.log("调用静态方法,获取静态属性", this.parentName);
return this.parentName;
},
},
parentName: {
value: "Animal",
},
}
);

let animal1 = new Animal("animal1");

// 调用实例方法
animal1.getName();
// 调用静态方法
Animal.getParentName();

在创建对象实例时 let animal1 = new Animal('animal1'),主要会经历几个步骤

// 1.新建空对象
let newObj = new Object();
// 2.指向构造函数的原型对象,共享实例方法
newObj.__proto__ = Animal.prototype;
// 3.以新对象为上下文执行构造函数
let result = Animal.call(newObj, "animal1");
// 4.返回一个新对象
// 如果构造函数返回一个对象或函数,对象实例就是该对象或函数;否则返回一个新对象
return result instanceof Object ? result : newObj;

继承

下面通过 extends 关键字实现对 Animal 的继承

class Animal {
constructor(name) {
// 实例属性赋值,这里面的this会指向实例
this.name = name;
}
static parentName = "Animal"; // 静态属性
getName() {
console.log("调用实例方法,获取实例属性", this.name);
return this.name;
}
}

class Cat extends Animal {
constructor(name) {
super(name);
}
}

let cat = new Cat("jack");
cat.getName();
console.log(Cat.parentName);
// 可以发现,静态属性和方法也被子类继承了

实现 extends

extends 本质上是基于寄生式组合继承来实现的

  1. new 一个实例时,调用父类构造函数,继承实例属性
  2. 通过原型链继承父类实例方法
  3. 设置子类的 proto = 父类,让子类可以调用父类静态属性和方法
// 已知 Animal 这个父类
function _inherits(child, parent) {
let proto = Object.create(parent.prototype);
proto.constructor = child;
child.prototype = proto;
}

function Cat(name) {
// 调用父类的构造函数,继承实例属性
Animal.call(this, name);
}

// 继承父类实例方法
_inherits(Cat, Animal);
// 设置子类的 __proto__ = 父类,让子类可以调用父类静态属性和方法
Object.setPrototypeOf(Cat, Animal);

let cat = new Cat("jack");
cat.getName();
console.log(Cat.parentName);

· 10 min read

数组是 js 中常见的对象,关于数组的操作很多,作为一个前端攻城狮,应该要能通过灵活应用其内置属性和方法,来解决一系列数组处理问题

初始化数组

  • 构造函数方法

var arr = new Array(3)

var arr = new Array('a','b','c')

var arr = Array(3)

  • 字面量方法 //无法指定成员个数

var arr = ['a','b','c']

数组检查和转化

检查数组类型

let arr = [];
// 1.ES5的isArray方法
Array.isArray(arr);

// 2.constructor指向对象实例的构造函数
arr.constructor === Array;

// 3.instanceof原理同2
arr instanceof Array;

// 4.利用Object的原型方法toString判断(必须是Object原型方法,array和function均重写了这个方法)
// toString方法返回反映这个对象的字符串
//可以用来区分各种类型,但是无法判断自定义对象类型,此时可用2.3方法判断自定义类型
Object.prototype.toString.call(arr) === "[object Array]";

数组转化问题

数组转 String--join(separator)
// 注意区别使用空格和空值
arr.join(" ");
String 转数组--split(separator)
// 根据separator来分割字符串,每一项作为数组的一个值
str.spilt(",");
对象转数组
let obj = { name: "jacksonzhou", age: 23 };
Object.keys(obj); // [name,age]
Object.values(obj); // ['jacksonzhou',23]
Object.entries(obj); // [[name,'jacksonzhou'],[age,23]]
数组转对象
Object.assign({},[1,2]) // 转成{0:1,1:1}
{...['a','b','c']} // 转成{0:'a',1:'b',3:'c'}
// es10的api,浏览器暂不支持
Object.fromEntries([name,'jacksonzhou'],[age,23])
类数组转化为数组

类数组定义:不具有数组所具有的方法,但具有 length 属性,其他属性值为非负整数;常见的类数组有 arguments 对象和 DOM 方法的返回结果(document.getElementsByTagName())

// 使用Array原型方法slice
Array.prototype.slice.call(arguments)
// from方法将类似数组或可迭代对象创建为数组
Array.from(arguments)
// ... (扩展运算符)将类数组扩展为一个列表??(列表实际上是啥?),再定义为数组。
[...arguments]
// 借助对象+indexOf,思路是遍历数组,将每一个值标记为属性,将出现一次的值加入新数组;
//一旦obj.indexOf(i)不为-1,说明重复了,不将它加入新数组

针对数组某项的操作

判断是否包含值

// indexOf
[1, 2, 3]
.indexOf(2) // 1(其对应索引),不存在则为-1
[
// includes
(1, 2, 3)
].includes(2) // true
[
// find
(1, 2, 3)
].find((i) => {
i === 3;
}) // 3,无值返回undefined
[
// findIndex
(1, 2, 3)
].findIndex((i) => {
i === 3;
}); //2,返回索引,无值返回-1

给每一项设置统一值

[1, 2, 3].fill(5); // [5,5,5]

每一项是否满足

[1, 2, 3].every((i) => {
return i > 2;
}); // false

有一项满足

[1, 2, 3].some((i) => {
return i > 2;
}); // true

计算每一项出现的次数

// 数组遍历+借助对象+属性标记+值计数

过滤数组

[1, 2, 3, 4, 5]
.filter((i) => {
return i > 2;
}) // [3,4,5]
[
// 过滤空值
(1, 2, null, 3, 0, false, 4, undefined, "", 5)
].filter(Boolean)
[
// [1,2,3,4,5],但不过滤空的引用类型,比如{}、[]
("1", "2", "3")
].map(parseInt) ===
// 输出[1, NaN, NaN]
["1", "2", "3"].map((item, index) => parseInt(item, index));
  • 简易实现代码
Array.prototype.filterz = function (fn) {
let arr = this;
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (fn(arr[i])) {
newArr.push(arr[i]);
}
}
return newArr;
}[(1, 2, 3, 4, 5)].filterz((i) => {
return i > 2;
});

重装数组

[1, 2, 3].map((i) => {
return "我是" + i;
});

数组乱序

let arr = [1,2,3,4,5,6]
arr.sort(function(){
return Math.random()-0.5
})

数组去重

Map 对象+过滤器 filter

Map 数据结构,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

function unique(arr) {
const res = new Map();
return arr.filter((a) => !res.has(a) && res.set(a, 1));
}

set(集合)去重

Set 数据结构,它类似于数组,但是成员的值都是唯一的,没有重复的值。

Array.from(new Set([1,2,2,3]))
[...new Set([1,2,2,3])]

直接使用数组去重

function unique2(arr) {
let newArr = []
for(let i=0;i<arr.length;i++){
if(newArr.indexOf(arr[i])===-1)newArr.push(arr[i])
}
return newArr
}

对象数组去重

let json1 = [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
{ id: 3, name: "c" },
];

let json2 = [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
{ id: 4, name: "d" },
];

let json = json1.concat(json2); //两个数组对象合并
let newJson = []; //盛放去重后数据的新数组
for (item1 of json) {
//循环json数组对象的内容
let flag = true; //建立标记,判断数据是否重复,true为不重复
for (item2 of newJson) {
//循环新数组的内容
if (item1.id == item2.id) {
//让json数组对象的内容与新数组的内容作比较,相同的话,改变标记为false
flag = false;
}
}

if (flag) {
//判断是否重复
newJson.push(item1); //不重复的放入新数组。 新数组的内容会继续进行上边的循环。
}
}

console.log("newJson", newJson);

扁平化数组

// flat是ES10的api, n表示维度, n为 Infinity 时维度为无限大
[[1,2],3].flat(2) // [1,2,3]
[1,[2,3,[4,[...]]]].flat(Infinity) // [1,2,3,4,...]
// 利用toString()实现
let arr = [1,[2,3,[4,5]]]
arr.toString().split(',').map(item=>{
+item // 强制转换为数字类型
})
// 使用迭代的方式实现
let arr = [1, 2, [3, 4, 5, [6, 7], 8]]
function flat(arr) {
while(arr.some(item => Array.isArray(item))){
arr = [].concat(...arr) // 将数组型数值追加,并重复追加,知道没有数组型数据
}
return arr
}
flat(arr)
// 使用递归的方式实现
const flat = arr => arr.reduce((acc, cur) => (Array.isArray(cur) ? [...acc, ...flat(cur)] : [...acc, cur]), [])

排序

// sort--关于排序有太多算法知识点,这里只简单实现升序降序,后续再总结相关算法吧~
[1, 3, 2]
.sort((a, b) => {
a - b;
}) // 升序
[(1, 3, 2)].sort((a, b) => {
b - a;
}); // 降序

求最值

// Math.max/min
Math.max(...[1, 2, 3]); // ...将数组转化为参数列表,供max方法使用
Math.max.apply(this, [1, 2, 3])[
// 利用apply可带数组参数
// === Math.max.call(this,...[1,2,3])

// reduce--累加器
(1, 2, 3, 5)
].reduce((pre, cur) => {
return Math.max(pre, cur);
}, 0);
// pre 指上一次return的值,cur指当前值,0指初始值

reducer 累加器原理

function reduce (arr, fn, initVal) {
for (let i = 0; i < arr.length; i++) {
initVal = fn(initVal, arr[i])
}
return initVal
}
let newArr = reduce(arr, (sum, cur) => {
return sum + cur
}, 5)
console.log(newArr)

求和

// 遍历求和
let sum = 0;
[1, 2, 3].map((i) => {
sum += i;
});
console.log(sum);
// reduce -- 累加器
let sum = [1, 2, 3].reduce((prev, current) => {
return prev + current;
}, 0);
// 0 是初始值, prev 是先前return的值,current是当前值

合并

// concat--不会改变原数组,返回一个数组副本
let newArr = arr.concat([1,2,3])
// push--会改变原数组
arr.push([1,2,3])
// ...
[...[1,2,3],...[4,5]]

其他

  • 请把两个数组 ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']['A', 'B', 'C', 'D'],合并为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']
// 先给第二个数组的值加个数字,让其能按顺序排序,然后再遍历数组去掉第二个数组的值加上的数字
let a1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
let a2 = ['A', 'B', 'C', 'D'].map((item) => {
return item + 3
})

let a3 = [...a1, ...a2].sort().map((item) => {
if(item.includes('3')){
return item.split('')[0]
}
return item
})

栈和队列操作

栈操作

  • push()
  • pop()

队操作

  • push()
  • shift() // 区别一下 unshift()

扩展运算符...

  • 复制数组
let arr1 = [1, 2, 3];
let arr2 = arr1; // 仅是复制引用,修改arr2会影响arr1
let arr2 = [...arr1]; // 实际上等同于重新声明赋值
  • 转为函数参数
Math.max(...[-1, 1, 1001, -52]); // 1001

· 13 min read

ECMAScript6 是 JavaScript 的标准,目前浏览器的 JavaScript 大多数都支持 ES6。ES6 使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

var,let 和 const

  • var 和 let 都可用于定义变量,但是 let 定义的变量只能在一个作用域内使用
{
var demo_1 = "demo_1";
let demo_2 = "demo_2";
}
console.log(demo_1);
// 可以打印出来demo_1
console.log(demo_2);
// 不可以打印出来demo_2
  • const 定义不可变量
const demo_3 = "demo_3";
demo_3 = "demo_4";
// 会提示报错
// 如果 const 的是一个对象,对象所包含的值是可以被修改的。
// 抽象一点儿说,就是对象所指向的地址不能改变,而变量成员是可以修改的。

js 在引擎扫描代码时,如果发现变量声明,用 var 声明变量时会将声明提升到函数或全局作用域的顶部。但是 let 或者 const,会将声明关进一个小黑屋也是 TDZ(暂时性死区),只有执行到变量声明这句语句时,变量才会从小黑屋被放出来,才能安全使用这个变量

字符串模板

  • 告别用 + 和 \ 拼接字符串
// 在ES5时我们通过反斜杠(\)来做多行字符串或者字符串一行行拼接
// ES6直接通过``拼接
const str = "demo";
console.log(`
test ${str}!!
`);
// 显示test demo!!
  • 其他好用的方法
// includes 判断字符串是否包含指定值
const str = "jacksonzhou";
console.log(str.includes("zh")); // true

// repeat 获取字符串重复的次数
const str = "z";
console.log(str.repeat(3)); // 'zzz'
//如果你带入小数, Math.floor(num) 来处理
// s.repeat(3.1) 或者 s.repeat(3.9) 都当做成 s.repeat(3) 来处理

// startsWith 和 endsWith 判断是否以给定文本开始或者结束
const str = "hello world!";
console.log(str.startsWith("hello")); // true
console.log(str.endsWith("!")); // true

// padStart 和 padEnd 填充字符串,应用场景:时分秒
setInterval(() => {
const now = new Date();
const hours = now.getHours().toString();
const minutes = now.getMinutes().toString();
const seconds = now.getSeconds().toString();
console.log(
`${hours.padStart(2, 0)}:${minutes.padStart(2, 0)}:${seconds.padStart(
2,
0
)}`
);
}, 1000);
  • 面试题
    • 模拟一个模板字符串的实现
    • 实现标签化模板(自定义模板规则)

函数扩展

  • 函数简写 (箭头函数)
    • 不需要 function 关键字来创建函数
    • 可省略 return 关键字
    • 继承当前上下文的 this 关键字
    • 没有 this、super、arguments 和 new.target 绑定,这些值由继承的上下文决定
    • 不存在 prototype 这个属性
//例如:
[1, 2, 3]
.map((x) => x + 1)
[
//等同于:
(1, 2, 3)
].map(
function (x) {
return x + 1;
}.bind(this)
);

什么情况下不适用箭头函数? 当要求动态上下文的时候,你就不能使用箭头函数,比如:定义对象方法、定义原型方法、定义构造函数、定义事件回调函数 什么时候你不能用箭头函数

  • 带默认参数
// es6之前,定义默认参数的方法是在一个方法内部定义
var link = function (width, height) {
var width = width || 100;
var height = height || 100;
};
// 而在es6中你可以这样写
var link = function (width = 100, height = 100) {};
// 做参数的非空检测
const notNull = () => {
throw new Error("参数不能为空");
};
const func = (id = notNull()) => {
console.log(`${id}`);
};
func(1);
func();

展开运算符 ...

// 数组
let arr = [1, 2];
console.log(...arr, 3, 4, 5);
// 打印出1,2,3,4,5
const number = [1, 2, 3, 4, 5];
const [first, ...rest] = number;
console.log(rest); //2,3,4,5
// 对象
const alp = { fist: "a", second: "b" };
const alphabets = { ...alp, third: "c" };
console.log(alphabets); //{ "fist": "a", "second": "b", "third": "c"
// 函数参数的应用
const add_4 = (num1, num2) => num1 + num2;
console.log(add_4(...arr));
// 展开1和2,分别作为num1和num2的参数,打印出3

// 对于对象而言,组合成新的对象时,如果有重复的属性名,后者会覆盖前者
const first = {
a: 1,
b: 2,
c: 6,
};
const second = {
c: 3,
d: 4,
};
const total = { ...first, ...second };
console.log(total); // { a: 1, b: 2, c: 3, d: 4 }

对象扩展

  • 对象简写
const demo = "objs";
const obj = {
name: "obj",
func:function() {
return: 1;
},
demo, // 相当于demo: demo,即键值对重名简写
}
console.log(obj)
// 打印出来的对象不含有objs属性,而是demo:"objs"
// 必须在外面定义 obj[demo] ="jack"才能使对象具有objs属性
const demo = "objs";
const obj = {
name: "obj",
func() {
return 1;
},
[demo]: "jack",
[demo+"2"]: "jackson",
}
console.log(obj)
// 打印出来的对象含有objs属性和objs2属性,即objs:"jack"

  • 对象方法

    • ES6 对象提供了 Object.assign()这个方法来实现浅复制。
    const objA = { name: "cc", age: 18 };
    const objB = { address: "beijing" };
    const objC = {}; // 这个为目标对象
    const obj = Object.assign(objC, objA, objB);

    // 我们将 objA objB objC obj 分别输出看看
    console.log(objA); // { name: 'cc', age: 18 }
    console.log(objB); // { address: 'beijing' }
    console.log(objC); // { name: 'cc', age: 18, address: 'beijing' }
    console.log(obj); // { name: 'cc', age: 18, address: 'beijing' }

    // 是的,目标对象ObjC的值被改变了。
    // so,如果objC也是你的一个源对象的话。请在objC前面填在一个目标对象{}
    Object.assign({}, objC, objA, objB);
  • 面试题

// 请使用 ES6 重构一下代码
// 第一题
var jsonParse = require("body-parser").jsonParse;
// 答
import { jsonParse } from "body-parser";
// 第二题
var body = request.body;
var username = body.username;
var password = body.password;
// 答
const {
body,
body: { username, password },
} = request;
// 这个在配合async和await处理异步请求时很好用

解构赋值

  • 数组赋值
let arr = [1, 2];
let [num1, num2] = arr;
<!--num1和num2分别赋值为12-->
  • 对象赋值
let obj = {num: 1, type: "对象"};
let {num, type} = obj;
<!--num和type分别赋值为1"对象"-->

class 类(对象封装的语法糖)

原理实现可看 从 js 看面向对象的封装、继承和多态

class Person {
constructor(name) { // name为实例传入的参数
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
class Actor extends Person {
hello() {
return super.hello() + ' I am an actor.'
}
}
var jacksonzhou = new Actor('jacksonzhou')
jacksonzhou.hello()

// 定义类的变量名
const testClass = class demo{
constructor(){}
}
// 可省略类名
const testClass = class {
constructor(){}
}

// 立即执行的类
const testClass = new class {
constructor(name,age){
console.log(name,age)
}
}("jacksonzhou",24)
  • constructor(arguments)
constructor中定义的属性称为实例属性
constructor外声明的属性定义在原型上,即原型属性
使用new创建实例对象时,自动调用这个函数,传入参数,只能有一个constructor
  • extends
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))
ES6的继承,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

  • super
1.子类constructor中需要执行super(arguments),要放在头部
2.因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工
3.通过使用super().methods 引用父类方法或属性
4.super作为对象时,指向父类的原型对象(super.getName())

模块化

将模块标准(AMD,CommonJS,RequireJS)化为通用格式

  • import, import{}, export, export default
1.当用export default people导出时,就用 import people 导入(不带大括号)
2.当用export name 时,就用import { name }导入(记得带上大括号)
3.一个文件里,有且只能有一个export default,但可以有多个export
4.当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age }
5.当一个文件里出现n多个 export 导出很多模块,导入时除了一个一个导入,也可以用import * as example
  • node 还不支持 import,需要用 require 加载文件

Promise

在 promise 之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过 Promise 机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。

// 其实就是用同步的方式去实现异步
fetch('/api')
.then(res => res.json())
.then(data => ({ data }))
.catch(err => ({ err }))
  • 面试题
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for (var i = 0; i < 10000; i++) {
i == 9999 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
});
console.log(5);

/* 在执行定时器、事件、ajax等操作的时候,会把异步操作放到任务队列里等待主线程执行完
* 所以,定时器(setTimeout)会放到任务队列中,代码继续往下走
* 先输出2、3
* promise中的then操作是放在执行栈,也就是在主线程的最后去执行
* 主线程会继续往下走
* 所以输出5、4
* 执行完主线程的任务,才会去执行任务队列中的任务
* 所以最后执行1
*/

Generators 和 yield

生成器( generator)是能返回一个迭代器的函数。生成器函数也是一种函数,最直观的表现就是比普通的 function 多了个星号*,在其函数体内可以使用 yield 关键字,有意思的是函数会在每个 yield 后暂停

// 生成器
function* createIterator() {
yield 1;
yield 2;
yield 3;
}

// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();

console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

async 和 await

以顺序、同步代码的编写方式来控制异步流程,需要注意以下几点:

  • await 关键字必须位于 async 函数内部
  • await 关键字后面需要一个 promise 对象(不是的话就调用 resolve 转换它)
  • 串行的执行异步操作,实现并行可以考虑promise.all
async getUser () {
const res = await Apis.getUser()
// 未返回的情况下会阻塞代码,res为异步请求返回的结果
console.log(res)
}

参考