font-main
2020-12-15 10:31:48 0 举报
AI智能生成
前端大纲知识点
作者其他创作
大纲/内容
review
1. 字节跳动前端岗位面试(已拿offer)文末附赠其他面试面经等
https://www.jianshu.com/p/d74fceb929c8
https://www.jianshu.com/p/d74fceb929c8
异步
1.async await
1.generator 语法糖
2. 演变
1. 回调
2.promise
3.* yield
4. async await
3. 常见面试题
1.pomise
1. console.log('start here')
new Promise((resolve, reject) => {
console.log('first promise constructor')
resolve()
})
.then(() => {
console.log('first promise then')
return new Promise((resolve, reject) => {
console.log('second promise')
resolve()
})
.then(() => {
console.log('second promise then')
})
})
.then(() => {
console.log('another first promise then')
})
console.log('end here')
// start here 、
// first promise constructor
// end here
// first promise then
// second promise
// second promise then
// another first promise then
new Promise((resolve, reject) => {
console.log('first promise constructor')
resolve()
})
.then(() => {
console.log('first promise then')
return new Promise((resolve, reject) => {
console.log('second promise')
resolve()
})
.then(() => {
console.log('second promise then')
})
})
.then(() => {
console.log('another first promise then')
})
console.log('end here')
// start here 、
// first promise constructor
// end here
// first promise then
// second promise
// second promise then
// another first promise then
2. console.log('start here')
const foo = () => (new Promise((resolve, reject) => {
console.log('first promise constructor')
let promise1 = new Promise((resolve, reject) => {
console.log('second promise constructor')
setTimeout(() => {
console.log('setTimeout here')
resolve()
}, 0)
resolve('promise1')
})
resolve('promise0')
promise1.then(arg => {
console.log(arg)
})
}))
foo().then(arg => {
console.log(arg)
})
console.log('end here')
// start here
// first promise constructor
// second promise constructor
// end here
// promise1
// promise0
// setTimeout here
const foo = () => (new Promise((resolve, reject) => {
console.log('first promise constructor')
let promise1 = new Promise((resolve, reject) => {
console.log('second promise constructor')
setTimeout(() => {
console.log('setTimeout here')
resolve()
}, 0)
resolve('promise1')
})
resolve('promise0')
promise1.then(arg => {
console.log(arg)
})
}))
foo().then(arg => {
console.log(arg)
})
console.log('end here')
// start here
// first promise constructor
// second promise constructor
// end here
// promise1
// promise0
// setTimeout here
3. async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
4. async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
await console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
await console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
// 3.流程解析
5. async function async1() {
console.log('async1 start') // step 4: 直接打印同步代码 async1 start
await async2() // step 5: 遇见 await,首先执行其右侧逻辑,并在这里中断 async1 函数
console.log('async1 end') // step 11: 再次回到 async1 函数,await 中断过后,打印代码 async1 end
}
async function async2() {
console.log('async2') // step 6: 直接打印同步代码 async2,并返回一个 resolve 值为 undefined 的 promise
}
console.log('script start') // step 1: 直接打印同步代码 script start
// step 2: 将 setTimeout 回调放到宏任务中,此时 macroTasks: [setTimeout]
setTimeout(function() {
console.log('setTimeout') // step 13: 开始执行宏任务,输出 setTimeout
}, 0)
async1() // step 3: 执行 async1
// step 7: async1 函数已经中断,继续执行到这里
new Promise(function(resolve) {
console.log('promise1') // step 8: 直接打印同步代码 promise1
resolve()
}).then(function() { // step 9: 将 then 逻辑放到微任务当中
console.log('promise2') // step 12: 开始执行微任务,输出 promise2
})
console.log('script end') // step 10: 直接打印同步代码 script end,并回到 async1 函数中继续执行
5. async function async1() {
console.log('async1 start') // step 4: 直接打印同步代码 async1 start
await async2() // step 5: 遇见 await,首先执行其右侧逻辑,并在这里中断 async1 函数
console.log('async1 end') // step 11: 再次回到 async1 函数,await 中断过后,打印代码 async1 end
}
async function async2() {
console.log('async2') // step 6: 直接打印同步代码 async2,并返回一个 resolve 值为 undefined 的 promise
}
console.log('script start') // step 1: 直接打印同步代码 script start
// step 2: 将 setTimeout 回调放到宏任务中,此时 macroTasks: [setTimeout]
setTimeout(function() {
console.log('setTimeout') // step 13: 开始执行宏任务,输出 setTimeout
}, 0)
async1() // step 3: 执行 async1
// step 7: async1 函数已经中断,继续执行到这里
new Promise(function(resolve) {
console.log('promise1') // step 8: 直接打印同步代码 promise1
resolve()
}).then(function() { // step 9: 将 then 逻辑放到微任务当中
console.log('promise2') // step 12: 开始执行微任务,输出 promise2
})
console.log('script end') // step 10: 直接打印同步代码 script end,并回到 async1 函数中继续执行
2. 如果new Promise1里面还有 new Promise2(嵌套关系) ,
并且都有完成处理函数,
会先执行new Promise1 同步代码,
然后直接执行new Promise2,
会先将new Promise2的完成处理函数放入微任务队列,
再将 new Promise1 的完成处理函数放入微任务队列
并且都有完成处理函数,
会先执行new Promise1 同步代码,
然后直接执行new Promise2,
会先将new Promise2的完成处理函数放入微任务队列,
再将 new Promise1 的完成处理函数放入微任务队列
1. async 声明的函数,其返回值必定是 promise 对象,
如果没有显式返回 promise 对象,
也会用 Promise.resolve() 对结果进行包装,保证返回值为 promise 类型
如果没有显式返回 promise 对象,
也会用 Promise.resolve() 对结果进行包装,保证返回值为 promise 类型
2. await 会先执行其右侧表达逻辑(从右向左执行),
并让出主线程,跳出 async 函数,
而去继续执行 async 函数外的同步代码
并让出主线程,跳出 async 函数,
而去继续执行 async 函数外的同步代码
3. 如果 await 右侧表达逻辑是个 promise,让出主线程,
继续执行 async 函数外的同步代码,等待同步任务结束后,
且该 promise 被 resolve 时,继续执行 await 后面的逻辑
继续执行 async 函数外的同步代码,等待同步任务结束后,
且该 promise 被 resolve 时,继续执行 await 后面的逻辑
4. 如果 await 右侧表达逻辑不是 promise 类型,
那么仍然异步处理,将其理解包装为 promise,
async 函数之外的同步代码执行完毕之后,
会回到 async 函数内部,继续执行 await 之后的逻辑
那么仍然异步处理,将其理解包装为 promise,
async 函数之外的同步代码执行完毕之后,
会回到 async 函数内部,继续执行 await 之后的逻辑
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2') // 直接打印同步代码 async2,
// 并返回一个 resolve 值为 undefined 的 promise
}
// async2 函数内并没有 await,按顺序执行,同步输出 async2,
相当于
async function async1() {
console.log('async1 start')
await Promise.resolve().then(() =>{})
console.log('async1 end')
}
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2') // 直接打印同步代码 async2,
// 并返回一个 resolve 值为 undefined 的 promise
}
// async2 函数内并没有 await,按顺序执行,同步输出 async2,
相当于
async function async1() {
console.log('async1 start')
await Promise.resolve().then(() =>{})
console.log('async1 end')
}
2.async await 、 promise
4. 浏览器js队列
执行栈
1.同步任务
2.异步任务
1.微任务
Promise.then
MutationObserver
process.nextTick (Node.js)
MutationObserver
process.nextTick (Node.js)
2.宏任务
setTimeout
setInterval
I/O
事件
postMessage
setImmediate (Node.js,浏览器端该 API 已经废弃)
requestAnimationFrame
UI 渲染
setInterval
I/O
事件
postMessage
setImmediate (Node.js,浏览器端该 API 已经废弃)
requestAnimationFrame
UI 渲染
5. 使用连接
1. async await 你真的用对了吗? https://www.cnblogs.com/kenkofox/
2. ES6 Async/Await 完爆Promise的6个原因 https://segmentfault.com/a/1190000009070711
3. 头条前端笔试题 - 实现一个带并发限制的promise异步调度器 https://blog.csdn.net/zz_jesse/article/details/107293743?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.compare&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.compare
4. js 多个异步 的并发控制 https://blog.csdn.net/anchu7971/article/details/101279805?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduend~default-1-101279805.nonecase&utm_term=js%E6%8E%A7%E5%88%B6%E5%BC%82%E6%AD%A5%E5%B9%B6%E5%8F%91%E6%95%B0%E9%87%8F&spm=1000.2123.3001.4430
5. https://blog.csdn.net/weixin_33928137/article/details/88754909?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.compare&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.compare
2. 异步流程控制
并发控制
demo
1. 图片碎片异步并发上传
2. gitchat 画正方形、 红绿灯
3. gitchat 异步并发数限制、promise.rate
4. gitchat 返回100本书籍
3. 链接
1. js实现并发请求控制 https://www.jianshu.com/p/be92059cd7f2
3.promise 原理
1. 手写Promise
1. promisefy
const promisefy = fn => args => {
return new Promise((resolve, reject) =>{
args.success = function(res) {
resolve(res)
}
args.faild = function(res) {
reject(res)
}
})
}
2. code process
step1
Promise 其实就是一个构造函数,我们使用这个构造函数创建一个 Promise 实例。该构造函数很简单,它只有一个参数,按照 Promise/A+ 规范的命名,把 Promise 构造函数的参数叫做 executor,executor 类型为函数。这个函数又“自动”具有 resolve、reject 两个方法作为参数。
function Promise(executor) {
}
step2 Promise 的实质:
Promise 构造函数返回一个 promise 对象实例,这个返回的 promise 对象具有一个 then 方法。then 方法中,调用者可以定义两个参数,分别是 onfulfilled 和 onrejected,它们都是函数类型。其中 onfulfilled 通过参数,可以获取 promise 对象 resolved 的值,onrejected 获得 promise 对象 rejected 的值。通过这个值,我们来处理异步完成后的逻辑
function Promise(executor) {
}
Promise.prototype.then = function(onfulfilled, onrejected) {
}
step3
我们在使用 new 关键字调用 Promise 构造函数时,在合适的时机(往往是异步结束时),调用 executor 的参数 resolve 方法,并将 resolved 的值作为 resolve 函数参数执行,这个值便可以后续在 then 方法第一个函数参数(onfulfilled)中拿到;同理,在出现错误时,调用 executor 的参数 reject 方法,并将错误信息作为 reject 函数参数执行,这个错误信息可以在后续的 then 方法第二个函数参数(onrejected)中拿到。
step4
因此,我们在实现 Promise 时,应该有两个值,分别储存 resolved 的值,以及 rejected 的值(当然,因为 Promise 状态的唯一性,不可能同时出现 resolved 的值和 rejected 的值,因此也可以用一个变量来存储);同时也需要存在一个状态,这个状态就是 promise 实例的状态(pending,fulfilled,rejected);同时还要提供 resolve 方法以及 reject 方法,这两个方法需要作为 executor 的参数提供给开发者使用:
function Promise(executor) {
const self = this
this.status = 'pending'
this.value = null
this.reason = null
function resolve(value) {
self.value = value
}
function reject(reason) {
self.reason = reason
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled(this.value)
onrejected(this.reason)
}
为了保证 onfulfilled、onrejected 能够强健执行,我们为其设置了默认值,其默认值为一个函数元(Function.prototype)。
step5
因为 resolve 的最终调用是由开发者在不确定环境下(往往是在全局中)直接调用的。为了在 resolve 函数中能够拿到 promise 实例的值,我们需要对 this 进行保存,上述代码中用 self 变量记录 this,或者使用箭头函数
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = value => {
this.value = value
}
const reject = reason => {
this.reason = reason
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled(this.value)
onrejected(this.reason)
}
构造函数原型上可以拿到 构造函数内部变量的值
setp6
为什么 then 放在 Promise 构造函数的原型上,而不是放在构造函数内部呢?
这涉及到原型、原型链的知识了:每个 promise 实例的 then 方法逻辑是一致的,在实例调用该方法时,可以通过原型(Promise.prototype)找到,而不需要每次实例化都新创建一个 then 方法,这样节省内存,显然更合适。
step7
测试
let promise = new Promise((resolve, reject) => {
resolve('data')
reject('error')
})
promise.then(data => {
console.log(data)
}, error => {
console.log(error)
})
只会输出:data,因为我们知道 promise 实例状态只能从 pending 改变为 fulfilled,或者从 pending 改变为 rejected。状态一旦变更完毕,就不可再次变化或者逆转。也就是说:如果一旦变到 fulfilled,就不能再 rejected,一旦变到 rejected,就不能 fulfilled。
而我们的代码实现,显然无法满足这一特性。执行上一段代码时,将会输出 data 以及 error。
step8
状态进行判断和完善
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
}
}
const reject = reason => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
}
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
}
这里我们对 Promise.prototype.then 参数 onfulfilled 和 onrejected 进行了判断,当实参不是一个函数类型时,赋予默认函数值。这时候的默认值不再是函数元 Function.prototype 了。为什么要这么更改?后面会有介绍。
step9
Promise 异步完善
function WPromise(executor){
this.status = 'pendding'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onrejectedFunc= Function.prototype
const resovle = (value) =>{
setTimeout(() => {
if(this.status === 'pendding'){
this.value = value
this.status = 'fulfilled'
// console.log("value333", value)
this.onFulfilledFunc(this.value)
}
})
}
const reject = (reason) =>{
setTimeout(() => {
if(this.status === 'pendding'){
this.reason = reason
this.status = 'rejected'
this.onrejectedFunc(reason)
}
})
}
executor(resovle, reject)
}
WPromise.prototype.then = function(onfulfilled , onrejected){
console.log('onfulfilled22', onfulfilled)
const temp = onfulfilled
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}
if(this.status === 'fulfilled') {
onfulfilled(this.value)
}
if(this.status === 'rejected') {
onrejected(this.reason)
}
if(this.status === 'pendding') {
this.onFulfilledFunc = onfulfilled
this.onrejectedFunc = onrejected
}
}
let promise = new WPromise((resolve, reject) => {
// setTimeout(() => {
// // console.log('data111')
// resolve('data value')
// }, 2000)
resolve('data value')
})
promise.then(data => {
console.log("data",data)
}, error => {
console.log(error)
})
console.log('111')
MutationObserver
https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
step10
Promise 多个then 处理、错误捕获
function WPromise(executor){
this.status = 'pendding'
this.value = null
this.reason = null
this.onFulfilledFunc = []
this.onrejectedFunc = []
const resovle = (value) =>{
console.log('value instanceof Promise', value instanceof Promise)
if(value instanceof Promise) {
return value.then(resovle,reject)
}
setTimeout(() => {
if(this.status === 'pendding'){
this.value = value
this.status = 'fulfilled'
// console.log("value333", value)
this.onFulfilledFunc.forEach(FulfilledItem => {
FulfilledItem(value)
});
}
})
}
const reject = (reason) =>{
setTimeout(() => {
if(this.status === 'pendding'){
this.reason = reason
this.status = 'rejected'
this.onrejectedFunc(reason)
}
})
}
try {
executor(resovle, reject)
}catch(e){
console.log('catch error',e)
}
}
WPromise.prototype.then = function(onfulfilled , onrejected){
console.log('onfulfilled22', onfulfilled)
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}
if(this.status === 'fulfilled') {
onfulfilled(this.value)
}
if(this.status === 'rejected') {
onrejected(this.reason)
}
if(this.status === 'pendding') {
this.onFulfilledFunc.push(onfulfilled)
this.onrejectedFunc.push(onrejected)
}
}
let promise = new WPromise((resolve, reject) => {
setTout(() => {
// console.log('data111')
resolve('data value')
}, 2000)
// resolve('data value')
})
promise.then(data => {
console.log("data11",data)
}, error => {
console.log(error)
})
promise.then(data => {
console.log("data22",data)
}, error => {
console.log('got error from promise', error)
})
console.log('111')
step11
总结:Promise特性
Promise 状态具有凝固性
Promise 错误处理
Promise 实例添加多个 then 处理
链接 https://www.jianshu.com/p/fe42b01ef076
3. 链接
1. 你以为我真的想让你手写 Promise 吗(上) https://gitbook.cn/gitchat/column/5c91c813968b1d64b1e08fde/topic/5cbbe876bbbba80861a35bf4
2. 完善手写Promise
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
const resolvePromise = (promise2, result, resolve, reject) => {
// 当 result 和 promise2 相等时,也就是说 onfulfilled 返回 promise2 时,进行 reject
if (result === promise2) {
return reject(new TypeError('error due to circular reference'))
}
// 是否已经执行过 onfulfilled 或者 onrejected
let consumed = false
let thenable
if (result instanceof Promise) {
if (result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
// 如果返回的是疑似 Promise 类型
if (isComplexResult(result)) {
try {
thenable = result.then
// 如果返回的是 Promise 类型,具有 then 方法
if (typeof thenable === 'function') {
thenable.call(result, function(data) {
if (consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if (consumed) {
return
}
consumed = true
return reject(error)
})
}
else {
return resolve(result)
}
} catch(e) {
if (consumed) {
return
}
consumed = true
return reject(e)
}
}
else {
return resolve(result)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
// 如何实现 Promise 穿透呢
// 给 .then() 函数传递非函数值作为其参数时,实际上会被解析成 .then(null),
// 这时候的表现应该是:上一个 promise 对象的结果进行“穿透”,
// 如果在后面链式调用仍存在第二个 .then() 函数时,将会获取被穿透下来的结果。
// 如果 onfulfilled 不是函数类型,则给一个默认值,该默认值是返回其参数的函数
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
// promise2 将作为 then 方法的返回值
let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 resolved 的值为 onfulfilled 的执行结果
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 reject 的值为 onrejected 的执行结果
let result = onrejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
reject(e)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(value => {
try {
let result = onfulfilled(value)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
return reject(e)
}
})
this.onRejectedArray.push(reason => {
try {
let result = onrejected(reason)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
return reject(e)
}
})
})
}
}
Promise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc)
}
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.reject = function(value) {
return new Promise((resolve, reject) => {
reject(value)
})
}
Promise.race = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(resolve, reject)
}
}
catch(e) {
reject(e)
}
})
}
Promise.all = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
let resultArray = []
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(data => {
resultArray.push(data)
if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
}
catch(e) {
reject(e)
}
})
}
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
const resolvePromise = (promise2, result, resolve, reject) => {
// 当 result 和 promise2 相等时,也就是说 onfulfilled 返回 promise2 时,进行 reject
if (result === promise2) {
return reject(new TypeError('error due to circular reference'))
}
// 是否已经执行过 onfulfilled 或者 onrejected
let consumed = false
let thenable
if (result instanceof Promise) {
if (result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
// 如果返回的是疑似 Promise 类型
if (isComplexResult(result)) {
try {
thenable = result.then
// 如果返回的是 Promise 类型,具有 then 方法
if (typeof thenable === 'function') {
thenable.call(result, function(data) {
if (consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if (consumed) {
return
}
consumed = true
return reject(error)
})
}
else {
return resolve(result)
}
} catch(e) {
if (consumed) {
return
}
consumed = true
return reject(e)
}
}
else {
return resolve(result)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
// 如何实现 Promise 穿透呢
// 给 .then() 函数传递非函数值作为其参数时,实际上会被解析成 .then(null),
// 这时候的表现应该是:上一个 promise 对象的结果进行“穿透”,
// 如果在后面链式调用仍存在第二个 .then() 函数时,将会获取被穿透下来的结果。
// 如果 onfulfilled 不是函数类型,则给一个默认值,该默认值是返回其参数的函数
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
// promise2 将作为 then 方法的返回值
let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 resolved 的值为 onfulfilled 的执行结果
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 reject 的值为 onrejected 的执行结果
let result = onrejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
reject(e)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(value => {
try {
let result = onfulfilled(value)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
return reject(e)
}
})
this.onRejectedArray.push(reason => {
try {
let result = onrejected(reason)
resolvePromise(promise2, result, resolve, reject)
}
catch(e) {
return reject(e)
}
})
})
}
}
Promise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc)
}
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.reject = function(value) {
return new Promise((resolve, reject) => {
reject(value)
})
}
Promise.race = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(resolve, reject)
}
}
catch(e) {
reject(e)
}
})
}
Promise.all = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
let resultArray = []
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(data => {
resultArray.push(data)
if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
}
catch(e) {
reject(e)
}
})
}
3.链接:
阮一峰:promise https://es6.ruanyifeng.com/#docs/promise
从零开始手写Promise https://zhuanlan.zhihu.com/p/144058361
阮一峰:promise https://es6.ruanyifeng.com/#docs/promise
从零开始手写Promise https://zhuanlan.zhihu.com/p/144058361
event loop
event loop
每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,所以不存在红色的等待时间。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。
http://www.ruanyifeng.com/blog/2013/10/event_loop.html
先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
浏览器event loop
1. new Promise() 会立即执行 2. 一个setTimeout 会执行一轮宏任务,如果这个setTimeout里面有微任务,先执行setTimeout里面的同步代码,在执行setTimeout里面的微任务。3.再执行下一轮宏任务setTimeout2 ,再执行setTImeout2里面的微任务
2.链接
0. 这一次,彻底弄懂 JavaScript 执行机制https://juejin.cn/post/6844903512845860872
1. JavaScript 运行机制详解:再谈Event Loop http://www.ruanyifeng.com/blog/2014/10/event-loop.html
nodejs event loop
子主题 2
链接
1. NodeJS 事件循环(第一部分)- 事件循环机制概述 https://zhuanlan.zhihu.com/p/37427130
2. 初探nodejs事件循环机制event loop
https://www.cnblogs.com/zifayin/p/11419808.html
3. NodeJS 事件循环 https://zhuanlan.zhihu.com/p/37427130
4. 笔记 nodejs-第二章-第二节-nodejs事件循环(2-1) http://note.youdao.com/s/MbA6ISKq
5. Node.js 事件循环机制 https://www.cnblogs.com/onepixel/p/7143769.html
es6
map、set、weakMap、weakSet
map
1.定义:map提供了“值---值”的对应,是一种更完善的hash结构,
将一个 DOM 节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串[object HTMLDivElement]
2.属性、方法
get/set/has/delete/size/clear
3.遍历方法
1.Map.prototype.keys()
2.Map.prototype.values()
3.Map.prototype.entries()
for (let [key, value] of map.entries()) {
console.log(key, value);
}
1.Map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
2.map[Symbol.iterator] === map.entries // true
4.Map.prototype.forEach()
4.Map转数组结构
1.扩展运算符
1. [...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
2. [...map.keys()]
// [1, 2, 3]
3. [...map.values()]
// ['one', 'two', 'three']
5.数组转Map
1.将数组传入 Map 构造函数,就可以转为 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
[true, 7],
[{foo: 3}, ['abc']]
])
6.Map 转为对象
1. Map 的键都是字符串,它可以无损地转为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
2.如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
const map1 = {
'function fun1() {}': true,
key2: true
}
'function fun1() {}': true,
key2: true
}
8.Map 转为 JSON
1.Map 的键名都是字符串,这时可以选择转为对象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
2.Map 的键名有非字符串,这时可以选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
7.对象转Map
1.对象转为 Map 可以通过Object.entries()。
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
let map = new Map(Object.entries(obj));
2.自己实现一个转换函数
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
9.JSON 转为 Map
1.正常情况下,所有键名都是字符串
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
2.整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
weakMap
1.定义
1.WeakMap结构与Map结构类似,也是用于生成键值对的集合
2. 区别
1. WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
2. WeakMap的键名所指向的对象,不计入垃圾回收机制。
3. 有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用
只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。
也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,
不用手动删除引用。
也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,
不用手动删除引用。
4. WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}
2. 属性和方法
1. WeakMap 与 Map 在 API 上的区别主要是两个,
1.一是没有遍历操作(即没有keys()、values()和entries()方法),
也没有size属性。
也没有size属性。
2. 二是无法清空,即不支持clear方法。
3. const wm = new WeakMap();
// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
2. 方法
get()、set()、has()、delete()。
3. 应用场景
1. 在网页dom上添加数据
在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。
WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制
也就是说,上面的 DOM 节点对象的引用计数是1,而不是2。
当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。
WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制
也就是说,上面的 DOM 节点对象的引用计数是1,而不是2。
当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
2. WeakMap的专用场合就是,它的键所对应的对象,
可能会在将来消失。WeakMap结构有助于防止内存泄漏。
可能会在将来消失。WeakMap结构有助于防止内存泄漏。
3. 查看引用内存
如果引用所指向的值占用特别多的内存,
就可以通过 Node 的process.memoryUsage方法看出来。
node --expose-gc
--expose-gc参数表示允许手动执行垃圾回收机制
就可以通过 Node 的process.memoryUsage方法看出来。
node --expose-gc
--expose-gc参数表示允许手动执行垃圾回收机制
// 手动执行一次垃圾回收,保证获取的内存使用状态准确
> global.gc();
undefined
// 查看内存占用的初始状态,heapUsed 为 4M 左右
> process.memoryUsage();
{ rss: 21106688,
heapTotal: 7376896,
heapUsed: 4153936,
external: 9059 }
> let wm = new WeakMap();
undefined
// 新建一个变量 key,指向一个 5*1024*1024 的数组
> let key = new Array(5 * 1024 * 1024);
undefined
// 设置 WeakMap 实例的键名,也指向 key 数组
// 这时,key 数组实际被引用了两次,
// 变量 key 引用一次,WeakMap 的键名引用了第二次
// 但是,WeakMap 是弱引用,对于引擎来说,引用计数还是1
> wm.set(key, 1);
WeakMap {}
> global.gc();
undefined
// 这时内存占用 heapUsed 增加到 45M 了
> process.memoryUsage();
{ rss: 67538944,
heapTotal: 7376896,
heapUsed: 45782816,
external: 8945 }
// 清除变量 key 对数组的引用,
// 但没有手动清除 WeakMap 实例的键名对数组的引用
> key = null;
null
// 再次执行垃圾回收
> global.gc();
undefined
// 内存占用 heapUsed 变回 4M 左右,
// 可以看到 WeakMap 的键名引用没有阻止 gc 对内存的回收
> process.memoryUsage();
{ rss: 20639744,
heapTotal: 8425472,
heapUsed: 3979792,
external: 8956 }
> global.gc();
undefined
// 查看内存占用的初始状态,heapUsed 为 4M 左右
> process.memoryUsage();
{ rss: 21106688,
heapTotal: 7376896,
heapUsed: 4153936,
external: 9059 }
> let wm = new WeakMap();
undefined
// 新建一个变量 key,指向一个 5*1024*1024 的数组
> let key = new Array(5 * 1024 * 1024);
undefined
// 设置 WeakMap 实例的键名,也指向 key 数组
// 这时,key 数组实际被引用了两次,
// 变量 key 引用一次,WeakMap 的键名引用了第二次
// 但是,WeakMap 是弱引用,对于引擎来说,引用计数还是1
> wm.set(key, 1);
WeakMap {}
> global.gc();
undefined
// 这时内存占用 heapUsed 增加到 45M 了
> process.memoryUsage();
{ rss: 67538944,
heapTotal: 7376896,
heapUsed: 45782816,
external: 8945 }
// 清除变量 key 对数组的引用,
// 但没有手动清除 WeakMap 实例的键名对数组的引用
> key = null;
null
// 再次执行垃圾回收
> global.gc();
undefined
// 内存占用 heapUsed 变回 4M 左右,
// 可以看到 WeakMap 的键名引用没有阻止 gc 对内存的回收
> process.memoryUsage();
{ rss: 20639744,
heapTotal: 8425472,
heapUsed: 3979792,
external: 8956 }
4. WeakMap 的另一个用处是部署私有属性。
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
set
1.定义
1.ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
2.Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
3. 有点类似队列的意思, Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
2. 用法
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 5
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 5
3.Set 实例的属性和方法
1.属性
1.Set.prototype.constructor
构造函数,默认就是Set函数。
2.Set.prototype.size
返回Set实例的成员总数。
2.方法
1.操作方法(用于操作数据)
1.Set.prototype.add(value) 添加某个值,返回Set结构本身
2.Set.prototype.delete(value) 删除某个值,返回一个布尔值,表示删除是否成功。
3.Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
4.Set.prototype.clear():清除所有成员,没有返回值。
2.遍历方法(用于遍历成员)
1. Set.prototype.keys():返回键名的遍历器
由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
2.Set.prototype.values():返回键值的遍历器
3. Set.prototype.entries():返回键值对的遍历器
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
4. Set.prototype.forEach():使用回调函数遍历每个成员
set1.forEach((value, key, set1) =>{})
3个参数:键值、键名、集合本身
5. 可以用for of 遍历Set
6. 扩展运算符遍历
扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构
3. 遍历的应用
1. 扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
let unique = [...new Set(arr)];
// [3, 5, 2]
2.数组的map和filter方法也可以间接用于 Set 了。
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
3. Set 可以很容易地实现并集(Union)、交集(Intersect)和差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
4. 同步改变原来Set结构
同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。
一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;
另一种是利用Array.from方法。
一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;
另一种是利用Array.from方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
4.Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。
1. Set.prototype[Symbol.iterator] === Set.prototype.values
// true
// true
4.注意点
1.向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
2.两个对象总是不相等的。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
set.add({});
set.size // 1
set.add({});
set.size // 2
5. 数组和set相互转换
1. Set 转数组
Array.from(new Set([1,2,3,3,4,]))
子主题
weakSet
1.定义
1.WeakSet 结构与 Set 类似,也是不重复的值的集合。
2. 区别:
1. WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象都可以.
第一点主要是因为值类型不存在引用,自然不能放在 WeakSet 中。
2. WeakSet 对象中存储的对象值都是被弱引用的,
如果没有其他的变量或属性引用这个对象值,
则这个对象值会被当成垃圾回收掉.
正因为这样, WeakSet 对象是无法被枚举的,
没有办法拿到它包含的所有元素
如果没有其他的变量或属性引用这个对象值,
则这个对象值会被当成垃圾回收掉.
正因为这样, WeakSet 对象是无法被枚举的,
没有办法拿到它包含的所有元素
在 WeakSet 中引用是弱引用
2. 属性方法
1. WeakSet.prototype.add(value)
在该 WeakSet 对象中添加一个新元素value.
2. WeakSet.prototype.clear()
清空该 WeakSet 对象中的所有元素.
3. WeakSet.prototype.delete(value)
从该 WeakSet 对象中删除value 这个元素, 之后 WeakSet.prototype.has(value) 方法便会返回false
4.WeakSet.prototype.has(value)
返回一个布尔值, 表示给定的值value是否存在于这个 WeakSet中
3.应用
1.存储dom节点
WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
2.保证实例的方法,只能在实例上调用
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}
上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用 WeakSet 的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏
3. 假如需要这个集合进行判断的一方并不想控制这个对象的生命周期
const requests = new WeakSet();
class ApiRequest {
constructor() {
requests.add(this);
}
makeRequest() {
if(!request.has(this)) throw new Error("Invalid access");
// do work
}
}
class ApiRequest {
constructor() {
requests.add(this);
}
makeRequest() {
if(!request.has(this)) throw new Error("Invalid access");
// do work
}
}
其实上文中“需要一个弱引用的集合”和“进行判断时外界对这个对象必然存在引用的”并不完全矛盾。
假如在一个模块内部,我既控制了对象的生命周期,
而又需要这个集合进行判断,那么的确,这个 WeakSet 确实没有必要。
但是假如需要这个集合进行判断的一方并不想控制这个对象的生命周期,
那么 WeakSet 倒显得有些必要了
假如在一个模块内部,我既控制了对象的生命周期,
而又需要这个集合进行判断,那么的确,这个 WeakSet 确实没有必要。
但是假如需要这个集合进行判断的一方并不想控制这个对象的生命周期,
那么 WeakSet 倒显得有些必要了
4.链接
1. WeakSet 用法解惑 https://zhuanlan.zhihu.com/p/54889129
Proxy
1.定义
1. Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,
所以属于一种“元编程”(meta programming),即对编程语言进行编程。
所以属于一种“元编程”(meta programming),即对编程语言进行编程。
2. Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
2. Proxy.revocable()
1. 创建一个可撤销的Proxy对象。
3. handler对象的方法
1. var proxy = new Proxy(target, handler);
1. new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,
2. handler参数也是一个对象,用来定制拦截行为。
2. handler是一个对象,里面是对应操作的函数
1. handler是一个空对象,没有任何拦截效果,
访问proxy就等同于访问target。
访问proxy就等同于访问target。
1. var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
2. 一个技巧是将 Proxy 对象,设置到object.proxy属性,
从而可以在object对象上调用。
从而可以在object对象上调用。
var object = { proxy: new Proxy(target, handler) };
3. Proxy 实例也可以作为其他对象的原型对象。
1. proxy对象是obj对象的原型,obj对象本身并没有time属性,
所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
get: function(target, propKey) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
4. Proxy 支持的拦截操作
1. get(target, propKey, receiver)
1.拦截对象属性的读取,比如proxy.foo和proxy['foo']。
2. get方法可以继承。
1. let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo
// 1.拦截操作定义在Prototype对象上面,
所以如果读取obj对象继承的属性时,拦截会生效。
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo
// 1.拦截操作定义在Prototype对象上面,
所以如果读取obj对象继承的属性时,拦截会生效。
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
// 数组的位置参数是-1,就会输出数组的倒数第一个成员。
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
// 数组的位置参数是-1,就会输出数组的倒数第一个成员。
// 利用 Proxy,可以将读取属性的操作(get),
转变为执行某个函数,从而实现属性的链式操作。
var pipe = function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
// 设置 Proxy 以后,达到了将函数名链式使用的效果
转变为执行某个函数,从而实现属性的链式操作。
var pipe = function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
// 设置 Proxy 以后,达到了将函数名链式使用的效果
// 利用get拦截,实现一个生成各种 DOM 节点的通用函数dom。
const dom = new Proxy({}, {
get(target, property) {
return function(attrs = {}, ...children) {
const el = document.createElement(property);
for (let prop of Object.keys(attrs)) {
el.setAttribute(prop, attrs[prop]);
}
for (let child of children) {
if (typeof child === 'string') {
child = document.createTextNode(child);
}
el.appendChild(child);
}
return el;
}
}
});
const el = dom.div({},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
document.body.appendChild(el);
const dom = new Proxy({}, {
get(target, property) {
return function(attrs = {}, ...children) {
const el = document.createElement(property);
for (let prop of Object.keys(attrs)) {
el.setAttribute(prop, attrs[prop]);
}
for (let child of children) {
if (typeof child === 'string') {
child = document.createTextNode(child);
}
el.appendChild(child);
}
return el;
}
}
});
const el = dom.div({},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
document.body.appendChild(el);
3. get 第三个参数的例子
1. proxy.getReceiver 获取第三个参数
2. get方法的第三个参数的例子,
它总是指向原始的读操作所在的那个对象,
一般情况下就是 Proxy 实例。
它总是指向原始的读操作所在的那个对象,
一般情况下就是 Proxy 实例。
const proxy = new Proxy({}, {
get: function(target, key, receiver) {
return receiver;
}
});
proxy.getReceiver === proxy // true
get: function(target, key, receiver) {
return receiver;
}
});
proxy.getReceiver === proxy // true
2. set(target, propKey, value, receiver)
1. 拦截对象属性的设置,
比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
3. has(target, propKey)
1. 拦截propKey in proxy的操作,
返回一个布尔值。
返回一个布尔值。
4. deleteProperty(target, propKey)
1. 拦截delete proxy[propKey]的操作,
返回一个布尔值。
返回一个布尔值。
5. ownKeys(target),拦截:1-4
返回一个数组。该方法返回目标对象所有自身的属性的属性名,
返回一个数组。该方法返回目标对象所有自身的属性的属性名,
1. Object.getOwnPropertyNames(proxy)
1. 方法返回一个由指定对象的
所有自身属性的属性名组成的数组。
(包括不可枚举属性但不包括Symbol值作为名称的属性)
所有自身属性的属性名组成的数组。
(包括不可枚举属性但不包括Symbol值作为名称的属性)
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort());
// ["0", "1", "2", "length"]
console.log(Object.getOwnPropertyNames(arr).sort());
// ["0", "1", "2", "length"]
2. Object.getOwnPropertySymbols(obj)
1. obj:要返回 Symbol 属性的对象。
2.在给定对象自身上找到的所有 Symbol 属性的数组。
2.在给定对象自身上找到的所有 Symbol 属性的数组。
1. 请注意,Object.getOwnPropertyNames()
本身不包含对象的 Symbol 属性,只包含字符串属性。
本身不包含对象的 Symbol 属性,只包含字符串属性。
2. 因为所有的对象在初始化的时候不会包含任何的 Symbol,
除非你在对象上赋值了 Symbol ,
否则Object.getOwnPropertySymbols()只会返回一个空的数组。
除非你在对象上赋值了 Symbol ,
否则Object.getOwnPropertySymbols()只会返回一个空的数组。
3. Object.keys(proxy)
1. 返回结果仅包括目标对象自身的可遍历属性。
4. for...in循环
6. getOwnPropertyDescriptor(target, propKey)
1. 拦截Object.getOwnPropertyDescriptor(),
返回属性的描述对象。
返回属性的描述对象。
2. Object.getOwnPropertyDescriptor()
1. target: 目标对象, prop: 属性名
2. 方法必须返回一个 object 或 undefined。
2. 方法必须返回一个 object 或 undefined。
var p = new Proxy(target, {
getOwnPropertyDescriptor: function(target, prop) {
}
});
3. 示例
var p = new Proxy({ a: 20}, {
getOwnPropertyDescriptor: function(target, prop) {
console.log('called: ' + prop);
return { configurable: true, enumerable: true, value: 10 };
}
});
console.log(Object.getOwnPropertyDescriptor(p, 'a').value); // "called: a"
// 10
var p = new Proxy({ a: 20}, {
getOwnPropertyDescriptor: function(target, prop) {
console.log('called: ' + prop);
return { configurable: true, enumerable: true, value: 10 };
}
});
console.log(Object.getOwnPropertyDescriptor(p, 'a').value); // "called: a"
// 10
7. defineProperty(target, propKey, propDesc)
1. 拦截Object.defineProperty(proxy, propKey, propDesc)、
Object.defineProperties(proxy, propDescs),返回一个布尔值。
Object.defineProperties(proxy, propDescs),返回一个布尔值。
2. Object.defineProperty()
1. 方法会直接在一个对象上定义一个新属性,
或者修改一个对象的现有属性,并返回此对象。
或者修改一个对象的现有属性,并返回此对象。
2. Object.defineProperty(obj, prop, descriptor)
1.obj 要定义属性的对象。
prop 要定义或修改的属性的名称或 Symbol 。
descriptor 要定义或修改的属性描述符。
返回值:被传递给函数的对象。
prop 要定义或修改的属性的名称或 Symbol 。
descriptor 要定义或修改的属性描述符。
返回值:被传递给函数的对象。
2. 这个方法允许修改默认的额外选项(或配置)
默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。
默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。
3. const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
1. // 在对象中添加一个设置了存取描述符属性的示例
var bValue = 38;
Object.defineProperty(o, "b", {
// 使用了方法名称缩写(ES2015 特性)
// 下面两个缩写等价于:
// get : function() { return bValue; },
// set : function(newValue) { bValue = newValue; },
get() { return bValue; },
set(newValue) { bValue = newValue; },
enumerable : true,
configurable : true
});
o.b; // 38
// 对象 o 拥有了属性 b,值为 38
// 现在,除非重新定义 o.b,o.b 的值总是与 bValue 相同
var bValue = 38;
Object.defineProperty(o, "b", {
// 使用了方法名称缩写(ES2015 特性)
// 下面两个缩写等价于:
// get : function() { return bValue; },
// set : function(newValue) { bValue = newValue; },
get() { return bValue; },
set(newValue) { bValue = newValue; },
enumerable : true,
configurable : true
});
o.b; // 38
// 对象 o 拥有了属性 b,值为 38
// 现在,除非重新定义 o.b,o.b 的值总是与 bValue 相同
4. 属性描述符
1. 数据描述符
1.configurable
1. configurable 特性表示对象的属性是否可以被删除,
以及除 value 和 writable 特性外的其他特性是否可以被修改。
以及除 value 和 writable 特性外的其他特性是否可以被修改。
var o = {};
Object.defineProperty(o, 'a', {
get() { return 1; },
configurable: false
});
Object.defineProperty(o, 'a', {
configurable: true
}); // throws a TypeError
Object.defineProperty(o, 'a', {
enumerable: true
}); // throws a TypeError
Object.defineProperty(o, 'a', {
set() {}
}); // throws a TypeError (set was undefined previously)
Object.defineProperty(o, 'a', {
get() { return 1; }
}); // throws a TypeError
// (even though the new get does exactly the same thing)
Object.defineProperty(o, 'a', {
value: 12
}); // throws a TypeError // ('value' can be changed when 'configurable' is false but not in this case due to 'get' accessor)
console.log(o.a); // logs 1
delete o.a; // Nothing happens
console.log(o.a); // logs 1
Object.defineProperty(o, 'a', {
get() { return 1; },
configurable: false
});
Object.defineProperty(o, 'a', {
configurable: true
}); // throws a TypeError
Object.defineProperty(o, 'a', {
enumerable: true
}); // throws a TypeError
Object.defineProperty(o, 'a', {
set() {}
}); // throws a TypeError (set was undefined previously)
Object.defineProperty(o, 'a', {
get() { return 1; }
}); // throws a TypeError
// (even though the new get does exactly the same thing)
Object.defineProperty(o, 'a', {
value: 12
}); // throws a TypeError // ('value' can be changed when 'configurable' is false but not in this case due to 'get' accessor)
console.log(o.a); // logs 1
delete o.a; // Nothing happens
console.log(o.a); // logs 1
如果 o.a 的 configurable 属性为 true,则不会抛出任何错误,并且,最后,该属性会被删除。
2. 当且仅当该属性的 configurable 键值为 true 时,
该属性的描述符才能够被改变,
同时该属性也能从对应的对象上被删除。
默认为 false。
该属性的描述符才能够被改变,
同时该属性也能从对应的对象上被删除。
默认为 false。
2. enumerable
1.当且仅当该属性的 enumerable 键值为 true 时,
该属性才会出现在对象的枚举属性中。
默认为 false。
// enumerable 默认为 false
// 如果使用直接赋值的方式创建对象的属性,则 enumerable 为 true
// 如果enumerable为false,o.b 为undefined
该属性才会出现在对象的枚举属性中。
默认为 false。
// enumerable 默认为 false
// 如果使用直接赋值的方式创建对象的属性,则 enumerable 为 true
// 如果enumerable为false,o.b 为undefined
1. enumerable 定义了对象的属性是否可以在
for...in 循环和 Object.keys() 中被枚举。
for...in 循环和 Object.keys() 中被枚举。
2. var o = {};
Object.defineProperty(o, "a", { value : 1, enumerable: true });
Object.defineProperty(o, "b", { value : 2, enumerable: false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable 默认为 false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则 enumerable 为 true
Object.defineProperty(o, Symbol.for('e'), {
value: 5,
enumerable: true
});
Object.defineProperty(o, Symbol.for('f'), {
value: 6,
enumerable: false
});
for (var i in o) {
console.log(i);
}
// logs 'a' and 'd' (in undefined order)
Object.keys(o); // ['a', 'd']
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true
o.propertyIsEnumerable(Symbol.for('e')); // true
o.propertyIsEnumerable(Symbol.for('f')); // false
var p = { ...o }
p.a // 1
p.b // undefined
p.c // undefined
p.d // 4
p[Symbol.for('e')] // 5
p[Symbol.for('f')] // undefined
Object.defineProperty(o, "a", { value : 1, enumerable: true });
Object.defineProperty(o, "b", { value : 2, enumerable: false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable 默认为 false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则 enumerable 为 true
Object.defineProperty(o, Symbol.for('e'), {
value: 5,
enumerable: true
});
Object.defineProperty(o, Symbol.for('f'), {
value: 6,
enumerable: false
});
for (var i in o) {
console.log(i);
}
// logs 'a' and 'd' (in undefined order)
Object.keys(o); // ['a', 'd']
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true
o.propertyIsEnumerable(Symbol.for('e')); // true
o.propertyIsEnumerable(Symbol.for('f')); // false
var p = { ...o }
p.a // 1
p.b // undefined
p.c // undefined
p.d // 4
p[Symbol.for('e')] // 5
p[Symbol.for('f')] // undefined
2. o.propertyIsEnumerable('a') // true
3. o.propertyIsEnumerable(Symbol.for('e')); // true
3. value
该属性对应的值。
可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。
可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。
4. writable
1.当且仅当该属性的 writable 键值为 true 时,
属性的值,也就是上面的 value,才能被赋值运算符改变。
默认为 false。
属性的值,也就是上面的 value,才能被赋值运算符改变。
默认为 false。
1. 如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,
并且没有属性可以被改变(除了单向改变 writable 为 false)。
当属性不可配置时,不能在数据和访问器属性类型之间切换。
如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,
并且没有属性可以被改变(除了单向改变 writable 为 false)。
当属性不可配置时,不能在数据和访问器属性类型之间切换。
2. 当试图改变不可配置属性(除了 value 和 writable 属性之外)的值时,
会抛出TypeError,除非当前值和新值相同。
会抛出TypeError,除非当前值和新值相同。
2. 当 writable 属性设置为 false 时,
该属性被称为“不可写的”。它不能被重新赋值。
该属性被称为“不可写的”。它不能被重新赋值。
1. var o = {}; // 创建一个新对象
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.
// strict mode
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // throws TypeError: "b" is read-only
return o.b; // returns 2 without the line above
}());
// 如示例所示,试图写入非可写属性不会改变它,也不会引发错误。
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.
// strict mode
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // throws TypeError: "b" is read-only
return o.b; // returns 2 without the line above
}());
// 如示例所示,试图写入非可写属性不会改变它,也不会引发错误。
5. 添加多个属性和默认值
1. 考虑特性被赋予的默认特性值非常重要,通常,
使用点运算符和 Object.defineProperty() 为对象的属性赋值时,
数据描述符中的属性默认值是不同的,
使用点运算符和 Object.defineProperty() 为对象的属性赋值时,
数据描述符中的属性默认值是不同的,
var o = {};
o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
2. 存取描述符
1. get
1. 属性的 getter 函数,如果没有 getter,则为 undefined。
当访问该属性时,会调用此函数。
执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
该函数的返回值会被用作属性的值。
默认为 undefined。
当访问该属性时,会调用此函数。
执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
该函数的返回值会被用作属性的值。
默认为 undefined。
2. set
1. 属性的 setter 函数,如果没有 setter,则为 undefined。
当属性值被修改时,会调用此函数。
该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined。
当属性值被修改时,会调用此函数。
该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined。
3. 一个描述符只能是这两者其中之一;不能同时是两者。
4. 描述符默认值汇总
1. 拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
1. 如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,
并且没有属性可以被改变(除了单向改变 writable 为 false)。
当属性不可配置时,不能在数据和访问器属性类型之间切换。
如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,
并且没有属性可以被改变(除了单向改变 writable 为 false)。
当属性不可配置时,不能在数据和访问器属性类型之间切换。
2. 当试图改变不可配置属性(除了 value 和 writable 属性之外)的值时,
会抛出TypeError,除非当前值和新值相同。
会抛出TypeError,除非当前值和新值相同。
2. 属性值和函数的键 value、get 和 set 字段的默认值为 undefined。
3. 如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,
那么它将被认为是一个数据描述符。
那么它将被认为是一个数据描述符。
4.如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常。
5. 这些选项不一定是自身属性,也要考虑继承来的属性。
为了确认保留这些默认值,在设置之前,可能要冻结 Object.prototype,
明确指定所有的选项,或者通过 Object.create(null) 将 __proto__ 属性指向 null。
为了确认保留这些默认值,在设置之前,可能要冻结 Object.prototype,
明确指定所有的选项,或者通过 Object.create(null) 将 __proto__ 属性指向 null。
1. // 使用 __proto__
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);
2. // 显式
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "static"
});
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "static"
});
3. // 循环使用同一对象
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: null
}
);
d.value = value;
return d;
}
// ... 并且 ...
Object.defineProperty(obj, "key", withValue("static"));
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: null
}
);
d.value = value;
return d;
}
// ... 并且 ...
Object.defineProperty(obj, "key", withValue("static"));
4. // 如果 freeze 可用, 防止后续代码添加或删除对象原型的属性
// (value, get, set, enumerable, writable, configurable)
(Object.freeze||Object)(Object.prototype);
// (value, get, set, enumerable, writable, configurable)
(Object.freeze||Object)(Object.prototype);
5. // 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
value: 0x9f91102,
get() { return 0xdeadbeef; }
});
// 抛出错误 TypeError: value appears only in data descriptors, get appears only in accessor descriptors
Object.defineProperty(o, "conflict", {
value: 0x9f91102,
get() { return 0xdeadbeef; }
});
// 抛出错误 TypeError: value appears only in data descriptors, get appears only in accessor descriptors
5. 自定义 Setters 和 Getters
1. 实现一个自存档对象。当设置temperature 属性时,
archive 数组会收到日志条目。
archive 数组会收到日志条目。
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
2. 设置 getter 总是会返回一个相同的值。
var pattern = {
get: function () {
return 'I alway return this string,whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
};
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);
get: function () {
return 'I alway return this string,whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
};
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);
6. 继承属性
1.如果访问者的属性是被继承的,它的 get 和 set 方法会在子对象的属性被访问或者修改时被调用。
如果这些方法用一个变量存值,该值会被所有对象共享
如果这些方法用一个变量存值,该值会被所有对象共享
1.function myclass() {
}
var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1
}
var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1
2. 这可以通过将值存储在另一个属性中解决。在 get 和 set 方法中,
this 指向某个被访问和修改属性的对象。
this 指向某个被访问和修改属性的对象。
function myclass() {
}
Object.defineProperty(myclass.prototype, "x", {
get() {
return this.stored_x;
},
set(val) {
this.stored_x = val;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined
}
Object.defineProperty(myclass.prototype, "x", {
get() {
return this.stored_x;
},
set(val) {
this.stored_x = val;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined
3. 不像访问者属性,值属性始终在对象自身上设置,
而不是一个原型。
然而,如果一个不可写的属性被继承,它仍然可以防止修改对象的属性。
而不是一个原型。
然而,如果一个不可写的属性被继承,它仍然可以防止修改对象的属性。
1.function myclass() {
}
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
writable: false,
value: 1
});
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1
}
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
writable: false,
value: 1
});
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1
7.链接: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
3. Object.defineProperties()
1. 定义: Object.defineProperties()
方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
Object.defineProperties(obj, props)
2. Object.defineProperties本质上定义了obj 对象上props的可枚举属性相对应的所有属性
1. var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
// etc. etc.
});
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
// etc. etc.
});
8. preventExtensions
1. 拦截Object.preventExtensions(proxy),
返回一个布尔值。
返回一个布尔值。
Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
2. Object.isExtensible(obj)
1. Object.isExtensible() 方法判断一个对象是否是可扩展的
(是否可以在它上面添加新的属性)。
返回Boolean
(是否可以在它上面添加新的属性)。
返回Boolean
2. 用法
1. 默认情况下,对象是可扩展的:即可以为他们添加新的属性。
以及它们的 __proto__ 属性可以被更改。
Object.preventExtensions,
Object.seal
或 Object.freeze
方法都可以标记一个对象为不可扩展(non-extensible)。
以及它们的 __proto__ 属性可以被更改。
Object.preventExtensions,
Object.seal
或 Object.freeze
方法都可以标记一个对象为不可扩展(non-extensible)。
// 新对象默认是可扩展的.
var empty = {};
Object.isExtensible(empty); // === true
// ...可以变的不可扩展.
Object.preventExtensions(empty);
Object.isExtensible(empty); // === false
// 密封对象是不可扩展的.
var sealed = Object.seal({});
Object.isExtensible(sealed); // === false
// 冻结对象也是不可扩展.
var frozen = Object.freeze({});
Object.isExtensible(frozen); // === false
var empty = {};
Object.isExtensible(empty); // === true
// ...可以变的不可扩展.
Object.preventExtensions(empty);
Object.isExtensible(empty); // === false
// 密封对象是不可扩展的.
var sealed = Object.seal({});
Object.isExtensible(sealed); // === false
// 冻结对象也是不可扩展.
var frozen = Object.freeze({});
Object.isExtensible(frozen); // === false
1. Object.preventExtensions(obj)
方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
参数: obj 将要变得不可扩展的对象
返回值: 已经不可扩展的对象
方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
参数: obj 将要变得不可扩展的对象
返回值: 已经不可扩展的对象
1. Object.preventExtensions()仅阻止添加自身的属性。
但其对象类型的原型依然可以添加新的属性。
但其对象类型的原型依然可以添加新的属性。
const object1 = {};
Object.preventExtensions(object1);
try {
Object.defineProperty(object1, 'property1', {
value: 42
});
} catch (e) {
console.log(e);
// expected output: TypeError: Cannot define property property1, object is not extensible
}
Object.preventExtensions(object1);
try {
Object.defineProperty(object1, 'property1', {
value: 42
});
} catch (e) {
console.log(e);
// expected output: TypeError: Cannot define property property1, object is not extensible
}
2. 如果一个对象可以添加新的属性,则这个对象是可扩展的。
Object.preventExtensions()将对象标记为不再可扩展,
这样它将永远不会具有它被标记为不可扩展时持有的属性之外的属性。
Object.preventExtensions()将对象标记为不再可扩展,
这样它将永远不会具有它被标记为不可扩展时持有的属性之外的属性。
2. Object.seal(obj)
方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。
参数: obj将要被密封的对象。
返回值:已经被密封的对象。
方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。
参数: obj将要被密封的对象。
返回值:已经被密封的对象。
var obj = {
prop: function() {},
foo: 'bar'
};
// 可以添加新的属性
// 可以更改或删除现有的属性
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
var o = Object.seal(obj);
o === obj; // true
Object.isSealed(obj); // === true
// 仍然可以修改密封对象的属性值
obj.foo = 'quux';
// 但是你不能将属性重新定义成为访问器属性
// 反之亦然
Object.defineProperty(obj, 'foo', {
get: function() { return 'g'; }
}); // throws a TypeError
// 除了属性值以外的任何变化,都会失败.
obj.quaxxor = 'the friendly duck';
// 添加属性将会失败
delete obj.foo;
// 删除属性将会失败
prop: function() {},
foo: 'bar'
};
// 可以添加新的属性
// 可以更改或删除现有的属性
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
var o = Object.seal(obj);
o === obj; // true
Object.isSealed(obj); // === true
// 仍然可以修改密封对象的属性值
obj.foo = 'quux';
// 但是你不能将属性重新定义成为访问器属性
// 反之亦然
Object.defineProperty(obj, 'foo', {
get: function() { return 'g'; }
}); // throws a TypeError
// 除了属性值以外的任何变化,都会失败.
obj.quaxxor = 'the friendly duck';
// 添加属性将会失败
delete obj.foo;
// 删除属性将会失败
3. Object.freeze() 方法可以冻结一个对象。
一个被冻结的对象再也不能被修改;
冻结了一个对象则不能向这个对象添加新的属性,
不能删除已有属性,
不能修改该对象已有属性的可枚举性、可配置性、可写性,
以及不能修改已有属性的值。
此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
一个被冻结的对象再也不能被修改;
冻结了一个对象则不能向这个对象添加新的属性,
不能删除已有属性,
不能修改该对象已有属性的可枚举性、可配置性、可写性,
以及不能修改已有属性的值。
此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
1. 解释
1. 这个方法返回传递的对象,而不是创建一个被冻结的副本。
2. 数据属性的值不可更改,访问器属性(有getter和setter)也同样(但由于是函数调用,给人的错觉是还是可以修改这个属性)。
3. 如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。
1. 冻结对象
// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }
// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }
var obj = {
prop: function() {},
foo: 'bar'
};
// 新的属性会被添加, 已存在的属性可能
// 会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
var o = Object.freeze(obj);
o === obj; // true
Object.isFrozen(obj); // === true
// 现在任何改变都会失效
obj.foo = 'quux'; // 静默地不做任何事
// 静默地不添加此属性
obj.quaxxor = 'the friendly duck';
// 在严格模式,如此行为将抛出 TypeErrors
function fail(){
'use strict';
obj.foo = 'sparky'; // throws a TypeError
delete obj.quaxxor; // 返回true,因为quaxxor属性从来未被添加
obj.sparky = 'arf'; // throws a TypeError
}
fail();
// 试图通过 Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError.
Object.defineProperty(obj, 'ohai', { value: 17 });
Object.defineProperty(obj, 'foo', { value: 'eit' });
// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }
prop: function() {},
foo: 'bar'
};
// 新的属性会被添加, 已存在的属性可能
// 会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
var o = Object.freeze(obj);
o === obj; // true
Object.isFrozen(obj); // === true
// 现在任何改变都会失效
obj.foo = 'quux'; // 静默地不做任何事
// 静默地不添加此属性
obj.quaxxor = 'the friendly duck';
// 在严格模式,如此行为将抛出 TypeErrors
function fail(){
'use strict';
obj.foo = 'sparky'; // throws a TypeError
delete obj.quaxxor; // 返回true,因为quaxxor属性从来未被添加
obj.sparky = 'arf'; // throws a TypeError
}
fail();
// 试图通过 Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError.
Object.defineProperty(obj, 'ohai', { value: 17 });
Object.defineProperty(obj, 'foo', { value: 'eit' });
// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }
1. 浅冻结
被冻结的对象是不可变的。但也不总是这样。下例展示了冻结对象不是常量对象(浅冻结)
obj1 = {
internal: {}
};
Object.freeze(obj1);
obj1.internal.a = 'aValue';
obj1 = {
internal: {}
};
Object.freeze(obj1);
obj1.internal.a = 'aValue';
2. 对于一个常量对象,整个引用图(直接和间接引用其他对象)只能引用不可变的冻结对象。
冻结的对象被认为是不可变的
,因为整个对象中的整个对象状态(对其他对象的值和引用)是固定的。
注意,字符串,数字和布尔总是不可变的,而函数和数组是对象
冻结的对象被认为是不可变的
,因为整个对象中的整个对象状态(对其他对象的值和引用)是固定的。
注意,字符串,数字和布尔总是不可变的,而函数和数组是对象
2.深冻结
1. 要使对象不可变,需要递归冻结每个类型为对象的属性(深冻结)。
当你知道对象在引用图中不包含任何 环 (循环引用)时,
将根据你的设计逐个使用该模式,否则将触发无限循环。
对 deepFreeze() 的增强将是具有接收路径(例如Array)参数的内部函数,
以便当对象进入不变时,可以递归地调用 deepFreeze() 。
你仍然有冻结不应冻结的对象的风险,例如[window]对象。
当你知道对象在引用图中不包含任何 环 (循环引用)时,
将根据你的设计逐个使用该模式,否则将触发无限循环。
对 deepFreeze() 的增强将是具有接收路径(例如Array)参数的内部函数,
以便当对象进入不变时,可以递归地调用 deepFreeze() 。
你仍然有冻结不应冻结的对象的风险,例如[window]对象。
2. // 深冻结函数.
function deepFreeze(obj) {
// 取回定义在obj上的属性名
var propNames = Object.getOwnPropertyNames(obj);
// 在冻结自身之前冻结属性
propNames.forEach(function(name) {
var prop = obj[name];
// 如果prop是个对象,冻结它
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// 冻结自身(no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
function deepFreeze(obj) {
// 取回定义在obj上的属性名
var propNames = Object.getOwnPropertyNames(obj);
// 在冻结自身之前冻结属性
propNames.forEach(function(name) {
var prop = obj[name];
// 如果prop是个对象,冻结它
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// 冻结自身(no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
2. 冻结数组
1. 数组作为一种对象,被冻结,其元素不能被修改。没有数组元素可以被添加或移除。
let a = [0];
Object.freeze(a); // 现在数组不能被修改了.
a[0]=1; // fails silently
a.push(2); // fails silently
// In strict mode such attempts will throw TypeErrors
function fail() {
"use strict"
a[0] = 1;
a.push(2);
}
fail();
Object.freeze(a); // 现在数组不能被修改了.
a[0]=1; // fails silently
a.push(2); // fails silently
// In strict mode such attempts will throw TypeErrors
function fail() {
"use strict"
a[0] = 1;
a.push(2);
}
fail();
3. 冻结参数只能是对象,不能是简单类型的值
1. 在ES5中,如果这个方法的参数不是一个对象(一个原始值),
那么它会导致 TypeError。在ES2015中,
非对象参数将被视为要被冻结的普通对象,并被简单地返回。
那么它会导致 TypeError。在ES2015中,
非对象参数将被视为要被冻结的普通对象,并被简单地返回。
> Object.freeze(1)
TypeError: 1 is not an object // ES5 code
> Object.freeze(1)
1 // ES2015 code
TypeError: 1 is not an object // ES5 code
> Object.freeze(1)
1 // ES2015 code
4. 对比Object.seal()
用Object.seal()密封的对象可以改变它们现有的属性。
使用Object.freeze() 冻结的对象中现有属性是不可变的。
使用Object.freeze() 冻结的对象中现有属性是不可变的。
2. obj被Object.preventExtensions和Object.seal 标记为不可扩展以后,
obj原来的属性值是可以修改的,并且返回一个新的不可扩展的对象;
obj 被Object.freeze标记为不可扩展以后,原来的值可以也是不能修改的,
Object.freeze方法返回传递的对象,而不是创建一个被冻结的副本。
obj原来的属性值是可以修改的,并且返回一个新的不可扩展的对象;
obj 被Object.freeze标记为不可扩展以后,原来的值可以也是不能修改的,
Object.freeze方法返回传递的对象,而不是创建一个被冻结的副本。
9. setPrototypeOf(target, proto)
1.Object.setPrototypeOf 方法的捕捉器。
1.Object.setPrototypeOf() 方法设置一个指定的对象的原型到另一个对象或 null。
( 即, 内部[[Prototype]]属性)
( 即, 内部[[Prototype]]属性)
1. 更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作
2.应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象
2. 用法:
Object.setPrototypeOf(obj, prototype)
obj:要设置其原型的对象。
prototype:该对象的新原型(一个对象 或 null).
Object.setPrototypeOf(obj, prototype)
obj:要设置其原型的对象。
prototype:该对象的新原型(一个对象 或 null).
1.Object.setPrototypeOf()是ECMAScript 6最新草案中的方法,
相对于 Object.prototype.__proto__ ,
它被认为是修改对象原型更合适的方法
相对于 Object.prototype.__proto__ ,
它被认为是修改对象原型更合适的方法
2. 如果prototype参数不是一个对象或者null,
则什么都不做(例如,数字,字符串,boolean,或者 undefined)。
否则,该方法将obj的[[Prototype]]修改为新的值。
则什么都不做(例如,数字,字符串,boolean,或者 undefined)。
否则,该方法将obj的[[Prototype]]修改为新的值。
3. 使用较旧的 Object.prototype.__proto__ 属性,
我们可以很容易地定义Object.setPrototypeOf 如果它不可用:
我们可以很容易地定义Object.setPrototypeOf 如果它不可用:
if (!Object.setPrototypeOf) {
// 仅适用于Chrome和FireFox,在IE中不工作:
Object.prototype.setPrototypeOf = function(obj, proto) {
if(obj.__proto__) {
obj.__proto__ = proto;
return obj;
} else {
// 如果你想返回 prototype of Object.create(null):
var Fn = function() {
for (var key in obj) {
Object.defineProperty(this, key, {
value: obj[key],
});
}
};
Fn.prototype = proto;
return new Fn();
}
}
}
// 仅适用于Chrome和FireFox,在IE中不工作:
Object.prototype.setPrototypeOf = function(obj, proto) {
if(obj.__proto__) {
obj.__proto__ = proto;
return obj;
} else {
// 如果你想返回 prototype of Object.create(null):
var Fn = function() {
for (var key in obj) {
Object.defineProperty(this, key, {
value: obj[key],
});
}
};
Fn.prototype = proto;
return new Fn();
}
}
}
4. 通过 Object.getPrototypeOf() 和
Object.prototype.__proto__ 的组合允许将一个原型链完整的附加到一个新的原型对象上:
Object.prototype.__proto__ 的组合允许将一个原型链完整的附加到一个新的原型对象上:
5. 向一个原型附加一个链
function Mammal() {
this.isMammal = 'yes';
}
function MammalSpecies(sMammalSpecies) {
this.species = sMammalSpecies;
}
MammalSpecies.prototype = new Mammal();
MammalSpecies.prototype.constructor = MammalSpecies;
var oCat = new MammalSpecies('Felis');
console.log(oCat.isMammal);
// 'yes'
function Animal() {
this.breathing = 'yes';
}
Object.appendChain(oCat, new Animal());
console.log(oCat.breathing);
// 'yes'
this.isMammal = 'yes';
}
function MammalSpecies(sMammalSpecies) {
this.species = sMammalSpecies;
}
MammalSpecies.prototype = new Mammal();
MammalSpecies.prototype.constructor = MammalSpecies;
var oCat = new MammalSpecies('Felis');
console.log(oCat.isMammal);
// 'yes'
function Animal() {
this.breathing = 'yes';
}
Object.appendChain(oCat, new Animal());
console.log(oCat.breathing);
// 'yes'
6. 将一个基本类型转化为对应的对象类型并添加到原型链上
function Symbol() {
this.isSymbol = 'yes';
}
var nPrime = 17;
console.log(typeof nPrime); // 'number'
var oPrime = Object.appendChain(nPrime, new Symbol());
console.log(oPrime); // '17'
console.log(oPrime.isSymbol); // 'yes'
console.log(typeof oPrime); // 'object
this.isSymbol = 'yes';
}
var nPrime = 17;
console.log(typeof nPrime); // 'number'
var oPrime = Object.appendChain(nPrime, new Symbol());
console.log(oPrime); // '17'
console.log(oPrime.isSymbol); // 'yes'
console.log(typeof oPrime); // 'object
7. 给函数类型的对象添加一个链,
并添加一个新的方法到那个链上
并添加一个新的方法到那个链上
function Person(sName) {
this.identity = sName;
}
var george = Object.appendChain(new Person('George'), 'console.log("Hello guys!!");');
console.log(george.identity); // 'George'
george(); // 'Hello guys!!'
this.identity = sName;
}
var george = Object.appendChain(new Person('George'), 'console.log("Hello guys!!");');
console.log(george.identity); // 'George'
george(); // 'Hello guys!!'
2. Reflect.setPrototypeOf()
10. apply(target, object, args)
1. 拦截 Proxy 实例作为函数调用的操作,
比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
11. construct(target, args)
1. 拦截 Proxy 实例作为构造函数调用的操作,
比如new proxy(...args)。
比如new proxy(...args)。
js
1.new 命令
1.
step1:首先创建一个空对象,这个对象将会作为执行 new 构造函数() 之后,返回的对象实例
step2:将上面创建的空对象的原型(__proto__),指向构造函数的 prototype 属性
step3:将这个空对象赋值给构造函数内部的 this,并执行构造函数逻辑
step4:根据构造函数执行逻辑,返回第一步创建的对象或者构造函数的显式返回值
// 构造函数如果有显式返回值,且返回值为对象类型,那么构造函数返回结果不再是目标实例
step1:首先创建一个空对象,这个对象将会作为执行 new 构造函数() 之后,返回的对象实例
step2:将上面创建的空对象的原型(__proto__),指向构造函数的 prototype 属性
step3:将这个空对象赋值给构造函数内部的 this,并执行构造函数逻辑
step4:根据构造函数执行逻辑,返回第一步创建的对象或者构造函数的显式返回值
// 构造函数如果有显式返回值,且返回值为对象类型,那么构造函数返回结果不再是目标实例
1. 实现new
function newFunc(...args) {
// 取出 args 数组第一个参数,即目标构造函数
const constructor = args.shift()
// 创建一个空对象,且这个空对象继承构造函数的 prototype 属性
// 即实现 obj.__proto__ === constructor.prototype
const obj = Object.create(constructor.prototype)
// 执行构造函数,得到构造函数返回结果
// 注意这里我们使用 apply,将构造函数内的 this 指向为 obj
const result = constructor.apply(obj, args)
// 如果造函数执行后,返回结果是对象类型,就直接返回,否则返回 obj 对象
return (typeof result === 'object' && result != null) ? result : obj
}
function newFunc(...args) {
// 取出 args 数组第一个参数,即目标构造函数
const constructor = args.shift()
// 创建一个空对象,且这个空对象继承构造函数的 prototype 属性
// 即实现 obj.__proto__ === constructor.prototype
const obj = Object.create(constructor.prototype)
// 执行构造函数,得到构造函数返回结果
// 注意这里我们使用 apply,将构造函数内的 this 指向为 obj
const result = constructor.apply(obj, args)
// 如果造函数执行后,返回结果是对象类型,就直接返回,否则返回 obj 对象
return (typeof result === 'object' && result != null) ? result : obj
}
1. 构造函数如果有显式返回值,且返回值为对象类型,
那么构造函数返回结果不再是目标实例。如下代码:
function Person(name) {
this.name = name
return {1: 1}
}
const person = new Person(Person, 'lucas')
console.log(person)
// {1: 1}
那么构造函数返回结果不再是目标实例。如下代码:
function Person(name) {
this.name = name
return {1: 1}
}
const person = new Person(Person, 'lucas')
console.log(person)
// {1: 1}
2. new 命令的原理 https://javascript.ruanyifeng.com/oop/basic.html
3. apply、call、bind
1. apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性.
Function.apply(obj,args)方法能接收两个参数
obj: 这个对象将代替Function类里面的this对象
args: 数组,它将作为参数传给Function(args -> arguments)
Function.apply(obj,args)方法能接收两个参数
obj: 这个对象将代替Function类里面的this对象
args: 数组,它将作为参数传给Function(args -> arguments)
1.apply方法妙用
1. Math.max.apply(null,[1,2,3,4]),
Math.min.apply(null, [1,2,3,4])
Math.min.apply(null, [1,2,3,4])
2. Array.prototype.push.apply(arr1, arr2)
// arr1调用了push方法,参数是通过apply将数组装换为参数列表的集合.
// arr1调用了push方法,参数是通过apply将数组装换为参数列表的集合.
3. 实现继承,劫持另外一个对象方法,
继承另一个对象的属性
继承另一个对象的属性
/*定义一个人类*/
function Person({name,age}){
this.name=name;
this.age=age;
}
/*定义一个打印类*/
function Print({bookName}) {
this.bookName = bookName
this.bookFun = function() {
console.log("打印了一本书")
}
}
/*定义一个学生类*/
function Student ({grade}) {
Person.apply(this,arguments)
Print.apply(this, arguments)
this.grade = grade
}
//创建一个学生类
var student=new Student({name:"zhangsan" ,age: 21,bookName: "防狼手册", grade: "大学一年级"});
//测试
console.log("name:"+student.name+"\n"+"age:"+student.age+"\n"+"bookName:"+student.bookName+"\n"+"grade:"+student.grade);
function Person({name,age}){
this.name=name;
this.age=age;
}
/*定义一个打印类*/
function Print({bookName}) {
this.bookName = bookName
this.bookFun = function() {
console.log("打印了一本书")
}
}
/*定义一个学生类*/
function Student ({grade}) {
Person.apply(this,arguments)
Print.apply(this, arguments)
this.grade = grade
}
//创建一个学生类
var student=new Student({name:"zhangsan" ,age: 21,bookName: "防狼手册", grade: "大学一年级"});
//测试
console.log("name:"+student.name+"\n"+"age:"+student.age+"\n"+"bookName:"+student.bookName+"\n"+"grade:"+student.grade);
2. call
1. Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替Function类里this对象
params:这个是一个参数列表,它将作为参数传给Function
obj:这个对象将代替Function类里this对象
params:这个是一个参数列表,它将作为参数传给Function
3. bind
1.bind参数和call一致
2. 不同的是bind返回的是一个新的函数,需要加括号“()”调用
2. 不同的是bind返回的是一个新的函数,需要加括号“()”调用
4.链接: apply方法详解 https://www.cnblogs.com/chenhuichao/p/8493095.html
4. 静态属性、
原型属性、
实例属性
原型属性、
实例属性
1.JS在构造函数和实例化时涉及到的3种属性,
分别是静态属性、原型属性和实例属性。
分别是静态属性、原型属性和实例属性。
1. 静态属性
1.静态属性也可以说对象的私有属性,
只能通过Foo.count(类名.属性)的方式访问,而实例是无法访问的
只能通过Foo.count(类名.属性)的方式访问,而实例是无法访问的
2. function Foo() {}
var f1=new Foo();
Foo.count = 0; //静态属性
Foo.count++;
console.log(f1.count);//undefined
console.log(Foo.count);//1
var f1=new Foo();
Foo.count = 0; //静态属性
Foo.count++;
console.log(f1.count);//undefined
console.log(Foo.count);//1
2. 原型属性
1.原型属性是构造函数和实例都可以访问的,两种访问方式:
Foo.prototype.属性
f1.属性
Foo.prototype.属性
f1.属性
2. function Foo(name) {};
var f1 = new Foo('f1');
Foo.prototype.count = 0;//原型属性
Foo.prototype.count++;
console.log(Foo.prototype.count);//1
console.log(f1.count);//1
var f1 = new Foo('f1');
Foo.prototype.count = 0;//原型属性
Foo.prototype.count++;
console.log(Foo.prototype.count);//1
console.log(f1.count);//1
3. 属性判断:
1. hasOwnProperty方法可以判断属性是否属于对象本身。
一般用来判断属性是属于实例还是原型对象。
2. in操作符判断是否能通过对象访问到属性。
一般用来判断属性是属于实例还是原型对象。
2. in操作符判断是否能通过对象访问到属性。
1. console.log(f1.hasOwnProperty('count'));//false
console.log(Foo.hasOwnProperty('count'));//false
console.log(Foo.prototype.hasOwnProperty('count'));//true
console.log('count' in f1);//true
console.log('count' in Foo);//false
console.log('count' in Foo.prototype);//true
console.log(Foo.hasOwnProperty('count'));//false
console.log(Foo.prototype.hasOwnProperty('count'));//true
console.log('count' in f1);//true
console.log('count' in Foo);//false
console.log('count' in Foo.prototype);//true
2. f1本身并没有count属性,当通过f1访问count时,
在f1中没有找到count属性,就会按着 原型链 向上查找(f1. __proto__ === Foo.prototype),
就会找到Foo.prototype的count属性并返回值。
因此,原型属性是共享给实例的,但是它并不属于实例,
也不属于构造函数,而是属于原型对象的。
在f1中没有找到count属性,就会按着 原型链 向上查找(f1. __proto__ === Foo.prototype),
就会找到Foo.prototype的count属性并返回值。
因此,原型属性是共享给实例的,但是它并不属于实例,
也不属于构造函数,而是属于原型对象的。
3.实例属性
1. function Foo(name) {};
var f1 = new Foo('f1');
Foo.prototype.count = 0; //原型属性
Foo.prototype.count++;
f1.count++; //实例属性
console.log(Foo.prototype.count); //1
console.log(f1.count); //2
var f1 = new Foo('f1');
Foo.prototype.count = 0; //原型属性
Foo.prototype.count++;
f1.count++; //实例属性
console.log(Foo.prototype.count); //1
console.log(f1.count); //2
2. 属性判断:
1. console.log(f1.hasOwnProperty('count')); //true
console.log(Foo.prototype.hasOwnProperty('count')); //true
console.log('count' in f1);//true
console.log('count' in Foo.prototype);//true
console.log(Foo.prototype.hasOwnProperty('count')); //true
console.log('count' in f1);//true
console.log('count' in Foo.prototype);//true
1. function Foo(name) {this.name=name};
var f1 = new Foo('f1');
var f2 = new Foo('f2');
Foo.prototype.count=0;//原型属性
Foo.prototype.names=[];//原型属性
f1.count++;//实例属性
f2.count++;//实例属性
f1.names.push(f1.name);
f2.names.push(f2.name);
console.log(Foo.prototype.count);//0
console.log(f1.count); //1
console.log(f2.count); //1
console.log(f2.name); //'f2',实例属性
console.log(f1.names) ;//["f1", "f2"]
console.log(f1.hasOwnProperty('count')); //true
console.log(f1.hasOwnProperty('name')); //true
console.log(f1.hasOwnProperty('names')); //false
console.log(Foo.prototype.hasOwnProperty('count')); //true
console.log(Foo.prototype.hasOwnProperty('name')); //false
var f1 = new Foo('f1');
var f2 = new Foo('f2');
Foo.prototype.count=0;//原型属性
Foo.prototype.names=[];//原型属性
f1.count++;//实例属性
f2.count++;//实例属性
f1.names.push(f1.name);
f2.names.push(f2.name);
console.log(Foo.prototype.count);//0
console.log(f1.count); //1
console.log(f2.count); //1
console.log(f2.name); //'f2',实例属性
console.log(f1.names) ;//["f1", "f2"]
console.log(f1.hasOwnProperty('count')); //true
console.log(f1.hasOwnProperty('name')); //true
console.log(f1.hasOwnProperty('names')); //false
console.log(Foo.prototype.hasOwnProperty('count')); //true
console.log(Foo.prototype.hasOwnProperty('name')); //false
2. 这个例子只比上一个例子多了一行代码,但是与上面原型属性不同的是
,这时会给f1创建一个count属性,然后将f1.count = Foo.prototype.count(深复制),
再 f1.count+1,此时,不仅原型对象具有count属性,f1本身也具有了count属性。
,这时会给f1创建一个count属性,然后将f1.count = Foo.prototype.count(深复制),
再 f1.count+1,此时,不仅原型对象具有count属性,f1本身也具有了count属性。
3. 访问
实例属性的访问方式也有两种:
f1.count(实例名.属性)
this.name(this.属性,这里的this指向的就是实例本身)
f1.count(实例名.属性)
this.name(this.属性,这里的this指向的就是实例本身)
4. 总结
1. 实例属性是属于实例自己的,而原型属性由于是共享的,
因此引用类型的names属性,可以同时被f1和f2访问/修改,
因此在使用过程中需要特别注意这一点。
因此引用类型的names属性,可以同时被f1和f2访问/修改,
因此在使用过程中需要特别注意这一点。
5. 链接
1. JS在构造函数和实例化时需要注意的3种属性:静态、原型、实例属性
https://blog.csdn.net/jian_zi/article/details/100009788
https://blog.csdn.net/jian_zi/article/details/100009788
2.js代码执行过程
1.代码预编译阶段
分支主题
1.预编译阶段进行变量声明;
2.预编译阶段变量声明进行提升,但是值为 undefined;
js 并不是在我们定义一个变量的时候,声明完成之后立即赋值,而是把所有用到的变量全部声明之后,再到变量的定义的地方进行赋值,变量的声明的过程就是变量的提升
function foo() {
var a;
var b;
a = 1;
console.log(a); // 1
console.log(b); // undefined
b = 2;
}
foo();
变量在声明提升的时候,是全部提升到作用域的最前面,一个接着一个的。但是在变量赋值的时候就不是一个接着一个赋值了,而是赋值的位置在变量原本定义的位置。原本js定义变量的地方,在js运行到这里的时候,才会进行赋值操作,而没有运行到的变量,不会进行赋值操作。
所以变量的提升,提升的其实是变量的声明,而不是变量的赋值。
3. 预编译阶段所有非表达式的函数声明进行提升。
1. 在作用域中,不管是变量还是函数,都会提升到作用域最开始的位置,不同的是,函数的提升后的位置是在变量提升后的位置之后的。
2. 函数只有声明式函数才会被提升,字面量函数不会被提升
4.注意:只有声明的变量和函数才会进行提升,隐式全局变量不会提升
b = 'aaa';
4.链接
分支主题
1.js变量提升与函数提升的详细过程 https://www.cnblogs.com/lvonve/p/9871226.html
2. js变量提升与函数提升的机制
https://segmentfault.com/a/1190000008568071
3.js的预编译 https://blog.csdn.net/csdn_zsdf/article/details/99645903
5.为什么要进行变量和函数声明提升
1.允许声明前调用
2.明确自我递归的语意
3.比较适合解释器分析程序
4.集中处理变量声明,并且关联作用域。
2. 代码执行阶段
作用域在预编译阶段确定,但是作用域链是在执行上下文的创建阶段完全生成的。因为函数在调用时,才会开始创建对应的执行上下文。执行上下文包括了:变量对象、作用域链以及 this 的指向
3. 调用栈
1.我们在执行一个函数时,如果这个函数又调用了另外一个函数,而这个“另外一个函数”也调用了“另外一个函数”,便形成了一系列的调用栈
2.正常来讲,在函数执行完毕并出栈时,函数内局部变量在下一个垃圾回收节点会被回收,该函数对应的执行上下文将会被销毁,这也正是我们在外界无法访问函数内定义的变量的原因。也就是说,只有在函数执行时,相关函数可以访问该变量,该变量在预编译阶段进行创建,在执行阶段进行激活,在函数执行完毕后,相关上下文被销毁。
3.闭包
1.定义: 函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,就形成了闭包。
借助闭包来绑定数据变量,可以保护这些数据变量的内存块在闭包存活时,始终不被垃圾回收机制回收
2.在chrome检查工具中,num 值被标记为 Closure,即闭包变量。
2.对比前述内容,我们知道正常情况下外界是无法访问函数内部变量的,函数执行完之后,上下文即被销毁。但是在(外层)函数中,如果我们返回了另一个函数,且这个返回的函数使用了(外层)函数内的变量,外界因而便能够通过这个返回的函数获取原(外层)函数内部的变量值。这就是闭包的基本原理。
3.内存管理
分支主题
1.分配内存空间
2.读写内存
3.释放内存空间
2.内存空间
栈空间:由操作系统自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈。
堆空间:一般由开发者分配释放,这部分空间就要考虑垃圾回收的问题。
3.内存泄漏
内存泄漏是指内存空间明明已经不再被使用,但由于某种原因并没有被释放的现象。
这是一个非常“玄学”的概念,因为内存空间是否还在使用,
某种程度上是不可判定问题,或者判定成本很高。
内存泄漏危害却非常直观:它会直接导致程序运行缓慢,甚至崩溃。
这是一个非常“玄学”的概念,因为内存空间是否还在使用,
某种程度上是不可判定问题,或者判定成本很高。
内存泄漏危害却非常直观:它会直接导致程序运行缓慢,甚至崩溃。
例子
1. var element = document.getElementById("element")
element.mark = "marked"
// 移除 element 节点
function remove() {
element.parentNode.removeChild(element)
} 我们需要在 remove 方法中添加:element = null,这样更为稳妥。
2. var element = document.getElementById('element')
element.innerHTML = '<button id="button">点击</button>'
var button = document.getElementById('button')
button.addEventListener('click', function() {
// ...
})
element.innerHTML = '' 这段代码执行后,因为 element.innerHTML = '',button 元素已经从 DOM 中移除了,但是由于其事件处理句柄还在,所以依然无法被垃圾回收。我们还需要增加 removeEventListener,防止内存泄漏。
3.function foo() {
var name = 'lucas'
window.setInterval(function() {
console.log(name)
}, 1000)
}
foo() 这段代码由于 window.setInterval 的存在,导致 name 内存空间始终无法被释放,如果不是业务要求的话,一定要记得在合适的时机使用 clearInterval 进行清理。
4. 链接
1. 如何处理 JavaScript 内存泄露
https://mp.weixin.qq.com/s?__biz=MzA5NzkwNDk3MQ==&mid=2650585408&idx=1&sn=4de7b5bbfa969d9587c163e98bc90684&source=41#wechat_redirect
https://mp.weixin.qq.com/s?__biz=MzA5NzkwNDk3MQ==&mid=2650585408&idx=1&sn=4de7b5bbfa969d9587c163e98bc90684&source=41#wechat_redirect
2. [译] 通过垃圾回收机制理解 JavaScript 内存管理 https://juejin.cn/post/6844903764302774279
3. [JavaScript 随笔] 垃圾回收 https://segmentfault.com/a/1190000003641343
4. [译]编写高性能对垃圾收集友好的代码 https://segmentfault.com/a/1190000007887891
5. JavaScript 中 4 种常见的内存泄露陷阱 https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651551451&idx=1&sn=b8447a12eceb467992d432b014d9c026&chksm=8025a11ab752280c7915db4ef726611f645d2fee590d6f0f3f9aeedd55c956454f66f786873a&scene=0#wechat_redirect
6. 记一次网页内存溢出分析及解决实践 https://juejin.cn/post/6844903761744265224
4. 浏览器垃圾回收
1.标记清除
2.引用计数
5.自由变量
自由变量是指没有在相关函数作用域中声明,但是使用了的变量
6.例题
1. const foo = (function() {
var v = 0
return () => {
return v++
}
}())
for (let i = 0; i < 10; i++) {
foo()
}
console.log(foo())
在循环执行时,执行 foo(),这样引用自由变量 10 次,v 自增 10 次,最后执行 foo 时,得到 10。
2. const foo = () => {
var arr = []
var i
for (i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i)
}
}
return arr[0]
}
foo()()
答案:10,这时自由变量为 i,分析类似例题 1:foo() 执行返回的是 arr[0], arr[0] 此时是函数:
3.var fn = null
const foo = () => {
var a = 2
function innerFoo() {
console.log(a)
}
fn = innerFoo
}
const bar = () => {
fn()
}
foo()
bar()
正常来讲,根据调用栈的知识,foo 函数执行完毕之后,其执行环境生命周期会结束,所占内存被垃圾收集器释放,上下文消失。但是通过 innerFoo 函数赋值给 fn,fn 是全局变量,这就导致了 foo 的变量对象 a 也被保留了下来。所以函数 fn 在函数 bar 内部执行时,依然可以访问这个被保留下来的变量对象,输出结果为 2。
4. var fn = null
const foo = () => {
var a = 2
function innerFoo() {
console.log(c)
console.log(a)
}
fn = innerFoo
}
const bar = () => {
var c = 100
fn()
}
foo()
bar()
在 bar 中执行 fn() 时,fn() 已经被复制为 innerFoo,变量 c 并不在其作用域链上,c 只是 bar 函数的内部变量。因此报错 ReferenceError: c is not defined。
5. 如何利用闭包实现单例模式
定义:保证一个类只有一个实例,并提供一个访问它的全局访问点。
function Person(){
this.name = 'lucas'
}
const getSingleInstance = (
function(){
var singleInstance
return function(){
if(singleInstance){
return singleInstance
}
return singleInstance = new Person()
}
}
)()
const instance1 = new getSingleInstance()
const instance2 = new getSingleInstance()
console.log('instance1', instance1)
console.log('instance2', instance2)
在redux 中的 createStore 的实现利用了闭包: 我之前很菜的思路认为,createStore是一个constructor构造函数,通过this.state来保存。后来看代码发现不是的,createStore是一个工厂函数,执行的时候,将用户的reducer,state,闭包储存了起来,返回给用户“引用了闭包的函数”。我想知道这个骚操作叫什么名字,想深入了解一下这种实现私有变量维护的手段。
4. 数据类型
1. 引用类型
对象
数组
判断是否是数组
怎么判断一个数组是数组呢? https://www.cnblogs.com/padding1015/p/9985718.html
JS判断是否是数组的四种做法 https://www.cnblogs.com/echolun/p/10287616.html
Object
0. 解析Object.prototype.toString.call()进行数据类型判断 http://www.webzsky.com/?p=969
1. JavaScript中Object.prototype.toString方法的原理 https://www.jb51.net/article/79941.htm
2. 深入理解Object.prototype.toString方法 https://blog.csdn.net/u014481405/article/details/107914835
3. JavaScript中Object.prototype.toString方法的原理 https://www.cnblogs.com/yyy6/p/9447322.html
1. JavaScript中Object.prototype.toString方法的原理 https://www.jb51.net/article/79941.htm
2. 深入理解Object.prototype.toString方法 https://blog.csdn.net/u014481405/article/details/107914835
3. JavaScript中Object.prototype.toString方法的原理 https://www.cnblogs.com/yyy6/p/9447322.html
nodejs
nodejs 教程 http://nodejs.cn/learn/differences-between-nodejs-and-the-browser
进程、线程
进程与线程的一个简单解释 http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
浏览器渲染过程
1. 浏览器渲染原理与过程 https://www.jianshu.com/p/e6252dc9be32
css
1.什么是BFC
BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
什么是BFC?看这一篇就够了 https://blog.csdn.net/sinat_36422236/article/details/88763187
2.CSS里的BFC和IFC的用法
https://www.cnblogs.com/yyli/p/10847418.html
算法
xly
1.数据结构
1.两数之和
1.暴力遍历
2.缓存
2.非波纳契数列
递归
缓存优化
递推
for循环
3.栈
1.括号
leetcode :括号20、71目录、判断jsx是否合法、加减乘除表达式、js函数嵌套(函数调用栈)
// 括号是否匹配
var isValid = function(s) {
let Stack = []
const left = ['(','{','[']
const right = [')','}',']']
let obj = {
')' :'(',
'}':'{',
']':'['
}
for(let i = 0;i < s.length;i++){
const item = s[i]
if(left.includes(item)){
Stack.push(item)
}
if(right.includes(item)) {
if(Stack.length === 0){
return false
} else {
let s1 = Stack.pop()
if(obj[item] !== s1){
return false
}
}
}
}
console.log('Stack1', Stack)
return !Stack.length
};
var isValid = function(s) {
let Stack = []
const left = ['(','{','[']
const right = [')','}',']']
let obj = {
')' :'(',
'}':'{',
']':'['
}
for(let i = 0;i < s.length;i++){
const item = s[i]
if(left.includes(item)){
Stack.push(item)
}
if(right.includes(item)) {
if(Stack.length === 0){
return false
} else {
let s1 = Stack.pop()
if(obj[item] !== s1){
return false
}
}
}
}
console.log('Stack1', Stack)
return !Stack.length
};
// 71 简化路径
// const obj = {
// "": stack => stack,
// ".": stack => stack,
// "../": stack => {
// stack.pop()
// return stack
// }
// // 也可以放正则,策略模式
// }
var simplifyPath = function(path) {
let stack = []
let paths = path.split("/")
console.log('paths', paths)
for(let i = 0;i < paths.length; i++ ){
const p = paths[i]
// "" . ../
// ~ == wjx/
// @ == src
if(p == '..'){
stack.pop()
}else if (p && p != '.'){
console.log('p', p)
stack.push(p)
}
// obj[p]
}
return '/' + stack.join('/')
}
const str = "/a/../../b/../c//.//"
const r = simplifyPath(str)
console.log('r', r) // /c
// const obj = {
// "": stack => stack,
// ".": stack => stack,
// "../": stack => {
// stack.pop()
// return stack
// }
// // 也可以放正则,策略模式
// }
var simplifyPath = function(path) {
let stack = []
let paths = path.split("/")
console.log('paths', paths)
for(let i = 0;i < paths.length; i++ ){
const p = paths[i]
// "" . ../
// ~ == wjx/
// @ == src
if(p == '..'){
stack.pop()
}else if (p && p != '.'){
console.log('p', p)
stack.push(p)
}
// obj[p]
}
return '/' + stack.join('/')
}
const str = "/a/../../b/../c//.//"
const r = simplifyPath(str)
console.log('r', r) // /c
栈和队列相互转化
4.队列
4.链表
1.单向链表
1. 动态数据结构,不需要做扩容的操作
2. node包括data和next()
1. 链表最后一位next => null
2. 链表第一位叫head
3. 常用递归
3.js实现链表
~~~
1. class Node {
data, next
}
~~~
1. class Node {
data, next
}
~~~
2.
class LinkNodeList{
append,remove,insert
}
class LinkNodeList{
append,remove,insert
}
4. 优缺点
1. 删除数据、插入数据比较方便
2. 没有办法做随机访问,
只能找到对应节点,然后next、next,判断data得到
只能找到对应节点,然后next、next,判断data得到
5. 练习
1. 删除链表元素 203
1.
// (删除头结点时另做考虑)
var removeElements = function(head, val) {
while(head != null && head.val === val){
head = head.next
}
if(head == null) return head
let cur = head
while(cur.next != null){
if(cur.next.val === val){
cur.next = cur.next.next
} else {
cur = cur.next
}
}
return head
};
// (删除头结点时另做考虑)
var removeElements = function(head, val) {
while(head != null && head.val === val){
head = head.next
}
if(head == null) return head
let cur = head
while(cur.next != null){
if(cur.next.val === val){
cur.next = cur.next.next
} else {
cur = cur.next
}
}
return head
};
2.递归
var removeElements = function(head, val) {
if(head === null){
return head
}
if(head.val === val){
head.next = head.next.next
return head
} else {
head = head.next
removeElements(head, val)
}
};
var removeElements = function(head, val) {
if(head === null){
return head
}
if(head.val === val){
head.next = head.next.next
return head
} else {
head = head.next
removeElements(head, val)
}
};
3. 添加虚拟节点
var removeElements = function(head, val) {
let virNode = new ListNode(val - 1)
virNode.next = head
let cur = virNode
while(cur.next != null){
if(cur.next.val === val){
cur.next = cur.next.next
}else {
cur =cur.next
}
}
return virNode.next
}
var removeElements = function(head, val) {
let virNode = new ListNode(val - 1)
virNode.next = head
let cur = virNode
while(cur.next != null){
if(cur.next.val === val){
cur.next = cur.next.next
}else {
cur =cur.next
}
}
return virNode.next
}
2. 206 反转链表
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let cur = head
let prev = null
while(cur != null){
[cur.next, prev, cur] = [prev, cur, cur.next]
// // 1.保存指针
// let next = cur.next
// // 2.改变指针指向
// cur.next = prev
// // 3.移动prev、cur 指针
// prev = cur
// cur = next
}
return prev
};
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let cur = head
let prev = null
while(cur != null){
[cur.next, prev, cur] = [prev, cur, cur.next]
// // 1.保存指针
// let next = cur.next
// // 2.改变指针指向
// cur.next = prev
// // 3.移动prev、cur 指针
// prev = cur
// cur = next
}
return prev
};
3. 141 环形链表判断
// flag
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
while(head){
if(head.flag) return true
head.flag = true
head = head.next
}
return false
};
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
while(head){
if(head.flag) return true
head.flag = true
head = head.next
}
return false
};
// 快慢指针
var hasCycle = function(head) {
let fast = head
let slow = head
while(fast && fast.next){
fast = fast.next.next
slow = slow.next
if(fast === slow){
return true
}
}
return false
};
var hasCycle = function(head) {
let fast = head
let slow = head
while(fast && fast.next){
fast = fast.next.next
slow = slow.next
if(fast === slow){
return true
}
}
return false
};
// 缓存
var hasCycle = function(head) {
let map = new Map()
while(head){
if(map.has(head)){
return true
}
map.set(head)
head = head.next
}
return false
};
var hasCycle = function(head) {
let map = new Map()
while(head){
if(map.has(head)){
return true
}
map.set(head)
head = head.next
}
return false
};
4. 142 用O(1)的空间复杂度返回环形入口的节点
var detectCycle = function(head) {
let slow = head
let fast = head
let start = head
while(fast && fast.next){
fast = fast.next.next
slow = slow.next
if(slow === fast){
while(start&& slow){
if(start === slow){
return start
}
slow = slow.next
start = start.next
}
}
}
return null
};
let slow = head
let fast = head
let start = head
while(fast && fast.next){
fast = fast.next.next
slow = slow.next
if(slow === fast){
while(start&& slow){
if(start === slow){
return start
}
slow = slow.next
start = start.next
}
}
}
return null
};
2. 双向链表
3. 环形链表
1. 约瑟夫环
4. 循环链表
5. 双向循环链表
6. 跳表
1. 二分查找
7.哈希表
1. 数组和链表配合
5. 树
1.定义
function TreeNode(val, left, right) {
this.val = (val===undefined ? 0 : val)
this.left = (left===undefined ? null : left)
this.right = (right===undefined ? null : right)
}
class Tree {
}
this.val = (val===undefined ? 0 : val)
this.left = (left===undefined ? null : left)
this.right = (right===undefined ? null : right)
}
class Tree {
}
练习
练习: 100 相同的树
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function(p, q) {
if(p === null && q === null){
return true
}
if(p === null || q === null ){
return false
}
if(p.val !== q.val){
return false
}
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
};
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function(p, q) {
if(p === null && q === null){
return true
}
if(p === null || q === null ){
return false
}
if(p.val !== q.val){
return false
}
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
};
2. 二叉树
226 翻转二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if(root === null){
return root
}
[root.right, root.left] = [invertTree(root.left), invertTree(root.right)]
return root
};
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if(root === null){
return root
}
[root.right, root.left] = [invertTree(root.left), invertTree(root.right)]
return root
};
3. 遍历树
1.前序遍历
1. 自 -> left -> right
144
1.递归写法
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root, arr = []) {
if(root){
arr.push(root.val)
preorderTraversal(root.left, arr)
preorderTraversal(root.right, arr)
}
return arr
};
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root, arr = []) {
if(root){
arr.push(root.val)
preorderTraversal(root.left, arr)
preorderTraversal(root.right, arr)
}
return arr
};
2. 循环迭代写法
var preorderTraversal = function(root) {
let ret = []
let stack = []
let cur = root
while(cur || stack.length > 0){
while(cur){
ret.push(cur.val)
stack.push(cur)
cur = cur.left
}
cur = stack.pop()
cur = cur.right
}
return ret
};
let ret = []
let stack = []
let cur = root
while(cur || stack.length > 0){
while(cur){
ret.push(cur.val)
stack.push(cur)
cur = cur.left
}
cur = stack.pop()
cur = cur.right
}
return ret
};
1.中序遍历
1. left ->自己 -> right
94
1.后序遍历
1. left -> right -> 自
145
二叉搜索树
1.定义: 每个节点的值都大于左子树所有的值,并且小于右子树的值
1. 验证二叉搜索树 98
1.迭代
2.递归
3. 可以使用中序遍历
235 二叉搜索树的最近公共祖先
236 二叉树的最近公共祖先
1. // 递归
var lowestCommonAncestor = function(root, p, q) {
// 如果p,q 比root都大,就去右边找
if(p.val > root.val && q.val > root.val){
return lowestCommonAncestor(root.right,p, q)
} // 如果p,q比root都小,就去左边找
else if( p.val < root.val &&q.val < root.val ){
return lowestCommonAncestor(root.left, p, q)
} else {
// 如果一个大,一个小,就是找到了
return root
}
}
var lowestCommonAncestor = function(root, p, q) {
// 如果p,q 比root都大,就去右边找
if(p.val > root.val && q.val > root.val){
return lowestCommonAncestor(root.right,p, q)
} // 如果p,q比root都小,就去左边找
else if( p.val < root.val &&q.val < root.val ){
return lowestCommonAncestor(root.left, p, q)
} else {
// 如果一个大,一个小,就是找到了
return root
}
}
2. // 迭代
var lowestCommonAncestor = function(root, p, q) {
while(root){
if(p.val > root.val && q.val > root.val){
root = root.right
} else if( p.val < root.val &&q.val < root.val ){
root = root.left
} else {
return root
}
}
};
var lowestCommonAncestor = function(root, p, q) {
while(root){
if(p.val > root.val && q.val > root.val){
root = root.right
} else if( p.val < root.val &&q.val < root.val ){
root = root.left
} else {
return root
}
}
};
二叉树的深度
1. 二叉树的最大深度 104
// 递归
var maxDepth = function(root) {
if(root == null){
return 0
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};
var maxDepth = function(root) {
if(root == null){
return 0
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};
// 迭代
3. 架构
1.虚拟dom
prop、children递归, React15
节点tree多了容易卡顿、不好中止
2. filber
1. 链表结构,diff过程可中断,继续diff
2. React16
4. 练习
1. 全排列
1. 递归 + 回溯
2. leetcode 46
广度优先遍历
深度优先遍历
function permute (nums){
let len = nums.length
let list = []
if(len == 0) return list
let used = new Array(len)
let path = []
dfs(nums, len, 0, path, used, list)
return list
}
function dfs(nums,len, depth, path, used, list){
console.log('depth111', path)
if(depth == len){
list.push([...path])
return
}
for(let i = 0;i < len;i ++){
if(!used[i]){
path.push(nums[i])
used[i] = true
console.log('递归之前 =》', path, used)
dfs(nums, len, depth + 1, path, used, list)
used[i] = false
path.pop()
console.log('递归之后 =====》', path, used)
}
}
}
permute([1,2,3])
let len = nums.length
let list = []
if(len == 0) return list
let used = new Array(len)
let path = []
dfs(nums, len, 0, path, used, list)
return list
}
function dfs(nums,len, depth, path, used, list){
console.log('depth111', path)
if(depth == len){
list.push([...path])
return
}
for(let i = 0;i < len;i ++){
if(!used[i]){
path.push(nums[i])
used[i] = true
console.log('递归之前 =》', path, used)
dfs(nums, len, depth + 1, path, used, list)
used[i] = false
path.pop()
console.log('递归之后 =====》', path, used)
}
}
}
permute([1,2,3])
var backTrack = function(list, temp, nums) {
if(temp.length === nums.length){
console.log('temp2',temp)
return list.push(temp)
}
for(let i = 0;i < nums.length; i++){
if(temp.includes(nums[i])) continue
temp.push(nums[i])
backTrack(list, temp, nums)
temp.pop()
}
};
function permute (nums){
let list = []
backTrack(list, [], nums)
return list
}
const res = permute([1,2,3])
console.log('res1',res)
if(temp.length === nums.length){
console.log('temp2',temp)
return list.push(temp)
}
for(let i = 0;i < nums.length; i++){
if(temp.includes(nums[i])) continue
temp.push(nums[i])
backTrack(list, temp, nums)
temp.pop()
}
};
function permute (nums){
let list = []
backTrack(list, [], nums)
return list
}
const res = permute([1,2,3])
console.log('res1',res)
新布置
37 数独问题
3. 皇后问题 51
2. 单词搜索 79
912 冒泡排序、快排
快排需要占用额外的存贮空间
26 删除数组中的重复项
26 数组原地去重返回length
11 盛水
15. 179 三数之和
js 自带的排序是 插入排序(数据量小)和快排的结合(数据量大)n*logn
哈希表
位运算
按位异或应用 leetCode 136
2.算法思想
1.二分思想
1.leftpad
374
复杂的 log2n
50
69
2.动态规划(动态递推)
1.思路点
1. 从下至上
2. 子问题叠加
2. 练习
120 三角形最小路径和
动态规划,全局最优解,DP,从下至上,找到最优解
递归自上而下
贪心算法
局部当前最优解
背包问题
322 零钱兑换
贪心算法
动态规划
硬币 4,3,1
贪心算法
4,1,1
动态规划
3,3
72 编辑距离
理解虚拟dom 有帮助
10 正则表达式匹配
3. 哈希表
4. 堆
最大堆
流式数据,打印前10名
5. 链表
慢
跳表
数组、链表、跳表时间复杂度分析
logn
146 LRU缓存 https://leetcode-cn.com/problems/lru-cache/
为啥 redis 使用跳表(skiplist)而不是使用 red-black? https://www.zhihu.com/question/20202931
Linked List 的标准实现代码 https://www.geeksforgeeks.org/implementing-a-linked-list-in-java-using-class/
跳跃表 https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html
Java 源码分析(LinkedList) http://developer.classpath.org/doc/java/util/LinkedList-source.html
Java 源码分析(ArrayList)http://developer.classpath.org/doc/java/util/ArrayList-source.html
6. 优先队列
插入操作O(1)
取出操作 O(logn)
按照元素优先级取出
7.练习
20 有效括号
有最近相关性可以用栈来解决
yx
ds总结
算法小抄 https://labuladong.gitbook.io/algo/guan-yu-zuo-zhe
设计模式
1. 策略模式
https://zhuanlan.zhihu.com/p/146500964
2. 发布订阅
https://zhuanlan.zhihu.com/p/144832214
3. 单例模式
https://zhuanlan.zhihu.com/p/145271407
0 条评论
下一页