ES6 就是 JavaScript 的打脸史
// ES3 语法
a = 1
var a = 1
// ES6 语法
let a = 1
const a = 1
a = 1
属于哪个对象,取决于运行环境(上下文)。
只想暴露一个全局变量,块({}
),C、Java 和 PHP 中都有,但是 JavaScript ES6 之前没有。
// ES6 之前
(function (){
var a = 1
window.JonathanBen = function(){
console.log(a)
}
}())
JonathanBen() // 1
console.log(a) // Uncaught ReferenceError: a is not defined
// ES6 之后
{
let a = 1
window.JonathanBen = function(){
console.log(a)
}
}
JonathanBen() // 1
console.log(a) // Uncaught ReferenceError: a is not defined
临时死区:直接使用还没定义的变量,下图蓝色部分就是临时死区。
let 和 const 总结:
let部分:
1 let 的作用域在最近的 {}
之间
2 如果在定义该变量前,使用该变量,会报错(临时死区)。
3 如果重复申明变量,会报错
const部分:
1 2 3 同上
4 在定义时并赋值只有一次(只有一次赋值机会)。
代码执行时机(守株待兔还是刻舟求剑)
var i
for(i=0; i<6; i++){
function fn(){
console.log(i)
}
xxx // 会执行 fn 函数
}
console.log(i) // 6
常见的执行形式xxx = fn()
(守株待兔)
var i
for(i=0; i<6; i++){
function fn(){
console.log(i) // 打印:0, 1, 2, 3, 4, 5
}
fn() // 会执行 fn 函数
}
console.log(i) // 6
通过点击事件触发,示例代码。(刻舟求剑)
点击事件触发时,for 循环已经结束,导致 i = 6,然后触发一次点击事件,打印一次6。
var i
for(i=0; i<6; i++){
function fn(){
console.log(i) // 打印:6
}
btn.onclick = fn // 通过点击触发 fn 函数
}
console.log(i) // 6
面试题:
快速生成 6 个导航ul>li{导航$}*6
点击每一个都打印 6。代码
现在需要点击哪个导航就需要打印该导航的编号。
ES6 之前语法(立即执行函数)代码
ES6 之后语法(let 解决)代码
ES6 之后语法中关于 let 详解:
for (let i = 0; i < liTags.length; i++) {
liTags[i].onclick = function () {
console.log(i)
}
}
// 等价于
for (let i = 0; i < liTags.length; i++) {
let i = i // i0, i1, i2, i3, i4, i5
liTags[i].onclick = function () {
console.log(i)
}
}
上面代码等价于后部分,是前部分的相信分解:
首先明确for()
中的let i
和for(){}
中的let i
是没有任何关系的。
for()
中的let i
的作用域就只是括号里面部分(紫色选中部分)。
所以这里JavaScript内部隐式的使用了let i = i
,来将()
中的i
的值,赋值到{}
中的的i
。
上图中出现了 7 次i
,for()
中 1 次,和for(){}
中 6 次。
疑问:既然说for()
中的let的作用域只是在上图紫色选中部分,那么怎么传值到for(){}
中呢?神奇的魔法
答:
在 for 循环中实际上是有两个作用域的,条件设置的圆括号 ()
内是一个父作用域,而代码块大括号{}
中是一个子作用域。
ES3 :
// 具名函数
function xxx(p1, p2){
console.log(1)
return 2
}
// 匿名函数
let xxx = function(p1, p2){
console.log(1)
return 2
}
ES6:
let xxx = (p1, p2)=>{
console.log(1)
return 2
}
// 当只有一个参数时,可以省略()
let xxx = p1 => {
console.log(1)
return 2
}
// 当只有一个函数体,可以省略 {},同时省略了 return
let xxx (p1, p2) => p1 + p2
this 是 call 的第一个参数
this 太难用了,ES 3 支持 this,ES 6 也支持 this,但是用箭头函数弱化了 this 的用法。
类比 python 类中的 self ,只不过 python 是在函数参数中显式出来的,而 JavaScript 中是隐式在函数参数中。
上图中,init 函数中调用了,this.onClick 函数,然后 onClick 中的 this 实际上是 C #app 元素,是JQuery作者自己指定的(需要看源码或者文档才能知道)。不要想当然认为是 this 是 controller。
因此,上图中的绿框 this 指的是 #app 元素。但是我们想实现 this 指向 controller 对象然后可以调用 getUsers 函数。解决方案之一如下:
用 self 保留 controller 然后调用 onClick 函数,这样 onClick 函数中的this 就是指向 controller 对象。
解决方法之二,用箭头函数:箭头函数没有 this 的概念。
验证箭头函数没有 this:
function f1(){
console.log(this)
}
f1.call({name: 'Jonathan Ben'}) // {name: "Jonathan Ben"}
let f2 = () => {console.log(this)}
f2.call({name: 'Jonathan Ben'})
// Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
用箭头函数获取 name,因为箭头函数没有 this,只能自己传参了。
let obj = {
name: 'Jonathan Ben',
hi: (self)=>{
console.log(self.name)
}
}
obj.hi(obj)
箭头函数是很纯粹的函数,之前的函数有’潜规则’
function fn(){
}
// 可以如下理解
function fn(this){
let this = arguments[-1]
}
JavaScript 没有借鉴 CoffeeScript 箭头函数之前,导致用户流逝,所以后面 JavaScript 加入了胖箭头函数=>
。
没有借鉴 CoffeeScript 的瘦箭头。
JavaScript 在对象中简化函数语法糖(ES 6):
var obj = {
name: 'Jonathan Ben',
hi: function(){
console.log(this.name)
}
}
// 函数简化为
var obj = {
name: 'Jonathan Ben',
hi(){
console.log(this.name)
}
}
也没有借鉴?
JavaScript 中
res = null
if(res && res.data && res.data.user){
console.log(res.data.user)
}
CoffeeScript 中
console.log(res?.data?.user)
vue 中,方法第一次不要用箭头函数,其他层都可以,因为用了箭头函数就永不了this.data
面试题:
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log("outer func: this.foo = " + this.foo);
console.log("outer func: self.foo = " + self.foo);
(function() {
console.log("inner func: this.foo = " + this.foo);
console.log("inner func: self.foo = " + self.foo);
}());
}
};
myObject.func();
// outer func: this.foo = bar
// outer func: self.foo = bar
// inner func: this.foo = undefined
// inner func: self.foo = bar
(1)函数的默认参数
function sum(a, b){
return a + b
}
sum(1, ) // NaN
// ES 5 参数保底值写法
function sum1(a, b){
a = a || 0
b = b || 1
return a + b
}
sum1(2) // 3
// ES 6 语法糖
function sum2(a=0, b=1){
return a + b
}
sum2(3) // 4
与 Python 的区别:默认参数是一个数组时,JavaScript 每次调用函数都会创建一个新的数组,而 Python 则不会。
JavaScript:
function push(item, array=[]){
array.push(item)
return array
}
console.log(push(1)) // [1]
console.log(push(2)) // [2]
Python:
def push(item, array=[]):
array.append(item)
return array
print(push(1)) # [1]
print(push(2)) # [1, 2]
(2)剩余参数
function sum(message){
let result = 0
for(let i=1; i<arguments.length; i++){
result += arguments[i]
}
return message + result
}
console.log(sum("结果是", 1, 2, 3, 4, 5)) // 结果是15
function sum1(message){
// arguments 伪数组转化为真数组
// ES 5最好写法
let args = Array.prototype.slice.call(arguments)
// ES 6 写法,两者等价
// let args = Array.from(arguments)
// let args = [...arguments]
let numbers = args.slice(1)
result = numbers.reduce((p, v)=> p+v, 0)
return message + result
}
console.log(sum1("结果是", 1, 2, 3, 4, 5, 6)) // 结果是21
function sum2(message, ...numbers){
result = numbers.reduce((p, v)=> p+v, 0)
return message + result
}
console.log(sum2("结果是", 1, 2, 3, 4, 5, 6, 7)) // 结果是28
reduce解释
(3)展开操作
let array1 =[1, 2, 3, 4]
// 数组拼接
// ES 5 语法
let array2 = [0].concat(array1).concat([7])
console.log(array2) // [0, 1, 2, 3, 4, 7]
// ES 6 语法
array2 = [0, ...array1, 7]
console.log(array2) // [0, 1, 2, 3, 4, 7]
(4)析构赋值
1)[a, b] = [b, a]
不加分号的坑!!!需要看阮大的不加分号的情况。
2)[a, b, ...rest] = [10, 20, 30, 40, 50]
3)let {name, age} = frank
var person = {name: 'Jonathan Ben', age: 24}
// ES 5 语法
var name = person.name
var age = person.age
// ES 6 语法
// 直观理解 var {name, age} = {name: 'Jonathan Ben', age: 24} 匹配对应
var {name, age} = person
4)[a=5, b=7] = [1]
:类似函数默认值
5)[a, b] = f()
,[a, , b] = f()
:赋值函数返回值
6){p: foo, q: bar} = o
7)let {a = 10, b = 5} = {a: 3}
,let {a:aa = 10, b:bb = 5} = {a: 3}
var person = {
name2: 'Jonathan Ben',
age: 24,
person2: {
age: 64
}
}
// 将属性 name2 修改为别名 mingzi
// 注:window 本身有个 name,因此这里要用 name2
var {name2: mingzi, age} = person
console.log(mingzi) // Jonathan Ben
var {person2:{name2:mingzi='Jonathan Lee', age}} = person
console.log(mingzi) // Jonathan Lee
console.log(name2) // Uncaught ReferenceError: name2 is not defined
8)对象浅拷贝
let objA = {
p1: 1,
p2: 2
}
let objC = {
p1: 1111,
p3: 3
}
// 都是 ES 6 语法
let objB = Object.assign({}, objA, objC)
console.log(objB) // {p1: 1111, p2: 2, p3: 3}
objB = {...objA, ...objC}
console.log(objB) // {p1: 1111, p2: 2, p3: 3}
补充:没有绝对的深拷贝
深拷贝目前最优:JSON.parse(JSON.stringify(data))
JSON.parse(JSON.stringify(data))
完全深拷贝的前提:
undefined
(JSON不支持)9)对象合并
MDN 上更多的例子
(5)对象属性加强
1)obj = { x, y }
右边
{x, y}
不是结构,结构一般是在=
左边。
var [x, y] = [1, 2]
// ES 5 语法
var obj = {
x:x,
y:y
}
// ES 6 语法糖
var obj = {
x,
y
}
2)obj = {["baz" + quux() ]: 42}
定义的时候就可以给对象赋值变量了,使用[]
。
function quux(){
return "quux"
}
var obj = {
["baz" + quux()]: 20
}
console.log(obj)
3)函数属性可以缩写
// ES 5 语法糖
var obj = {
sayHi: function(){}
}
// ES 6 语法糖
var obj = {
sayHi(){}
}
(1)换行
// ES 5 定义时,可换行 2 种方式
var string = "\
string string
\
" // 结果没有换行符
console.log(string) // string string
var string1 = "" +
" string string
" +
"" // 结果没有换行符
console.log(string1) // string string
// ES 6 换行语法糖
var string2 = `
string string
`
console.log(string2)
// 输出结果 4 个回车
//
//
// string string
//
(2)反引号支持字符串插入变量(插值)
var name = "Jonathan Ben"
var string = `${name} 是个好人`
console.log(string) // Jonathan Ben 是个好人
(3)函数接字符串
var name = "Jonathan Ben"
var fn = function(){
let strings = arguments[0]
let var1 = arguments[1]
if(var1 === 'Jonathan Ben'){
return var1 + strings[1] + '好人'
}else{
return var1 + strings[1] + '坏人'
}
}
fn`${name} 是一个 ${person}`
目前在 React 中 styled-component 已经应用了:
h1
是一个函数接字符串
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
字面量(literal)就是字面直接理解的表达。比如`'hello’等。
非字面量:new Object()
不太理解
(1)进制字面量增强
更安全的二进制字面量 Ob11111001
和更安全的八进制字面量Oo767
目的是为了更加容易区分。
// 八进制
// ES 5
0777 // 511
0888 // 888
// 0开头是八进制,但是没有8,所以自动转为十进制了
// ES 6 更容易区分
0o777 // 511
(2)字符串支持Unicode
String.fromCodePoint
String.prototype.codePointAt
阮一峰 Unicode与JavaScript详解
阮一峰 字符编码笔记:ASCII,Unicode 和 UTF-8
JavaScript 考虑兼容性,会一直保留之前不好的地方。
(3)正则
正则表达式支持字添加 Unicode;添加y标记,支持粘滞匹配。
(1)Symbol
简单来说,就是一个你具体不清楚,但是唯一存在的字符串。
方应杭 JS中的Symbol是什么?
(2)迭代器
遍历:有限的数据集的一个个访问,数组变量。
迭代:版本迭代,无限的,若存在下一个版本就迭代。
生成器:暂时用得少,自己开发东西的时候,应该就会用到了。
function 发布器(){
var _value = 0
var max = 3
return {
next: function(){
_value += 1
if(_value > max){ throw new Error('没有下一个了!')}
if(_value === max){
return {
value: _value,
done:true
}
}else{
return {
value: _value,
done: false
}
}
}
}
}
var a = 发布器()
a.next() // {value: 1, done: false}
a.next() // {value: 2, done: false}
a.next() // {value: 3, done: true}
a.next() // Uncaught Error: 没有下一个了!
// ES 6 之前
var a = new Object()
var b = {}
// ES 6
var c = Object.create(null)
var d = Object.create(Object.prototype) //等价于前两种创建对象
console.log(a)
// {}
// __proto__: Object
console.log(b)
// {}
// __proto__: Object
console.log(c)
// {}
console.log(d)
// {}
// __proto__: Object
ECMAScript 2015 就是 ES 6。
一个对象中,定义了相同属性名,后面定义的会覆盖前面定义的。
var obj = {
a: 1,
b: 2,
a: 3
}
console.log(obj) // {a: 3, b: 2}
属性名和属性值的变量名相同,则可以简写
var a = 11
var b = 22
var obj = {
a: a,
b: b
}
console.log(obj) // {a: 11, b: 22}
// ES 6 简写
var obj = {
a,
b
}
console.log(obj) // {a: 11, b: 22}
严格模式(ES 6之前的知识)一般用的很少!!!
ES 6 之前,只能先初始化后变量赋值;ES 6 支持初始化时,变量赋值。
var name = 'xingming'
// ES 6 之前
var obj = {}
obj[name] = 'Jonathan Ben'
console.log(obj) // {xingming: "Jonathan Ben"}
// ES 6
var obj2 = {
[name]: 'Jonathan Ben2'
}
console.log(obj2) // {xingming: "Jonathan Ben2"}
对象属性也可以是一个函数、getter 和 setter
getter的一个’奇怪’的应用现象
var i = 0
Object.defineProperty(window, 'a',{
get(){
i+=1
return i
}
})
a === 1 && a ===2 && a === 3
误区,以下代码不是浅拷贝,而是将变量指向同一个对象,改变其中一个变量的对象属性,另一个也会改变。
var obj = {a:1, b:2, c:3}
var obj2 = obj
obj2.a = 100
console.log(obj2) // {a: 100, b: 2, c: 3}
console.log(obj) // {a: 100, b: 2, c: 3}
浅拷贝的几种方法
var obj = {a:1, b:2, c:3}
// ES 5
var obj1 = {}
for(let key in obj){
obj1[key] = obj[key]
}
obj1.a = 10
console.log(obj1) // {a: 10, b: 2, c: 3}
// ES 6
var obj2 = Object.assign({}, obj)
var obj3 = {...obj}
obj2.a = 100
console.log(obj2) // {a: 100, b: 2, c: 3}
obj3.a = 1000
console.log(obj3) // {a: 1000, b: 2, c: 3}
console.log(obj) // {a: 1, b: 2, c: 3}
推荐写法:Object.getPrototypeOf(a)
,不推荐写法:a.__proto__
JSON是一门存储数据的语言(抄袭JavaScript),没有undefined
和函数,JavaScript的undefined
就是一个 bug。
使用Object.defineProperty()
在旧的对象上添加自定义属性,自定义的功能都是JavaScript的坑造成的。
undefined
不是关键字(判断关键字方法:关键字是不可以作为变量的),是一个不可修改的变量。
undefined
是 window
上的一个只读属性。
JavaScript 是一门很烂的语言,所有出现一些奇怪的现象见怪不怪。
关于限制对象属性写的属性描述符可以通过get
或者writable
两种方式来实现。
var obj = {
get name(){
return 'Jonathan Ben'
}
}
Object.defineProperty(obj, 'name2', {
writable: 'Jonathan Ben'
})
obj
绿框部分直观来看像是obj
对象的一个属性,但是直接可以从代码定义部分可以看出,name
属性是虚拟出来,实际还是指向get name
部分。
configurable
enumerable
Vue 中的 data
和页面中的 message
如何做到双向绑定呢?
Vue官网链接有所介绍
使用 Symbol 定义一个对象,但是用到的极少。Symbol 目前用的最多的地方就是迭代器。
var s = Symbol()
var obj = {
[s]: 2
}
obj[s] // 2
console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()]
注:访问 Symbol 定义的的属性,直接使用Object.keys
显示不出来,需要使用getOwnPropertySymbols
。
ES 6特性有很多,记住常用的,遇到不会的临时抱佛脚。
模块化就是逻辑上(不局限于某种形式)将代码进行分开。
发现:函数表达式可以使用 let
,如果使用 function 形式定义函数则默认就是 var
。
import defaultExport from "module-name"; // 默认导入
import * as name from "module-name"; // 全部导入并命名为 name
import { export } from "module-name"; // 导入需要的部分
import { export as alias } from "module-name"; // 导入需要的部分并命名为 alias
import { export1 , export2 } from "module-name"; // 导入需要的多个部分
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name"; // 导入需要的多个部分,部分用别名
import defaultExport, { export [ , [...] ] } from "module-name"; // 导入默认的部分和需要的多个部分
import defaultExport, * as name from "module-name"; // 导入默认的部分和需要的多个部分,部分用别名
import "module-name"; // 仅仅导入,执行代码
仅为副作用而导入一个模块:
整个模块仅为副作用(中性词,无贬义含义)而导入,而不导入模块中的任何内容(接口)。 这将运行模块中的全局代码, 但实际上不导入任何值。
import "module-name"
export 用法:
导出暂时这3中够用了,最多的就是前两种(可同时使用)。
export { name1, name2, …, nameN }; // 导出列表
export default expression; // 默认导出
export * from …; // // 导出模块合集
如果要兼容所有设备支持 import
和 export
语法。需要通过工具(babel webpack parcel)来翻译使其兼容使用的设备。
Branden Eich 在 1995 年参考 scheme(基于函数式)语言和 self(基于原型)发明了 JavaScript 。
同时他很讨厌 Java,公司又让他蹭 Java 的热度。
全局对象 window
为什么创建的对象和数组使用 toString()
得到的结果不同?
答:
牵扯到this
相关知识,相当于显示传递,实际是调用了call方法,把toString()
前面的对象传给了原型,因此可以判断传的是什么。
object.toString.call(object)
用 java 来实现的对面对象编程的书
什么是回调?
函数 A 在函数 B 中的调用就是回调。
把函数A传给函数B调用,那么A就是回调函数。
标准的回调:
function 获取用户信息(fn){
fn('姓名: Jonathan Ben')
}
function 打印用户信息(用户信息){
console.log('这是我打印的用户信息')
console.log(用户信息)
}
获取用户信息(打印用户信息)
匿名回调:
function 获取用户信息(fn){
fn('姓名: Jonathan Ben')
}
获取用户信息(function (用户信息){
console.log('这是我打印的用户信息')
console.log(用户信息)
})
函数不光可以通过 return 返回结果,也可以通过回调返回。
回调超过5层就很难理解,代码怎么定义的了。
为了解决回调地狱问题,前端发明了Promise
回调的缺点:
1 回调地狱
2 不知道怎么使用 Node/JQuery
每一套都需要单独记忆
// Node.js
// 通过函数第一个参数 error 判断成功和失败
readFile('C:\\1.txt', function (error, data){
if(error){
console.log('成功')
console.log(data.toString())
}else{
console.log('读取文件失败')
}
})
// JQuery
// 传一个对象里面有 success 和 error 函数,然后判断成功和失败
$.ajax({
url: '/2.txt',
success: function (response){
console.log('成功')
},
error: function (){
console.log('失败')
}
})
then中规定,第一个参数写成功部分, 第二个参数写失败部分。
readFilePromie('C:\\1.txt')
.then(function(){}, function (){})
简单示例:
function 获取用户信息(name) {
return new Promise(function(resolve, reject){
if(name === 'Jonathan Ben'){
console.log('我认识')
resolve('是个胖子')
}else{
console.log('不认识')
reject()
}
})
}
获取用户信息('Jonathan B1en')
.then(
function (d) {console.log(d)},
function (){console.log('看来不认识呀')}
Web 服务的客户端:
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
console.dir(employees)
});
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl + '/' + propKey);
}
});
}
function httpGet(url){
return new Promise(function(resolve, reject){
resolve('{"result":true, "count":42, "desc": "这是一个伪造的 url 的get 请求返回的 JSON 对象"}')
})
}
这一部分讲的内容都是不常用的内容。
Symbol 前面不能加 new
Symbol 翻译为符号
Symbol 是唯一的
Symbol 可以作为对象的属性
Symbol 属于基本数据类型,Number、String、Boolean、Symbol、null、undefined(六种)
Object 是一种复杂数据类型
简单理解:Symbol 就是全局唯一字符串
唯一用法作为对象的属性的标识符
ES6之前做不到对象属性隐藏。
当面试官问Symbol有什么用的时候一定要举这个栗子。
在块级作用域中,用 Symbol 可以创建一个隐藏属性(私有属性)
{
let a = Symbol()
let obj = {
name: "Jonathen Ben",
age: 24,
[a]: '隐藏属性'
}
console.log(obj[a]) // 只能块内访问
window.object = obj
}
console.log(object)
object.name
object.age
// 块外访问不到 隐藏属性
面试题:如何给数组去重
Set 是去重后的数组。可以对数字,字符串,null,undefined,对象都可以去重。
不用 Set 怎么去重,但是只能简单实现一个去重数组。
function unique(array){
let hash = {}
let result = []
for(let i=0; i< array.length; i++){
hash[array[i]] = true
}
for(let key in hash){
result.push(key)
}
return result
}
console.log(unique([1, 2, 3, 3, 3]))
缺点:
1 无法区分数字和字符串
2 无法区分对象
因为 JavaScript 中对象只支持字符串标识符
map 对象保存键值对,最大改进就是:相比较Plain Object 的键只能用字符串,map可以任意值都可以作为一个键或值。
set 设置
get 获取
Object.key获取不到信息
需要for…of
然后又有3修饰符
for(let i of map.entries()){} // 默认方式
for(let i of map.keys()){}
for(let i of map.values()){}
数组和 Set 中放了对象设计到垃圾回收(Garbage Collection),对象占内存。
GC:找出开发者访问不到的对象,进行回收。
弱引用不属于GC计算的范围,所以浏览器很可能吧弱引用的东西删掉。
Set 和 WeakSet的区别:
WeakSet 是弱引用,且不能告诉所有的引用(相比 Set 没有 entries 方法),因为可能会随时消失。
因为是弱引用,所有 key 必须是对象,值任意都行
类似 WeakMap 效果。
二进制文件处理(音频、视频、图片以及文件的处理)。除非你自己开发一个轮子,才用的上。
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。
浅拷贝和深拷贝
浅拷贝只拷贝 stack 区,深拷贝是 stack 区和 heap 区都拷贝
因为不知道完美的深拷贝需要拷贝哪些东西,所以一般说拷贝都是浅拷贝。
Object.assign
拷贝示例:
let a = {
a1: 'a',
a2: 2
}
let b = {
obj:{
name: 'b'
}
}
Object.assign(a, b)
a.obj.name = 'c'
console.log(a.obj.name) // c
console.log(b.obj.name) // c
toString
、__proto__
都是不可枚举的
创建属性的两个方式对属性定义的默认状态的影响:
let a = {
a1: 'a1'
}
let b = {
get b1(){
return 'b1'
}
}
let c = {}
let d = {}
Object.defineProperty(c, 'c1', {
get(){
return 'c1'
}
})
Object.defineProperty(d, 'd1', {
value: 'd1'
})
console.log(Object.getOwnPropertyDescriptor(a, 'a1'))
console.log(Object.getOwnPropertyDescriptor(b, 'b1'))
console.log(Object.getOwnPropertyDescriptor(c, 'c1'))
console.log(Object.getOwnPropertyDescriptor(d, 'd1'))
可以看出直接创建属性,enumerable
和 configurable
的默认值都是 true
,通过 Object.defineProperty
来创建,默认值都是false
。
通过 Object.assign 可以复制一个对象:
const obj = {a: 1}
const copy = Object.assign({}, obj})
console.log(copy); // { a: 1 }
new Array(5)
生成的数组是没有对应下标的数组,因此不可以 map。
let arr = new Array(5)
console.log(arr)
let arr2 = Array.from({length:5})
// ES5的等价写法
// let arr2 = Array.apply(null, {length:5})
console.log(arr2)
方方遇到的面试题:创建一个填充字符串的指定长度的数组。
// ES6
function setArrES6(n, fill){
return Array.from({length: n}, item => fill)
}
// ES5
function setArrES5(n, fill){
return new Array(n+1).join(fill).split('')
}
但是 ES5 的代码有缺陷,就是 split 切分时,如果字符串是相同的字符,则都会被切开。
Array.find()
:只找到第一个就不往下找了
Array.filter()
:找到所有满足条件的。
let arr = [
{name: 'n1', age: 80},
{name: 'n1', age: 20},
{name: 'n1', age: 20}
]
console.log(arr.find(item => item.age === 20))
console.log(arr.filter(item => item.age === 20))
// { name: 'n1', age: 20 }
// [ { name: 'n1', age: 20 }, { name: 'n1', age: 20 } ]
Array.copyWithin
: 不常用
Array.entries
、Array.keys
、Array.values
都是可迭代对象,可以通过 next()
一个一个打出
功能不是很强大,大概知道意思就行。
ES6 之前,就可以解决这些问题了,只是ES6出了一个统一的接口。
String.prototype.includes()
let string = '12345'
// ES6
console.log(string.includes('12'))
// ES5
console.log(string.indexOf('12') > -1)
console.log(string.search(/2/) > -1)
String.prototype.repeat()
将字符串重复几次String.prototype.startsWith()
let string = '12345'
// ES6
console.log(string.startsWith('1'))
// ES5
console.log(string.indexOf('123') === 0)
String.prototype.endsWith()
let string = '12345'
// ES6
console.log(string.endsWith('5'))
// ES5
function endsWith(string, subString){
return string.lastIndexOf(subString) === string.length - subString.length
}
console.log(endsWith(string, '45'))
Number.EPSILON
表示 1 与 Number 可表示的大于 1 的最小的浮点数之间的差值。用途:
当 i
为1的时候可以 log 出来?
因为 JavaScript 的浮点精度问题会导致死循环,谨慎执行代码。
let i = 0;
while(i !== 1){
i += 0.1
}
console.log("i si 1")
解决方法:
let i = 0;
// ES 6
while(Math.abs(i - 1) < Number.EPSILON){
i += 0.1
}
// ES 5
while(Math.abs(i - 1) < 0.000001){
i += 0.1
}
console.log("i si 1")
Number.isInteger
let data = 1.0
// ES6
console.log(Number.isInteger(data))
// ES5
console.log(data === parseInt(data, 10))
Number.isSafeInteger
安全整数范围为 -(253 - 1)到 253 - 1 之间的整数,包含 -(253 - 1)和 253 - 1。
Number.isFinite
来检测传入的参数是否是一个有穷数
// JavaScirpt 中的 PI 是 长度为 17 的有效数字
console.log(Number.isFinite(Math.PI))
Number.isNaN
NaN 不是 JavaScript 定义的。
Math.acosh
返回一个数的反双曲余弦值
Math.hypot
返回所有参数的平方和的平方根
console.log(Math.hypot(3, 4)) // 5
Math.imul
该函数将两个参数分别转换为 32 位整数,相乘后返回 32 位结果,类似 C 语言的 32 位整数相乘。
Math.sign
函数返回一个数字的符号, 指示数字是正数,负数还是零(正负 0 ),不是数就返回 NaN。
Math.trunc
Math.trunc
和 parseInt
的区别:
parseInt
当输入的数值(22位及以上)需要转化为科学计数法的时候,会将数值科学技术法作为字符串,导致转为整型的过程中只截取 .
之前的一个数值。因此,为了解决这个问题,ES6 推出了 Math.trunc
。换句话来说 Math.trunc
是 parseInt
的升级版。
console.log(parseInt(123.1)) // 123
console.log(parseInt(80121230123120381029381390123098)) // 8
// '8.012123012312038e+31' 截取 . 前面的数字 8
反射打个比方就是,山和湖中山的倒影。特性①山和倒影是一模一样的,② 虚的。
如果 name
属性部署了读取函数(getter),则读取函数的 this
绑定 receiver
。
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
var myReceiverObject = {
foo: 4,
bar: 4,
};
Reflect.get(myObject, 'baz', myReceiverObject) // 8
等价于下述代码,使用 baz
函数然后通过 call
改变 this
(上述方法中 get 不能使用 call,会报错因此需要用 Reflect 来实现。)
var myObject = {
foo: 1,
bar: 2,
baz: function () {
return this.foo + this.bar;
},
};
var myReceiverObject = {
foo: 4,
bar: 4,
};
console.log(myObject.baz.call(myReceiverObject))
var myObject = {
foo: 4,
set bar(value) {
return this.foo = value;
},
};
var myReceiverObject = {
foo: 0,
};
Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1
// normal
let min = Math.min
console.log(min(1, 2, 3))
// ES5 apply
let min2 = (...args) => Math.min.apply(Math, args)
console.log(min2(1, 2, 3))
console.log(Math.min.apply(undefined, [1, 2, 3]))
// ES6 Reflect
console.log(Reflect.apply(Math.min, undefined, [1, 2, 3]))
function Greeting(name) {
this.name = name;
}
// new 的写法
const instance = new Greeting('张三');
// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);
第一种形式:
以上这几种 Reflect 的方法,对应对象的就是 target.xxx
(xxx
就是对应的名字),例如:taget.apply(thisArg, args)
第二种形式:
以上这几种 Reflect 的方法,对应对象的就是 Object.xxx
(xxx
就是对应的名字),例如:Object.defineProperty(target, name, desc)
第三种形式:没有对象对应的方式。
Proxy 简单用法:
let beiProxy = {}
let proxy = new Proxy(beiProxy, {
get: function (target, key){
console.log('target: ', target)
console.log('key: ', key)
return Reflect.get(target, key)
},
set: function (target, key, value){
console.log('target: ', target)
console.log('key: ', key)
console.log('value: ', value)
return Reflect.set(target, key, value)
},
})
proxy.name = 'Jonathan Ben'
console.log('************')
console.log(proxy)
console.log('------------')
console.log(beiProxy)
console.log('************')
console.log(proxy.name)
通过,Proxy 来控制 game 中的血条不能为负数。
// 直接将target 写入 Proxy参数, 因此是一个匿名对象,这样防止直接修改 target 对象本身。
let proxy = new Proxy({lives: 3}, {
get(target, name){
return Reflect.get(target, name)
},
set(target, name, value){
if(name === 'lives' && value < 0){
value = 0
}
return Reflect.set(target, name, value)
}
})
proxy.lives = 1
console.log(proxy.lives) // 1
proxy.lives = -1
console.log(proxy.lives) // 0
很多东西都可以用 Proxy 来做,但用 Proxy 来做自由度比较大。
实现一些不借助代理根本无法实现的功能。
无限代理Tree
tree.branch1.branch2.twig
如果是对象 tree
,想要创建 branch2
的属性 twig
。因为 branch
为 undefined
,会报错。
// TypeError: Cannot read property 'branch2' of undefined
let tree = {}
tree.branch1.branch2.twig = "green";
类似于存放文件的时候,但是路径中文件夹不存在这种感觉,需要先创建所需的文件夹,最后存储文件。
那么可以通过 Proxy 来快速实现。
function Tree() {
return new Proxy({}, handler);
}
let handler = {
get: function (target, key, receiver) {
if (!(key in target)) {
target[key] = Tree(); // 自动创建一个子树
}
return Reflect.get(target, key, receiver);
}
};
let tree = Tree();
console.log(tree) // {}
tree.branch1.branch2.twig = "green";
console.log(tree) // { branch1: { branch2: { twig: 'green' } } }
tree.branch1.branch3.twig = "yellow";
console.log(tree)
// {
// branch1: { branch2: { twig: 'green' }, branch3: { twig: 'yellow' } }
// }
Vue 2 中存在一个bug:没有添加绿色框中的代码(也就是预先定义好 age
),会导致点击按钮 “添加年龄” 后,不会显示 18
。因为Vue 2 中的 data
是通过 JavaScript 中对象的 defineProperty
方法中的 getter
和setter
来双向绑定的。既然 defineProperty
中对象的参数中没有捕获到 age
属性,那么就不会响应式的改变。因此,预先定义好 age
(相当于绑定了) ,就可以达到点击按钮显示年龄的效果。
为了解决这个问题,因此可以通过 Proxy 代理来解决。通过上面无限代理 tree 可以实现。
let data = {
obj: {
name: 'Jonathan Ben'
}
}
function crateProxy(target) {
return new Proxy(target, {
get: function (target, key, receiver) {
if (!(key in target)) {
target[key] = Tree() // 自动创建一个子树
}
return Reflect.get(target, key, receiver)
}
})
}
let proxy = crateProxy(data)
proxy.obj.age = 24
console.log(data)
摇色子 Promise
function 摇色子(){
return new Promise((resolve, reject)=>{
setTimeout(() => {
let n = parseInt(Math.random() * 6 + 1)
resolve(n)
}, 3000)
})
}
摇色子().then(
x => console.log("色子的点数是" + x),
() => console.log("摇失败了!!!")
)
async
就是为了标记,表示该函数是一个异步函数。
await
只能放到 async
函数中,同时 await
后面需要跟一个返回 Promise 的函数并调用。如果等待的不是 Promise 对象,则返回该值本身。
其实 async
和 await
像是 Promise
的语法糖
function 摇色子(){
return new Promise((resolve, reject)=>{
setTimeout(() => {
let n = parseInt(Math.random() * 6 + 1)
resolve(n)
}, 3000)
})
}
async function test(){
let n = await 摇色子()
console.log(n);
}
test()
如果有错误,则这里需要用 try...catch
来处理
function 赌大小(猜测){
return new Promise((resolve, reject)=>{
setTimeout(() => {
let n = parseInt(Math.random() * 6 + 1)
console.log('开始赌博了:')
if(n > 3 ){
if(猜测 === '大')
resolve(n)
else
reject(n)
}else{
if(猜测 === '小')
resolve(n)
else
reject(n)
}
}, 1000)
})
}
async function test(){
try{
let n = await 赌大小('大')
console.log('好嗨哦' + n);
}catch(error){
console.log('输光了' + error)
}
}
test()
如果一次来两个筛子赌大小?这里需要使用 Promise.all
来实现
function 赌大小(猜测) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let n = 1 //parseInt(Math.random() * 6 + 1, 10);
console.log("开始赌博了:");
if (n > 3) {
if (猜测 === "大"){
resolve(n);
}else{
console.log("error");
reject(n);
}
} else {
if (猜测 === "小"){
resolve(n);
} else{
console.log("error");
reject(n);
}
}
console.log('***')
}, 5000);
});
}
// 链式写法
Promise.all([赌大小("大"), 赌大小("大")])
.then(
(n) => console.log('执行成功了' + n),
(n) => console.log('执行失败了' + n)
)
// async await 写法
async function test() {
try {
let n = await Promise.all([赌大小("大"), 赌大小("大")]);
console.log("好嗨哦" + n);
} catch (error) {
console.log("输光了" + error);
}
}
test();