目录
Set 和 Map 数据结构
Set 数据结构
基本用法
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 去除数组的重复成员
[...new Set(array)]
[...new Set('ababbc')].join('')
// "abc"
var mySet = new Set();
mySet.add("5")
mySet.add(5)
console.log(mySet); // Set(2) {'5', 5}
size属性
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
Set 数据结构方法
add()
var mySet = new Set();
mySet.add("5")
console.log(mySet);
delete()
var mySet = new Set();
mySet.add("5")
var flag = mySet.delete("5");
console.log(flag); // true
has()
var mySet = new Set();
mySet.add("5")
var flag = mySet.has("5");
console.log(flag); // true
clear()
var mySet = new Set();
mySet.add("5")
mySet.clear();
console.log(mySet); // Set(0) {size: 0}
Array.from方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
这就提供了去除数组重复成员的另一种方法。
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
遍历操作
- Set.prototype.keys() :返回键名的遍历器
- Set.prototype.values() :返回键值的遍历器
- Set.prototype.entries() :返回键值对的遍历器
- Set.prototype.forEach() :使用回调函数遍历每个成员
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item); // red green blue
}
for (let item of set.values()) {
console.log(item); // red green blue
}
for (let item of set.entries()) {
console.log(item); // ['red', 'red'] ['green', 'green'] ['blue', 'blue']
}
let set = new Set([1, 4, 9]);
set.forEach((value) => console.log(value))
WeakSet数据结构
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
在初始化 WeakSet 的时候,无论是数组还是对象,都是将他们的成员放入到 WeakSet 中的
const ws = new WeakSet([{name: 'iwen'}]);//再套一层
console.log(ws);
const ws = new WeakSet([[10,20]]);
console.log(ws);
- WeakSet.prototype.add(value) :向 WeakSet 实例添加一个新成员。
- WeakSet.prototype.delete(value) :清除 WeakSet 实例的指定成员。
- WeakSet.prototype.has(value) :返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
let user1 = {
"name1": "iwen"
}
let user2 = {
"name2": "itbaizhan"
}
let weakSet = new WeakSet([user1])
console.log(weakSet.add(user2)) // WeakSet
{{…}, {…}}
console.log(weakSet.has(user2)) // true
console.log(weakSet.delete(user2)) // true
console.log(weakSet.has(user2)) // false
Map数据结构
var user = {
"name":"iwen",
age:20
}
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
<script>
const m = new Map();
const o = { p: 'Hello World' };
m.set(o, 'content')
console.log(m.get(o));//content
</script>
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map.set(k1, 111).set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
const map = new Map();
// const k1 = ['a'];
// const k2 = ['a'];
map.set(['a'], 111).set(['a'], 222);
console.log(map.get(['a']));
// undefined
console.log(map.get(['a']));
// undefined
const map = new Map();
map.set(['hello'], 10);
var h = ["hello"];
map.set(h, 20)
console.log(map.get(h));//20
console.log(map.get(['hello']));//undefined
Map数据结构的属性和方法
const map = new Map();
map.set('itbaizhan', true);
map.set('sxt', false);
map.size // 2
const map = new Map();
map.set('itbaizhan', true);
map.set('sxt', false);
console.log(map);
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
console.log(map);
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
console.log(map.get("itbaizhan"));
console.log(map.get("it"));
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
console.log(map.has("itbaizhan"));
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
map.delete("itbaizhan")
console.log(map);
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
map.clear()
console.log(map);
- Map.prototype.keys() :返回键名的遍历器。
- Map.prototype.values() :返回键值的遍历器。
- Map.prototype.entries() :返回所有成员的遍历器。
- Map.prototype.forEach() :遍历 Map 的所有成员。
const map = new Map();
map.set('itbaizhan', true).set("sxt",15)
for (let key of map.keys()) {
console.log(key);
}
for (let value of map.values()) {
console.log(value);
}
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
map.forEach(function (value, key, map) {
console.log(key,value);
});
Proxy
<script>
var user = {};
var proxy = new Proxy(user,{
// get:function(target,propKey){
// console.log(target,propKey);
// return propKey;
// },
set:function(target,propKey,value){//对应参数 user age 1000
if(propKey === "age"){
if(!Number.isInteger(value)){
throw new TypeError('The age is not an integer');
}
if(value >= 200){
throw new RangeError('The age seems invalid');
}
}
target[propKey] = value;
}
})
proxy.age = 1000;
console.log(proxy.age);
</script>
var user = {};
var obj = new Proxy(user, {
get: function (target, propKey) {
return "不给你看"
},
set: function (target, propKey, value) {
if (propKey === "age") {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
target[propKey] = value;
}
})
obj.age = 20
obj.name = "iwen"
console.log(obj.age);//不给你看
Reflect
const duck = {
name: 'Maurice',
color: 'white',
greeting: function() {
console.log(`Quaaaack! My name is
${this.name}`);
}
}
Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false
const duck = {
name: 'Maurice',
color: 'white',
greeting: function () {
console.log(`Quaaaack! My name is
${this.name}`);
}
}
console.log(Reflect.ownKeys(duck));//['name', 'color', 'greeting']
const duck = {
name: 'Maurice',
color: 'white',
greeting: function () {
console.log(`Quaaaack! My name is ${this.name}`);
}
}
Reflect.set(duck, 'eyes', 'black');
console.log(duck);//{name: 'Maurice', color: 'white', eyes: 'black', greeting: ƒ}
var user = {}
var obj = new Proxy(user, {
set: function (target, name, value) {
var success = Reflect.set(target,name, value);
if (success) {
console.log('property ' + name +' on ' + target + ' set to ' + value);
}
return success;
}
});
obj.name = "iwen"
console.log(obj.name);
//property name on [object Object] set to iwen
//iwen
同步与异步
举例:你去商城买东西,你看上了一款手机,能和店家说你一个这款手机,他就去仓库拿货,你得在店里等着,不能离开,这叫做同步。现在你买手机直接去京东下单,下单完成后你就可用做其他时间(追剧、打王者、 lol )等货到了去签收就 ok 了 . 这就叫异步
for (var i = 0; i < 10000; i++) {
if (i == 9999){
console.log("循环结束了~~")
}
}
console.log("ok")
// 循环结束了~~
// ok
while(1){
}
console.log("ok")
var n = 0;
setTimeout(function () {
n++;
console.log(n);
}, 1000);
console.log(n);
// 0
// 1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jquery-3.6.0.js"></script>
</head>
<body>
<div id="root"></div>
<script>
var content = ""
// 异步操作
$.ajax({
type:"get",
url:"http://iwenwiki.com/api/blueberrypai/getBlueBerryJamInfo.php",
success:function(data){
callback(data)
}
})
// 回调函数
function callback(data){
var root = document.getElementById("root");
root.innerHTML = data.msg;
cb1()
}
function cb1(){
console.log("cb1");
}
</script>
</body>
</html>
Promise 对象
const promise = new Promise(function(resolve,
reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jquery-3.6.0.js"></script>
</head>
<body>
<div>
</div>
<script>
function loadImageAsync(url) {
var promise = new Promise(function (resolve, reject) {
var image = new Image();
image.src = url;
image.onload = function () {
resolve(image);
}
image.onerror = function () {
reject(new Error('Could not load image at ' + url))
}
})
return promise;
}
loadImageAsync("http://iwenwiki.com/api/vue-data/vue-data-1.png").then(
function (data) {
$("div").append(data);
},
function (error) {
$("div").html(error);
}
)
</script>
</body>
</html>
<script>
function getJSON(url) {
const promise = new Promise(function (resolve, reject) {
//网络请求
const client = new XMLHttpRequest();
client.open("get", url);
//指定json格式数据
// client.responseType = "json";
client.send();
client.onreadystatechange = function () {
if (client.readState === 4) {
if (client.status === 200) {
resolve(client.response);
} else {
reject(new Error(client.statusText));
}
}
}
})
return promise;
}
getJSON("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php").then(
function (data) {
console.log(data);
},
function (error) {
console.log(error);
}
)
</script>
Promise对象_方法
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
},function (err){
console.log("rejected: ", err);
});
getJSON("http://iwenwiki.com/api/blueberrypa
i/getChengpinDetails.php").then(
data =>{
console.log(data)
throw new Error('test');
},
error =>{
console.log("error"+error);
}
).catch(err =>{
console.log("catch"+err);
})
getJSON("http://iwenwiki.com/api/blueberryp
ai/getChengpinDetails.php").then(
data =>{
console.log(data)
},
error =>{
console.log("error"+error);
}
).catch(err =>{
console.log("catch"+err);
}).finally(() =>{
console.log("成功失败都会执行");
})
var url1 =
"http://iwenwiki.com/api/musicimg/1.png"
var url2 =
"http://iwenwiki.com/api/musicimg/2.png"
var promise1 = new Promise(function
(resolve, reject) {
var image = new Image();
image.src = url1;
image.onload = function () {
resolve(image)
}
image.onerror = function () {
reject(new Error('Could not load image at ' + url1))
}
})
var promise2 = new Promise(function
(resolve, reject) {
var image = new Image();
image.src = url2;
image.onload = function () {
resolve(image)
}
image.onerror = function () {
reject(new Error('Could not load image at ' + url2))
}
})
Promise.all([promise1,promise2]).then(data =>{
console.log(data);
}).catch(error =>{
console.log(error);
})
$.getJSON("http://localhost/generator/list.php",function(data){
$.getJSON("http://iwenwiki.com/api/generator/id.php",{id:data[0]},function(data){
$.getJSON("http://iwenwiki.com/api/generator/name.php",{name:data.name},function(data){
console.log(data);
})
})
})
function getIds(){
return new Promise(function(resolve,reject){
$.getJSON("http://iwenwiki.com/generator/list.php",function(data){
resolve(data)
},function(error){
reject(error)
})
})
}
getIds().then(data =>{
return new Promise((resolve,reject) =>{
$.getJSON("http://iwenwiki.com/api/generator/id.php", { id: data[0] },function(data){
resolve(data)
},function(error){
reject(error)
})
})
}).then(data =>{
return new Promise((resolve,reject) =>{
$.getJSON("http://iwenwiki.com/api/generator/name.php", { name: data.name
},function(data){
resolve(data)
},function(error){
reject(error)
})
})
}).then(data =>{
console.log(data);
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jquery-3.6.0.min.js"></script>
</head>
<body>
<script>
// function getAjax(url) {
// return new Promise((resolve, reject) => {
// //网络请求
// const client = new XMLHttpRequest();
// client.open("get", url);
// client.responseType = "json";
// client.send();
// client.onreadystatechange = function () {
// if (client.readyState === 4) {
// if (client.status === 200) {
// resolve(client.response);
// } else {
// reject(new Error(client.statusText));
// }
// }
// }
// })
// }
// getAjax("http://iwenwiki.com/api/blueberrypai/getChengpinInfo.php")
// .then(res => {
// console.log(res);
// return res.chengpinInfo;
// // throw new Error('test');
// }, error => {
// console.log(error);
// }).catch(err => {
// // 上一个then方法发生错误会触发
// console.log(err);
// }).finally(() => {
// console.log("不管是否发生错误,我都会执行");
// })
</script>
<script>
// 回调地狱:不停的嵌套回调函数
// $.getJSON("http://iwenwiki.com/api/generator/list.php",function(data){
// $.getJSON("http://iwenwiki.com/api/generator/id.php?id="+data[0],function(data){
// $.getJSON("http://iwenwiki.com/api/generator/name.php?name="+data.name,function(data){
// console.log(data);
// })
// })
// })
function getIds() {
return new Promise((resolve, reject) => {
$.getJSON("http://iwenwiki.com/api/generator/list.php", function (data) {
resolve(data)
}, function (error) {
reject(error)
})
})
}
getIds().then(res => {
return new Promise((resolve, reject) => {
$.getJSON("http://iwenwiki.com/api/generator/id.php?id=" + res[0], function (data) {
resolve(data)
}, function (error) {
reject(error)
})
})
}).then(data => {
return new Promise((resolve, reject) => {
$.getJSON("http://iwenwiki.com/api/generator/name.php?name=" + data.name, function (data) {
resolve(data)
}, function (error) {
reject(error)
})
})
}).then(data => {
console.log(data); // 最终结果了
})
</script>
</body>
</html>
Generator 函数的语法
<script>
// Generator 函数
// 星号总是放在function后面
// function* getInfo(){
// yield "hello"
// yield "world"
// return "end"
// }
// var info = getInfo();
// console.log(info.next()); // {value: 'hello', done: false}
// console.log(info.next()); // {value: 'world', done: false}
// console.log(info.next()); // {value: 'end', done: true}
function* foo() {
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for (let i of foo()) {
console.log(i); // 1 2 3 4 5 没有6
}
</script>
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
function* foo() {
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for (let i of foo()) {
console.log(i); // 1 2 3 4 5 没有6
}
<script>
// $.getJSON("http://iwenwiki.com/api/generator/list.php", function (data) {
// $.getJSON("http://iwenwiki.com/api/generator/id.php", { id: data[0] }, function (data) {
// $.getJSON("http://iwenwiki.com/api/generator/name.php", { name: data.name }, function (data) {
// console.log(data);
// })
// })
// })
// 把一个异步的操作变成一个同步的操作,但是实质上还是异步
function ajax(url) {
$.getJSON(url, function (data) {
info.next(data)
})
}
function* getInfo() {
var ids = yield ajax("http://iwenwiki.com/api/generator/list.php");
var names = yield ajax("http://iwenwiki.com/api/generator/id.php?id=" + ids[0]);
var infos = yield ajax("http://iwenwiki.com/api/generator/name.php?name=" + names.name)
console.log(infos);
}
var info = getInfo();
info.next()
</script>
Async 函数
function print(){
setTimeout(() =>{
console.log("定时器");
},1000)
console.log("Hello");
}
print()
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jquery-3.6.0.min.js"></script>
</head>
<body>
<script>
// 把一个异步的操作变成一个同步的操作,但是实质上还是异步
// function ajax(url){
// $.getJSON(url,function(data){
// info.next(data)
// })
// }
// function* getInfo(){
// var ids = yield ajax("http://iwenwiki.com/api/generator/list.php");
// var names = yield ajax("http://iwenwiki.com/api/generator/id.php?id="+ids[0]);
// var infos = yield ajax("http://iwenwiki.com/api/generator/name.php?name="+names.name)
// console.log(infos);
// }
// var info = getInfo();
// info.next()
// 接下来我们要写的才是日后我们真正要写的代码业务
function ajax(url) {
return new Promise((resolve, reject) => {
$.getJSON(url, function (data) {
resolve(data)
}, function (error) {
reject(error)
})
})
}
async function getInfo() {
var ids = await ajax("http://iwenwiki.com/api/generator/list.php")
var names = await ajax("http://iwenwiki.com/api/generator/id.php?id=" + ids[0])
var infos = await ajax("http://iwenwiki.com/api/generator/name.php?name=" + names.name)
console.log(infos);
}
getInfo();
// promise: 优势在于then,解决了回调函数问题
// Generator: 让异步以同步的方式处理
// async: 让异步以同步的方式处理,优势,代码的可读性更好了
// async + promise得到更优质的开发体验
// 回调函数 事件处理 Promise Generator async
</script>
</body>
</html>
Fetch API
- fetch() 使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁
- fetch() 采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码
- fetch() 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来
fetch(url)
.then(...)
.catch(...)
fetch('http://iwenwiki.com/api/blueberrypai/g
etChengpinDetails.php')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log('Request
Failed', err));
// fetch默认是get请求方式 post请求方式
fetch("http://iwenwiki.com/api/blueberrypai/login.php", {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
// post请求的参数
// 接受的参数类型其实和后台也有关系,但是大部分情况下是字符串形式
body: "user_id=iwen@qq.com&password=iwen123&verification_code=crfvw"
}).then(res => {
return res.json();
}).then(data => {
console.log(data);
}).catch(error => {
console.log(error);
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// fetch("http://iwenwiki.com/api/blueberrypai/getBlueBerryJamInfo.php").then(res =>{
// return res.json(); // 我要在相应对象获取json格式的数据
// }).then(data =>{
// console.log(data); // 最终的数据
// }).catch(err =>{
// console.log(err);
// })
// fetch默认是get请求方式 post请求方式
fetch("http://iwenwiki.com/api/blueberrypai/login.php", {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
// post请求的参数
// 接受的参数类型其实和后台也有关系,但是大部分情况下是字符串形式
body: "user_id=iwen@qq.com&password=iwen123&verification_code=crfvw"
}).then(res => {
return res.json();
}).then(data => {
console.log(data);
}).catch(error => {
console.log(error);
})
</script>
</body>
</html>
Fetch API POST请求注意事项
<script>
function formator(data) {
var dataStr = ""
Object.keys(data).forEach(key => {
dataStr += key + "=" + data[key] + "&"
})
// 去掉&符号
if (dataStr) {
return dataStr.substr(0, dataStr.lastIndexOf('&'));
}
}
function postFetch(url, data) {
return new Promise((resolve, reject) => {
fetch(url, {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
// data的对象格式转换成字符串格式
// user_id=iwen@qq.com&password=iwen123&verification_code=crfvw
body: formator(data)
}).then(res => {
return res.json()
}).then(data => {
resolve(data)
}).catch(error => {
reject(error);
})
})
}
postFetch("http://iwenwiki.com/api/blueberrypai/login.php", {
user_id: "iwen@qq.com",
password: "iwen123",
verification_code: "crfvw"
}).then(res => {
console.log(res);
}).catch(error => {
console.log(error);
})
</script>
Fetch网络请求应用
<script>
function ajax(url) {
return new Promise((resolve, reject) => {
fetch(url).then(res => {
return res.json();
}).then(data => {
resolve(data)
}).catch(error => {
reject(error)
})
})
}
async function getInfo() {
var ids = await ajax("http://iwenwiki.com/api/generator/list.php")
var names = await ajax("http://iwenwiki.com/api/generator/id.php?id=" + ids[0])
var infos = await ajax("http://iwenwiki.com/api/generator/name.php?name=" + names.name)
return infos
}
getInfo().then(res => {
console.log(res);
}).catch(error => {
console.log(error);
})
</script>
封装Fetch网络请求
<script src="./fetch.js"></script>
<script>
ajax("http://iwenwiki.com/api/blueberrypai/getBlueBerryJamInfo.php","GET")
.then(res =>{
console.log(res);
})
ajax("http://iwenwiki.com/api/blueberrypai/login.php","POST",{
user_id: "iwen@qq.com",
password: "iwen123",
verification_code: "crfvw"
}).then(res =>{
console.log(res);
})
</script>
fetch.js
function formator(data){
var dataStr = ""
Object.keys(data).forEach(key => {
dataStr += key + '=' + data[key] + '&';
})
if (dataStr !== '') {
dataStr = dataStr.substring(0, dataStr.length - 1);
}
return dataStr
}
async function ajax(url = "",type = "GET",data = {}){
// GET请求 url?username=iwen&password=123
if(type === "GET"){
var dataStr = formator(data);
url = url +"?"+ dataStr;
}
let requestConfig = {
method:type,
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
}
}
// POST请求
if(type === "POST"){
requestConfig.body = formator(data);
// 拦截对象,为对象增加新的属性
// 在ES6中,我们不推荐直接为一个对象增加新属性的时候使用赋值方式
// Object.defineProperty(requestConfig,"body",{
// value:formator(data)
// })
}
let response = await fetch(url,requestConfig)
response = await response.json();
return response;
}
Class 的基本语法
类的由来
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。
Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法。
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致
class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar();
b.doStuff() // "stuff"
构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
constructor 方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
上面代码中,定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor方法。
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
类的实例
生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
取值函数(getter)和存值函数(setter)
与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
静态属性
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
私有方法和私有属性
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
一种做法是在命名上加以区别
class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
Class 的继承
Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多
class Point {
}
class ColorPoint extends Point {
}
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。
另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
super 关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
Module 的语法
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// ES6模块
import { stat, exists, readFile } from 'fs';
export 命令
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
export的写法,除了像上面这样,还有另外一种。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
export命令除了输出变量,还可以输出函数或类(class)。
export function multiply(x, y) {
return x * y;
};
import 命令
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
下面是一个circle.js文件,它输出两个方法area和circumference
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
现在,加载这个模块
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
上面写法是逐一指定要加载的方法,整体加载的写法如下
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default 命令
从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
上面代码是一个模块文件export-default.js,它的默认输出是一个函数。
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'