解构赋值
对于不是对象的变量会调用
ToObject()封装,null和undefine不能解构赋值
数组解构
是将数组的单元值 快速 批量 的赋值给一系列变量的简介语法
为了标记每个元素的意义
const arr = [100,10,1]
const max = arr[0]
const min = arr[2]
const avg = arr[1]
console.log(max)
console.log(min)
console.log(avg)
- 解构写法
const arr = [100,10,1]
const [max,min,avg] = arr
console.log(max)
console.log(min)
console.log(avg)
//这个写法和上面的传统写法是一个效果
- 交换变量
let a = 1
let b = 2
;[b,a] = [a,b]//注意这里有个分号
注意这里有个分号 立即执行函数也要加分号
单元值和变量不对应
const [a,b,c,d] = [1,2,3]
console.log(d)//undefined
const [a,b] = [1,2,3]
//3就会漏掉
用剩余变量来接
const[a,b,...c] = [1,2,3,4,5]
//c是一个真数组
防止undefine传递
//防止undefine传递
const [a = 0, b = 0 ] = [ ]
按需操作
//按需操作
const [a,b, , d] = [1,2,3,4]
多维数组
无脑可以嵌套解构
const arr = [1,2,[3,4]]
const [a,b,c] = arr
const [a,b,[c,d]]] = arr
对象解构
将属性和方法快速批量的赋值給变量的方法’
语法
属性名和变量名必须是一样的 所以要注意会不会和外面的变量冲突,可以更名
const obj = {
uname = 'pink',
age : 18
}
const {uname,age} = obj
//===const uname = obj.uname//pink
解构变量改名
oldname : newname
const {uname:username ,age} = obj
对象数组解构
const pig = [
{uname :'佩奇',
age:18
}]
const [{uname,age}] = pig
console.log(uname)
console.log(age)
多级对象解构
注意加上内对象的名字加:
const pig {
name :'pink',
family :{
mother:1,
father:2,
sister:3
},
age: 6
}
const {name,family: {mother,father,sister} } = pig
给已经声明的变量解构赋值
let personName,personAge
let person = {
name : 'matt',
age: 7
}
//要加一个括号全部括起来
({name:personName,age: personAge} = person)
函数进阶
函数声明
-
声明式
function FunName(){}
-
表达式
let FunName = function () {}- 其实就是声明一个匿名函数然后赋值给FunName
-
箭头函数式
let FunName = () = > {}
-
不建议用函数对象的实例化来定义函数,效率不高可读性差
函数声明提升
类似于变量提升 会把函数的声明提升到当前 作用域 的最顶部
fn() //会把函数的声明提升到当前作用域的最顶部
functino fn() {
console.log(
提升
)
}
可以
不提升调用 不提升赋值
fun()
var fun = funtion (){console.log()}
相当于
var fun()
fun()
fun = funtion () {}
不可以 函数表达式必须声明赋值好再调用
函数参数
传递参数
-
红宝书解释
-
ECMAScript中的所有函数的传参都是按值传递的
-
什么意思呢,就是说函数外的变量会直接复制到函数内部
-
如果参数是一个原始值,那么就相当于直接变成一个新的局部变量
- 所以函数内部修改不会反映到函数外部
-
如果传递的是一个对象这种引用类型的数据,**传递的也是值而不是引用,**虽然内部的修改复制的值来访问对象,可以将内部的修改映射到外部,因为obj指向的对象保存再堆内存上
- 这并不意味着是按照引用传递的;证明如下
-
function setName(obj){ obj.name = 'Nicholas'; obj = new Object() obj.name = "Greg" } let person = new Object() setName (person) console.log(person.name) // 'Nicholas' - 📌 这个观点可能不够全面但是却证明了引用值不是按引用传递的
-
-
-
我更喜欢的解释
-
function changeStuff(a, b, c) { a= a* 10; b.item= "changed"; c= {item: "changed"}; } var num= 10; var obj1= {item: "unchanged"}; var obj2= {item: "unchanged"}; changeStuff(num, obj1, obj2); console.log(num); console.log(obj1.item); console.log(obj2.item); -
10 changed unchanged
-
-
传递的对象的指针的拷贝,如何理解呢
- 对于原始值,拷贝了指针,次指针原本指向的是实参,重新赋值之后相当于给这个指针拷贝改变了指向,所以原来的指针对应的实参的值当然不会发生改变
- 对于引用值,拷贝了指针,而不是指针本身,用这个拷贝来的指针访问到的值实际上就是本来的实参的真实值,但是直接对这个指针赋值,只会改变这个指针的拷贝的指向
-
初始状态
- 函数域出现,拷贝出指针ab
- 执行操作
a.item = changed,b = {item : changed}
- 所以得到的结果obj1改变了,obj2没有改变,原始值的状况就和obj2一样
基本类型可以称为传值调用没错,引用类型是传共享调用,本质上就是传变量的指针的拷贝,而引用类型的值就是一个指针,所以也可以称为值传递,所以说javascript的所有数据类型都是值传递的是没有问题的
默认参数作用域和暂时性死区
- 既然可以定义默认参数,默认参数甚至可以动态调用函数,那么他就一定有一个作用域,默认参数的定义效果和下面第二个代码是一样的
let Fun1 = function (a = 123, b = 456) {
console.log(a, b)
}
function Fun2() {
let a = 123
let b = 456
}
这里传达两个信息: 第一作用域是函数作用域,第二默认参数的定义具有顺序,所以后面的参数可以引用前面的参数,但是前面的不能引用后面的参数。这就是暂时性死区
function Fun4(a = b + 1, b = 1) {
console.log(a, b)
}
Fun4()//Uncaught ReferenceError ReferenceError: Cannot access 'b' before initialization
默认参数只有在函数被调用而且没有传入参数的时候才会求值
arguments 动态参数
arguments只存在函数里面 实际上是一个伪数组
funtion getSum()
{
for( let i = 0 ; i < arguments.length;i++)
{
sum += arguments[i]
}
}
getSum(1,1,2,2,31,4)
getSum(1,2,3)
arguments在箭头箭头函数里没有 而且这是个伪数组 他的方法特殊的
- 修改argument的元素,对应形参的值也会发生改变
- 如果形参没有被传入,那么给argument对应元素复制无用
- 在严格模式中,修改argument不能反映到形参上
- argument不反映默认参数,只会反映传给函数的参数
rest 剩余参数
…arr 这个真数组,他只接收形参以外的东西 , 必须要放在最后
funtion getSum (a,b,...list)
{
console.log(list)//不用写...
}
对象中的rest
function connect({ host, port, ...user }) {
console.log(host)
console.log(port)
console.log(user)
}
connect({
host: '127.0.0.1',
port: 3306,
username: 'root',
password: 'root',
type: 'master'
})
对象中的展开运算符
做对象的合并
const a = {
q: "a"
}
const b = {
w: "b"
}
const c = {
e: "c"
}
const d = { ...a, ...b, ...c }
console.log(d)//{q: 'a', w: 'b', e: 'c'}
⚠️区分展开运算符
不一定在函数里的
const arr = [1,2,3]
console.log(...arr) // 1 2 3
//...arr === 1,2,3
不会修改数组
应用:求最大值
console.log(Math.arr(..arr)) //3 Math.arr(不能传数组)
应用:合并数组
const arr2 = [3,4,5]
const arr3 = [...arr,...arr2]// [1,2,3,3,4,5]
箭头函数
箭头函数没有
arguments变量,super(),new.target箭头函数不能作为构造实例化对象 箭头函数的this是静态的,它的this就是声明的时候的this 箭头函数省略花括号之后要省略return 箭头函数没有prototype属性
基本语法
!important 目的是为了更短的代码,把函数表达式更加简洁
const fn = funtion() {
console.log(123)}
//可以等价于=>
const fn =
(x) =>
{
console.log(123)}
案例:
cosnt arr = [1,6,7,11,2,90]
cosnt result = arr.filter(item => item % 2 === 0 )
省略
//**'**这个小括号在只有一个参数的时候可以省略'
const fn = x => console.log(123)//只有一行代码的时候大括号可以省略
const fn = x => x + x //省略了return
console.log(fn(1))//2
直接返回对象
用小括号包含对象,因为函数体也是{}
const fn = (uname) => ({name:uname})//用小括号包含起来
箭头函数参数
没有arguments 但是有…arr剩余参数
const getSum = (...arr) => {
let sum = 0
for ( let i = 0 ; i < arr.lenght; i++
sum += arr[i]
return result
}
this
this就是调用此函数的对象
对于普通情况而言:
console.log(this) //window
function fn() {
console.log(this)//window
}
//对象方法中的this
const obj = {
name : 'andy',
sayHi: function() {
console.log(this)//得到this==obj
}
obj.sayHi
对于箭头函数而言
箭头函数不会创this,只会从作用域链的上一层来找this 所以在dom中用回调函数的时候就不用箭头函数(丢失this 只会指向window)
const fn = () => {
console.log(this)//在这个局部作用域中没有this,
//所以找到的是window,实际上不是window调用的
}
//对象方法的箭头函数的this
const obj = {
name : 'andy',
sayHi : () => {
console.log(this)//这个作用域里没有this,所以找到的this也是window
}
obj.sayHi
处理this
严格模式下没有调用调用主体的时候this = undefine
普通函数
谁调用就指向谁
箭头函数
箭头函数会找最近的外层的this,本身不会产生this
操作dom对象的时候要用this就不要用箭头函数
再构造函数和原型函数中也不要用箭头函数
改变this
call()
调用的同时改变this指向 fun.call(thisArg,arg1,arg2)
funciton fn() {
console.log(this)
}
fn.call(obj)//输出obj
apply()
类似call fun.apply(thisArg,[argArr])
function fn (x, y){
console.log(this)
console.log(x+y)
}
fn.apply(obj,[1,2])
使用场景:求最大值
//const max = Math.max(...arr)
const max = Math.max.apply(Math,arr)
const max = Math.max.apply(null,arr)
bind()
bind()不会调用函数 语法和call()相似
function fn() {
console.log(this)
}
const newFn fn.bind(obj)//返回值是一个函数(原函数的拷贝)
函数重载
在比如C++和java的语言中,函数可以重载,就是在同一个定义域里可以定义同名的不同函数,这些函数的参数和实现不同,调用这个函数的时候,会进行重载决策,决定用哪个函数 JavaScript中不存在函数重载,因为参数本来就是一个参数数组的形式传进去的,所以后声明的函数会直接覆盖原来的同名函数
函数解耦(decoupling)
arguments.callee()
调用创造
arguments所在作用域的那个函数
- 递归函数原本是高度耦合的,函数名是一样的才行
function coupling(n) {
//这是一个硬代码递归函数
if (n > 10) {
console.log(n * 10)
}
else {
coupling(n + 1)//这里执行递归必须是同名函数
}
}
coupling(1)
coupling(12)
解耦之后,就算名字改变了也无所谓了
function coupling(n) {
//这是一个硬代码递归函数
if (n > 10) {
console.log(n * 10)
}
else {
arguments.callee(n + 1)//解耦
}
let decoupling = coupling;//将coupling指向的函数赋值给decoupling,此时函数名为decouping,函数内容不变
//然后把原来的指针指向其他地方
coupling = function (n) {
console.log(0)
}
decoupling(1)//110
coupling(1)//0
这个方法在严格模式下不能使用
在严格模式下要使用命名函数表达式
const Fun = (function f(num){}) 在参数括号前面加一个f
const Fun = function f(num) {
if (num > 10) {
console.log(num)
}
else {
f(num + 1)
}
}
函数内置属性方法
function.caller
引用调用当前函数的函数(的源代码)
-
如果在全局中调用就是null
-
可以结合函数解耦降低耦合度
arguments.callee.caller
function.name
函数对象暴露了一个只读的name 属性
用箭头函数,函数声明,函数表达式创建的函数都会有这个name属性,如果没有名字那就是空字符串
如果使用bind实例化的函数或者是set() | get() 函数会加上一个前缀 : bound | set | get
new.target()
在函数内部调用
new.target()可以确定函数是不是由new关键字创建的,如果不是就是undefine,否则是被调用的构造函数
function.prototype
不可枚举,所以不被for in读取
function.length
函数的形参个数
function.call(thisArg,arg1,arg2...)
function.apply(thisArg,argumentsList()
function.bond(this)
不会调用函数,直接拷贝一个函数,指定this
尾调用优化
尾调用
最常见的就是递归函数,每次返回另一个函数的调用
尾调用优化的要求
-
严格模式
- 因为非严格模式下有
funcition.arguments | function.caller他们会调用外部函数的栈帧,意味着就不能优化了
- 因为非严格模式下有
-
外部函数的返回是内部函数的调用
-
返回之后不用再做其他操作
-
尾调用的函数不是一个闭包
未优化尾调用
- 执行外层函数,推入第一个栈帧
- 执行到return发现必须对内层函数求值
- 执行内层函数,推入第二个栈帧
- return第二个函数,第一个函数得到值返回
- 弹出栈帧
使用了两个栈帧
优化后尾调用
- 执行外层函数,推入第一个栈帧
- 执行到return发现必须对内层求值
- 引擎发现外层函数的返回值就是内层函数的调用,直接弹出第一个栈帧
- 执行第二个函数,推入第二个栈帧
- 计算返回值
- 弹出第二个栈帧
全程只用了一个栈帧,在递归中的优化理论上会比较明显
代理和反射
Proxy对象,创建一个新的代理,拦截 和自定义基本操作(查找,赋值,枚举,调用等操作)
-
new Proxy()创建一个新代理const p = new Proxy (target , handler)target要代理的对象,handler处理函数- handler中包含
Proxy的各个捕获器,trap,所有捕获器都是可选的,可以重写或者修改 [handler.getPrototypeOf()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf>)[Object.getPrototypeOf](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf>)方法的捕捉器。[handler.setPrototypeOf()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf>)[Object.setPrototypeOf](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf>)方法的捕捉器。[handler.isExtensible()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible>)[Object.isExtensible](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible>)方法的捕捉器。[handler.preventExtensions()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions>)[Object.preventExtensions](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions>)方法的捕捉器。[handler.getOwnPropertyDescriptor()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor>)[Object.getOwnPropertyDescriptor](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor>)方法的捕捉器。[handler.defineProperty()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty>)[Object.defineProperty](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty>)方法的捕捉器。[handler.has()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has>)[in](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/in>)操作符的捕捉器。[handler.get()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get>)属性读取操作的捕捉器。[handler.set()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set>)属性设置操作的捕捉器。[handler.deleteProperty()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty>)[delete](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/delete>)操作符的捕捉器。[handler.ownKeys()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys>)[Object.getOwnPropertyNames](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames>)方法和[Object.getOwnPropertySymbols](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols>)方法的捕捉器。[handler.apply()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply>)函数调用操作的捕捉器。[handler.construct()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct>)[new](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new>)操作符的捕捉器。get()读取操作的捕获器接受3个参数tarpTarget目标对象,porperty被读取的属性,reveiver代理对象-
class Running { age = 18 distance = '100km' } const handler = { get(target, property, recevier) { let result = target[property] + '(For reference)' return result } } const mike = new Running() const proxy = new Proxy(mike, handler) console.log(mike.age)//18 console.log(proxy.age)//18(For reference)
-
在代理和目标上的赋值和修改都会反映到对方身上,因为他们操作的其实是同一个对象
捕获器不变式
防止捕获器返回一个反常的值,比如在不可写不可配置的属性上返回不同的值,会抛出
TypeError
可撤销代理
-
通过静态方法实现可撤销代理
-
Proxy.revocable ( target , handler )- 返回一个对象,对象的结构为 :
{"proxy": proxy, "revoke": revoke} - 用解构赋值来定义:
const {proxy , revoke } = Proxy.revocable (target ,handler)
- 返回一个对象,对象的结构为 :
多层代理
class Running {
age = 18
distance = '100km'
}
const handler2 = {
get(target, property, recevier) {
let result =
Reflect.get(...arguments)
+ '(Protect for the second time)'
return result//这里的Reflect拿到的已经是代理过一次的了
}
}
const handler1 = {
get(target, property, recevier) {
let result =
Reflect.get(...arguments)
+ '(Protect for the first time)'
return result//这个Reflect出来还是原始的
}
}
const mike = new Running()
const proxy2 = new Proxy(mike, handler2)
const proxy1 = new Proxy(proxy2, handler1)
console.log(mike.age)//18
console.log(proxy1.age)//18(Protect for the second time)(Protect for the first time)
代理的问题
代理改变了this,对于依赖this的对象,比如用weakmap实现的私有属性,用代理就会无法获取
const privateClass = (function () {
let priDate = new WeakMap()
class Private {
constructor(id) {
priDate.set(this, id)
}
set id(id) {//set基本方法,当一个关键字,id才是函数的名字
priDate.set(this, id)
}
get id() {//get基本方法,这里重名了,但这里要当作一个属性来用
return priDate.get(this)
}
}
return Private
})()//立刻执行函数,得到一个对象,封装了一个类的
const test = new privateClass(124)
console.log(test.id)//124
const proxy = new Proxy(test, {})//建立空代理
console.log(proxy.id)//undefined
此外,还有Date类型依赖this值上的
[[NumberDate]]但是代理无法控制这个值,所以也会导致TypeError
反射 API
要重写一个基本操作不是都像get的实现这样简单,所以全部手撕不可取,使用Reflect 对象来轻松重建,Reflect封装了基本操作
Reflect.trap(...arguments)
const handler = {
get(target, property, recevier) {
let result = Reflect.get(...arguments) + '(For reference)'
return result
}
}
效果和上面是一样的,只不过是用Reflect.get(...argument)代替了我们手动的实现的获取的返回值
-
状态标记,有点反射方法提供了一个标记,用来表示意图执行的操作是否成功
Reflect.defineProperty() | .prevneExtensions() | setPrototypeOf() | set() | deleteProerty()
-
代替操作符,
Reflect.get()访问符Reflect.set()赋值符Reflect.has()in|withReflect.deleteProperty()delete符Reflect.construct()new符
代理模式
追踪属性访问
get(target,property,recevier)
//追踪属性访问
const test1 = {
username: 'abc',
age: 18
}
const proxy1 = new Proxy(test1, {
get(target, property, recevier) {
console.log(
get ${target.username}'s property: ${property}
)
return Reflect.get(...arguments)
},
set(target, property, value, recevier) {
console.log(
setting ${target.username}'s ${property} as ${value}
)
Reflect.set(...arguments)
}
})
proxy1.username
proxy1.age = 20
console.log(test1.age)
隐藏属性
get(target,property,reveicier)
has(target,property)
//隐藏属性
const hiddenList = ['a', 'b']
const test2 = {
'a': 1,
'b': 2,
'c': 3
}
const proxy2 = new Proxy(test2, {
get(target, property, recevier) {
if (hiddenList.includes(property)) {
return undefined
}
else return Reflect.get(...arguments)
},
has(target, property) {
if (hiddenList.includes(property)) {
return false
}
else return Reflect.has(...arguments)
}
})
console.log(proxy2.a)
console.log(test2.a)
console.log('b' in proxy2)//false
console.log('b' in test2)//true
验证属性
set(target,property,value)
//属性验证
const test3 = {
}//必须是数字类型
const proxy3 = new Proxy(test3, {
set(target, property, value, recevier) {
if (typeof value !== 'number') {
console.log('setting faliure')
}
else {
Reflect.set(...arguments)
return Reflect.set(...arguments)
}
}
})
proxy3.a = 1
proxy3.b = '1'//setting faliure
console.log(proxy3)//Proxy {a: 1}
函数和构造函数参数验证
apply(target,thisArg,argumentsList)
constructor(target,argumentList,newTarget)
//构造函数参数验证
class Test4 {
constructor(id) {
this.id = id
}
}
const proxy4 = new Proxy(Test4, {
construct(target, argumentList, newTarget) {
if (typeof argumentList[0] !== 'number') {
throw 'the argument must be a number'//这里必须抛错误,因为有捕获不变式的限制
}
else {
return Reflect.construct(...arguments)
}
}
})
// const test4 = new proxy4('123') //Error the argument must be a number
const test4_ = new proxy4(123)
console.log(test4_)//Test4 {id: 123}
//函数参数验证
function test5(id) {
console.log(id)
}
const proxy5 = new Proxy(test5, {
apply(target, thisArg, argumentList) {
if (typeof argumentList[0] !== 'number') {
throw 'argument must be type of number'
}
else {
return Reflect.apply(...arguments)
}
}
})
// proxy5('123')//Uncaught Error argument must be type of number
proxy5(134)
数据绑定和可视化对象
就是在set的时候将对象加入到一个全局的数组中
部分插图来自网络,侵删。 小弟才疏学浅难免错漏,请多多担待不吝赐教。