首页
统计
赞助
留言
关于
更多
友链
壁纸
直播
Search
1
Joe — 一款个人类型Typecho主题
32,198 阅读
2
Joe开源目录程序系统
11,868 阅读
3
Typecho自定义后台编辑器功能
8,274 阅读
4
Joe主题实现自动更新
7,527 阅读
5
Joe主题自定义搭配色教程
4,435 阅读
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
登录
Search
https://78.al
累计撰写
76
篇文章
累计收到
3,727
条评论
首页
栏目
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
页面
统计
赞助
留言
关于
友链
壁纸
直播
搜索到
55
篇与
WEB前端
的结果
2022-07-13
JS压缩图片并保留图片元信息
JS实现图片压缩比较简单,但是图片经过压缩后,压缩后的图片的元信息(拍摄时间、设备、地点)等会丢失掉,如果在特殊场景中需要使用这些元信息的话,就会出现问题了,因此需要将未压缩前的图片元信息填充至压缩后的图片中,以下是实现代码// 封装一个获取变量的数据类型函数 const getType = (data: unknown): string => { const toStingResult = Object.prototype.toString.call(data); const type = toStingResult.replace(/^\[object (\w+)\]$/, "$1"); return type.toLowerCase(); }; // 封装一个将 Base64 的字符串转换成 Blob 流的函数 const dataURLtoBlob = (dataURL: string): Blob | null => { const dataType = getType(dataURL); if (dataType !== "string") return null; const arr = dataURL.split(","); if (!arr[0] || !arr[1]) return null; const code = window.atob(arr[1]); const mimeExpRes = arr[0].match(/:(.*?);/); if (!mimeExpRes) return null; let len = code.length; const mime = mimeExpRes[1]; if (!mime) return null; const ia = new Uint8Array(len); while (len--) ia[len] = code.charCodeAt(len); return new Blob([ia], { type: mime }); }; // 利用规律编码格式把里面的标记以及值等分割开来,传原图片的 ArrayBuffer 进来 const getSegments = (arrayBuffer: ArrayBuffer): number[][] => { if (!arrayBuffer.byteLength) return []; let head = 0; let length, endPoint, seg; const segments = []; const arr = [].slice.call(new Uint8Array(arrayBuffer), 0); while (1) { if (arr[head] === 0xff && arr[head + 1] === 0xda) break; if (arr[head] === 0xff && arr[head + 1] === 0xd8) { head += 2; } else { length = arr[head + 2] * 256 + arr[head + 3]; endPoint = head + length + 2; seg = arr.slice(head, endPoint); head = endPoint; segments.push(seg); } if (head > arr.length) break; } return segments; }; // 传入上面 getSegments 的返回值,取出EXIF图片元信息 const getEXIF = (segments: number[][]): Array<number> => { if (!segments.length) return []; let seg: Array<number> = []; for (let i = 0; i < segments.length; i++) { const item = segments[i]; if (item[0] === 0xff && item[1] === 0xe1) { seg = seg.concat(item); } } return seg; }; // 将 getEXIF 获取的元信息,插入到压缩后的图片的 Blob 中,传 压缩图片后的 Blob 流 const insertEXIF = (blob: Blob, exif: number[]): Promise<Blob> => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = () => { const arr = [].slice.call(new Uint8Array(fileReader.result as ArrayBuffer), 0); if (arr[2] !== 0xff || arr[3] !== 0xe0) { return reject(new Error("Couldn't find APP0 marker from blob data")); } const length = arr[4] * 256 + arr[5]; const newImage = [0xff, 0xd8].concat(exif, arr.slice(4 + length)); const uint8Array = new Uint8Array(newImage); const newBlob = new Blob([uint8Array], { type: "image/jpeg" }); resolve(newBlob); }; fileReader.readAsArrayBuffer(blob); }); }; // 压缩图片逻辑 const compressImage = (file: File, quality: number): Promise<Blob | null> => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = () => { const img = new Image(); img.src = fileReader.result as string; img.onload = () => { const { width, height } = img; const canvas = window.document.createElement("canvas"); const ctx = <CanvasRenderingContext2D>canvas.getContext("2d"); canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); const fileData = canvas.toDataURL("image/jpeg", quality); const fileBlob = dataURLtoBlob(fileData); resolve(fileBlob); }; img.onerror = (err) => reject(err); }; fileReader.onerror = (err) => reject(err); fileReader.readAsDataURL(file); }); }; /** * @description: 完整的压缩图片,最终对外暴露的函数 * @param {File} file * @param {number} quality 0 - 1 * @return {Promise<File>} */ export default (file: File, quality = 0.5): Promise<File> => { return new Promise((resolve, reject) => { const dataType = getType(file); if (dataType !== "file") return reject(new Error(`Expected parameter type is file, You passed in ${dataType}`)); if (file.type.indexOf("image") === -1) return resolve(file); // 压缩图片 compressImage(file, quality) .then((compressdBlob) => { if (!compressdBlob) return resolve(file); const fileReader = new FileReader(); fileReader.onload = () => { // 获取图片元信息 const segments = getSegments(fileReader.result as ArrayBuffer); const exif = getEXIF(segments); // 没有元数据的时候, 直接抛出压缩后的图片 if (!exif.length) return resolve(new File([compressdBlob], file.name, { type: file.type, lastModified: file.lastModified })); // 有元数据的时候, 将元信息合并到压缩图片里 insertEXIF(compressdBlob, exif) .then((newBlob) => resolve(new File([newBlob], file.name, { type: file.type, lastModified: file.lastModified }))) .catch(() => resolve(file)); }; fileReader.onerror = () => resolve(file); fileReader.readAsArrayBuffer(file); }) .catch(() => resolve(file)); }); };
2022年07月13日
1,935 阅读
13 评论
18 点赞
2022-07-13
记录部分安卓手机 input type="file"的onchange不能触发问题
今天写了一个文件上传,发现在部分安卓手机上触发不了onchange事件,代码如下:<input type="file" id="input" /> <script> // 在部分安卓手机不触发 input.onchange = () => { alert(123) } </script>经过不断测试后发现,在标签上补上accept="image/*"后可解决该问题,修复后的代码如下:<input type="file" accept="image/*" id="input" /> <script> input.onchange = () => { alert(123) } </script>
2022年07月13日
904 阅读
1 评论
12 点赞
2021-08-04
Vue 2与Vue 3在自定义组件v-model上的区别
在vue开发中,通常会对一个自定义的组件进行封装,并实现v-model双向绑定功能在 Vue 2 中,通常这样实现父组件<template> <Child v-model="number"></Child> </template> <script> export default { data() { return { number: 0 } }, components: { Child: () => import("./Child.vue") } } </script>子组件<template> <button @click="handleClick">{{ value }}</button> </template> <script> export default { props: { value: Number }, methods: { handleClick() { // 通过emit一个input事件出去,实现 v-model this.$emit('input', this.value + 1) } } } </script>在 vue 3 中,通过这样实现父组件<template> <Child v-model="number"></Child> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; export default defineComponent({ setup() { const number = ref(0); return { number }; }, }); </script>子组件<template> <button @click="handleClick">{{ value }}</button> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: { // 更换成了 modelValue modelValue: Number }, setup(props, { emit }) { // 关闭弹出层 const handleClick = () => emit('update:modelValue', props.modelValue + 1); return { handleClick }; }, }); </script>
2021年08月04日
1,189 阅读
14 评论
20 点赞
2021-07-28
Vue.set与this.$set源码
Vue.set()和this.$set()应用的场景在 Vue 2.X 项目开发中,有时候需要对数组进行修改,或是对对象新增一个属性,但是发现页面并不会同步更新。例如:const vm = new Vue({ data: { arr: [1, 2], obj: { a: 3 } } }); vm.$data.arr[0] = 3; // 页面不会发生改变 vm.$data.obj.b = 3; // 页面不会发生改变此时就需要使用到 Vue.set() 或 this.$set()。这个2个的使用方法一样,不过一个是挂载在Vue身上,一个挂载在Vue.prototype上// Vue.set import { set } from '../observer/index' Vue.set = set // this.$set import { set } from '../observer/index' Vue.prototype.$set = se使用Vue.set或this.$set来实现页面不更新的问题:const vm = new Vue({ data: { arr: [1, 2], obj: { a: 3 } } }); // 修改数组 Vue.set(vm.$data.arr, 0, 3); vm.$set(vm.$data.arr, 0, 3); // 对象新增属性 Vue.set(vm.$data.obj, 'b', 3); vm.$set(vm.$data.obj, 'b', 3);set函数的源代码../observer/indexfunction set (target: Array<any> | Object, key: any, val: any): any { // isUndef(target):用于判断传入的target是否是undefined或null // isPrimitive(target):用于判断传入的target是否是原始数据类型 // 如果是 null 或者是 undefined 时,抛出异常 if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 如果传入了一个数组,并且传入的key是数组的有效值 if (Array.isArray(target) && isValidArrayIndex(key)) { // 用于设置数组的最大length // Math.max(target.length, key):取数组的length和key二者中的最大值 target.length = Math.max(target.length, key) // 直接调用数组身上的splice方法,因为Vue里变异了数组方法,调用会触发dep.notify() target.splice(key, 1, val) return val } // 接下来就是对象的判断了 // 如果传入的 key 在原对象中,说明已经是响应式了,直接修改即可 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 这里到最后的代码,都是对象不在原对象上,是新增的属性值 const ob = (target: any).__ob__ // 如果是Vue实例对象,或根数据对象,则抛出警告 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 如果没有 __ob__ 表示当前 target 不是响应式的,那么直接赋值 if (!ob) { target[key] = val return val } // 剩下就是给响应式对象增加一个 key 新属性,并通知更新 defineReactive(ob.value, key, val) ob.dep.notify() return val }
2021年07月28日
631 阅读
2 评论
11 点赞
2021-07-06
Vue 二次封装组件的方法
介绍有的时候我们全局引用 UI 框架(类似于 ElementUI)的时候,有些简单的组件我们想进行二次封装,并确保能传递所需的 props 和事件,这时候就需要用到 vue 实例上的两个属性:$attrs 和$listeners。代码二次封装的组件<template> <!-- v-bind="$attrs":会将传递过来的属性属性值排放在这里,可以看成react里的{...props}展开运算符类似 v-on="$listeners":父组件绑定的@事件会传递到这里 --> <el-input v-bind="$attrs" v-on="$listeners"></el-input> </template>父组件使用这个组件<template> <HelloWorld placeholder="请输入内容111" @input="handleInput"/> </template> <script> export default { methods: { handleInput() { console.log(1); } }, components: { HelloWorld: () => import('@/components/HelloWorld.vue') } }; </script>
2021年07月06日
830 阅读
3 评论
8 点赞
2021-07-02
HTTP缓存
前端缓存前端缓存可分为两大类:http缓存和浏览器缓存。我们今天重点讲的是http缓存,所以关于浏览器缓存大家自行去查阅。下面这张图是前端缓存的一个大致知识点:HTTP 缓存策略分为两种: 强缓存 和 协商缓存 ,这两种缓存策略都是服务端设置 HTTP Header 来实现的(一)强缓存强缓存的意思很简单,直接从浏览器缓存过的本地进行读取,不会去请求服务器。例如请求一个图片,当缓存后,第二次访问,直接从本地去拿,不会再去请求这个资源,可以节省服务器资源。可以通过三种方式来设置强缓存Expires:服务端在响应头中设置一个 GMT 格式的到期时间。客户端的本地时间小于响应头的 Expires 时间,那么会从本地进行读取,不会去请求服务器。如果超过了,那么就去请求服务器去获取最新资源。但是就是因为根据本地时间进行判断,本地时间可以随便修改,所以这种缓存机制有漏洞,会与服务端时间有偏差,为了解决这个问题,就出现了下面的 Cache-contorlCache-control:他和Expires不一样,Expires是直接设置一个时间戳就行了,而Cache-control可以设置下面这几种属性:max-age:这个用于设置一个滑动时间,例如设置 max-age=30 表示客户端时间向后滑动30秒,在这30秒内都是强缓存,不会去请求服务器s-maxage:这个和上面的一样,只不过这个设置的是代理服务器的缓存时间privte:这个表示缓存只能被客户端的浏览器缓存,不能被代理服务器缓存public:这个表示缓存既可以被浏览器缓存,也可以被代理服务器缓存no-store:这个属性表示不缓存,在任何情况下,都是与服务器进行最新的交互no-cache:这个并非不缓存的意思,这个表示强制进行协商缓存,会在下面描述(二)协商缓存协商缓存表示在使用本地的缓存之前,会先向服务器发一个请求,与服务器协商当前浏览器的缓存是否已经过期了,如果没过期,那么就使用本地的资源,如果过期了就去请求最新资源。协商缓存主要是解决强缓存资源不能及时更新的问题,协商缓存服务端可以通过2种设置来实现:第一种:last-modified 配合 If-Modified-Since例如,客户端请求一个 03.jpg,服务端接收到这个请求后,会读取这个文件的最后修改时间,然后设置到响应头中,设置的参数就是 last-modified,参数值是文件最后修改的时间戳。客户端第二次请求 03.jpg 这个文件的时候,会带上一个 If-Modified-Since 参数,服务端能拿到这个参数与last-modified进行比对,如果一致,那么就返回304状态,否则就去请求最新的文件,使用nodejs实现这个代码:(注意协商缓存需要设置Cache-Control为no-cache,表示设置成协商缓存)... // 判断客户端请求的是03这个图片 if(pathname === '/img/03.jpg') { // 读取 03 图片的最后修改时间 const { mtime } = fs.statSync("./img/03.jpg") // 判断客户端发送过来的if-modified-since是否与mtime一致,如果一致就直接返回304 if(req.headers['if-modified-since'] === mtime.toUTCString()) { res.statusCode = 304 res.end() } else { // 如果不一致,那么就请求最新的资源返回给客户端 const data = fs.readFileSync("./img/03.jpg") // 这2句代码是设置协商缓存 res.setHeader("last-modified", mtime.toUTCString()) res.setHeader("Cache-Control", "no-cache") res.end(data) } } ...上面的 last-modified 配合 If-Modified-Since在使用时有些弊端,例如将03.jpg修改成04.jpg,再改回03.jpg。此时这个文件其实是没有变化的,但是最后修改时间更改了,因此客户端就需要重新请求,因此就出现了下面的第二种使用Etag的方式第二种:Etag 配合 If-None-MatchEtag实现的方式服务端是为文件生成一个指纹,类似于MD5字符串。接着响应头中塞进 Etag 参数,参数的值就是计算出的字符串,客户端接收到后,第二次请求会带上一个 If-None-Match 的参数,接着服务端和上面第一种方式一样进行比对,nodejs的实现代码如下:// 引入 etag 模块 const etag = reqiure("etag") ... if(pathname === '/img/03.jpg') { const data = fs.readFileSync("./img/03.jpg") // 获取生成的etag字符串 const etag = etag(data) // 判断客户端发送的 If-None-Match 与服务端是否一致 if(req.headers['if-none-match'] === etag) { res.statusCode = 304 res.end() } else { // 如果不一致,那么就请求最新的资源返回给客户端 const data = fs.readFileSync("./img/03.jpg") // 这2句代码是设置协商缓存 res.setHeader("etag", etag) res.setHeader("Cache-Control", "no-cache") res.end(data) } } ...
2021年07月02日
1,006 阅读
5 评论
15 点赞
2021-06-28
Vue底层判断标签的性能优化方法
在vue中,如果写div、span等正常的html标签,vue会解析成传统的html标签,但当写不是这些标签的时候,vue会认为他是一个组件,例如:<Custom></Custom>。是如何做到这种判断的呢,首先自己来实现一个这样的判断const tags = 'div,span,img,a'.split(",") function checkTag(tag) { return tags.some(item => item === tag) } console.log(checkTag('Custom')); // false console.log(checkTag('div')); // true console.log(checkTag('a')); // true这里的实现方案有很多,可以用for、some、forEach等,但是都是离不开循环,思考这样的一个问题,传入一个a,a在字符串最后一个位置,所以会循环4次来判断是否包含a,如果页面上的标签极多,甚至会有上万次的循环再来查看Vue实现这个的方式,大致源码如下const tags = 'div,span,img,a' function makeMap(str) { var map = Object.create(null); var list = str.split(','); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } return function (val) { return map[val]; } } const isHtmlTag = makeMap(tags) isHtmlTag('div') isHtmlTag('Custom') isHtmlTag('a')这里可以看出,实现出一个柯里化函数,并且也是一个闭包状态。此时,在第一次调用时,会循环一次,后续不论在去判断什么标签,都不会再去循环,因为第一次的循环结果利用闭包已经存在了内存里,这就是闭包能带来的性能优化
2021年06月28日
626 阅读
4 评论
10 点赞
2021-06-28
原生JS模拟虚拟dom,虚拟dom转真实dom
<div id="root" class="aaa"> <div class="title">11</div> <div class="title">222</div> <ul> <li>111</li> <li>222</li> <li>333</li> </ul> </div> <script type="text/javascript"> class VNode { constructor(name, attrs, value, type) { this.name = name this.attrs = attrs this.value = value this.type = type this.children = [] } addChildren(node) { this.children.push(node) } } function create(node) { let _vnode = null if (node.nodeType === 1) { const attributes = [...node.attributes] const attrs = attributes.reduce((prev, curr) => { prev[curr.name] = curr.value return prev }, {}); _vnode = new VNode(node.nodeName.toLowerCase(), attrs, null, node.nodeType) for (let item of node.childNodes) { _vnode.addChildren(create(item)) } } else if (node.nodeType === 3) { _vnode = new VNode(node.nodeName.toLowerCase(), null, node.nodeValue, node.nodeType) } return _vnode } let vnode = create(document.querySelector("#root")) console.log(vnode); function parse(node) { let nodeEl = null if (node.type === 1) { nodeEl = document.createElement(node.name) for (let key in node.attrs) nodeEl.setAttribute(key, node.attrs[key]) node.children.forEach(item => { nodeEl.appendChild(parse(item)) }); } else { nodeEl = document.createTextNode(node.value) } return nodeEl } let dom = parse(vnode) console.log(dom); </script>
2021年06月28日
730 阅读
4 评论
6 点赞
2021-06-28
Vue 2.X 拓展图片预览插件
Preview.jsimport ToastComponent from './Toast.vue' const Toast = {} Toast.install = Vue => { const ToastConstructor = Vue.extend(ToastComponent) const instance = new ToastConstructor() Vue.prototype.$Toast = function (options) { if (!options) { return console.error('Please Pass In The Data / 请传入内容') } if (options.constructor !== Object) { return console.error('Please Pass In The Object Format / 请传入对象格式') } options.msg = options.msg || '默认消息' options.duration = options.duration || 3000 if (document.getElementsByClassName('toast').length === 0) { instance.$mount(document.createElement('div')) document.body.appendChild(instance.$el) instance.msg = options.msg instance.visible = true setTimeout(() => { instance.visible = false document.body.removeChild(instance.$el) }, options.duration) } } } export default ToastPreview.vue<template> <transition name="bounce"> <div class="toast" v-show="visible"> <span>{{ msg }}</span> </div> </transition> </template> <script> export default { data() { return { msg: '', visible: false } } } </script> <style lang="css" scoped> .bounce-enter-active { animation: bounce-in 0.5s linear; } .bounce-leave-active { animation: bounce-in 0.5s linear reverse; } @keyframes bounce-in { 0% { transform: translate3d(-50%, 0, 0) scale(0); } 25% { transform: translate3d(-50%, 0, 0) scale(0.55); } 50% { transform: translate3d(-50%, 0, 0) scale(1); } 75% { transform: translate3d(-50%, 0, 0) scale(0.85); } 100% { transform: translate3d(-50%, 0, 0) scale(1); } } .toast { position: fixed; z-index: 5201314; top: 50%; left: 50%; transform: translate3d(-50%, 0, 0); background: rgba(0, 0, 0, 0.75); padding: 0 32px; height: 50px; line-height: 50px; color: #fff; user-select: none; border-radius: 4px; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2); font-size: 14px; letter-spacing: 1px; } </style>
2021年06月28日
932 阅读
2 评论
16 点赞
2021-06-28
chrome闭包断点
首先呢,必须搞清楚闭包这个概念:闭包其实是一个特殊的对象,他由两部分组成,一个是执行上下文(代号A),以及在该执行上下文中创建的函数(代号B),当B执行时,如果访问了A中变量对象的变量,那么闭包就产生了。下面这个例子会产生闭包function add(x) { return function _add(y){ return x + y } } var sum = add(2)(3)上面这个例子毫无疑问肯定是 5那么使用chrome浏览器单步调试一下将断点打到 var sum = add(2)(3) 这里Brealpoints 表示当前打的断点是什么CallStack 表示当前函数调用栈Scope 表示当前作用域Scope 里面的 Local 表示当前活动对象Scope 里面的 Closure 表示闭包图上一直点击红色箭头所指按钮表示单点调试,代码一步一步执行,等执行到return那里的时候,右侧 Scope 里会多出一个 Closure 属性,括号内的add表示当前闭包函数是add,闭包产生的值是 x = 2有时候调试会发现没有这个 Closure 属性,例如下面这个例子const obj = { name: '我是需要被绑定改变this指向的对象' } function fn() { } Function.prototype.bind = function (sbbbbb) { const bind = Symbol(); sbbbbb[bind] = this return function () { sbbbbb[bind](); } } fn.bind(obj);这个例子是 bind 改变 this 指向的实现方式,将断点打向 fn.bind(obj) 接着看动态图在动画的过程中,当走到 sbbbbb[bind]() 的时候,Local 里面会多出一个 Return value: ƒ () 的属性,展开这个属性,会发现有个 [[Scopes]] 的属性,接着展开 [[Scopes]],里面有个 Closure (Function.bind) {sbbbbb: {…}, bind: Symbol()} 就表示当前产生的闭包
2021年06月28日
888 阅读
7 评论
7 点赞
2021-06-23
原型继承和 Class 继承
⾸先先来讲下 class ,其实在 JS 中并不存在类, class 只是语法糖,本质还是函数class Person {} Person instanceof Function // true组合继承function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = new Parent() const child = new Child(1) child.getValue() // 1 child instanceof Parent // true{callout color="#6db9f3"}以上继承的⽅式核⼼是在⼦类的构造函数中通过 Parent.call(this) 继承⽗类的属性,然后改变⼦类的原型为 new Parent() 来继承⽗类的函数。这种继承⽅式优点在于构造函数可以传参,不会与⽗类引⽤属性共享,可以复⽤⽗类的函数,但是也存在⼀个缺点就是在继承⽗类函数的时候调⽤了⽗类构造函数,导致⼦类的原型上多了不需要的⽗类属性,存在内存上的浪费{/callout}寄⽣组合继承这种继承⽅式对组合继承进⾏了优化,组合继承缺点在于继承⽗类函数时调⽤了构造函数,我们只需要优化掉这点就⾏了function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }) const child = new Child(1) child.getValue() // 1 child instanceof Parent // trueclass继承class Parent { constructor(value) { this.val = value } getValue() { console.log(this.val) } } class Child extends Parent { constructor(value) { super(value) this.val = value } } let child = new Child(1) child.getValue() // 1 child instanceof Parent // true
2021年06月23日
425 阅读
3 评论
16 点赞
2021-06-18
前端面试题二
类数组的length{callout color="#8cd0f3"} var obj = { "2" : 3, "3" : 4, "length" : 2, "splice" : Array.prototype.splice, "push" : Array.prototype.push } obj.push(1) obj.push(2) console.log(obj) // { 2: 1, 3: 2, length: 4 }这题主要是将对象转换成了类数组,push方法其实就是在数组length的地方,添加一个元素 var obj = { "2" : 3, "3" : 4, "length" : 2, // 这个splice方法,我在jquery源码里也看到过这样写,贼牛逼 // 加上splice 或 push 后,直接将obj改成了Object创建的伪数组形式 "splice" : Array.prototype.splice, "push" : Array.prototype.push } obj[obj.length++] = 1 obj[obj.length++] = 2 // 这里因为改成了类数组,但是索引0 和 1的位置没有值,所以就变成了empty * 2,代表有2个空元素在这儿占位 console.log(obj) // Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]{/callout}操作符优先级题目{callout color="#8cd0f3"}let a = {n : 1}; let b = a; a.x = a = {n: 2}; console.log(a.x) // undefined console.log(b.x) // { n: 2}解析,上面的题目可以改成下面的let a = {n : 1}; let b = a; // 这一步将a.x 赋值成 {n: 2} // 此时 a = {n: 1, x: {n: 2}} b = {n: 1, x: {n: 2}} a.x = {n: 2} // 接着又将a初始化成 {n: 2} // 此时 a = {n: 2} , 地址会发生变化,因此a !== b。而 b 并仍然是上一次的值 b = {n: 1, x: {n: 2}} a = {n: 2} console.log(a.x) // undefined console.log(b.x) // {n: 2}{/callout}作用域相关{callout color="#8cd0f3"}var a = 0, b = 0; function A(a) { A = function (b) { console.log(a + b++) } console.log(a++) } A(1) // 1 A(2) // 4这题还好,明白变量作用域查找,就会了,上面的解析// 首先看A(1)的执行顺序,A(2) 需要放在后面看 var a = 0 var b = 0; function A(a) { // 这里我的理解是 // 这里只是把A赋值了,开辟了新的内存地址,他依旧是个函数,并且没有执行,不会影响上一次A(1)的函数 // 因此在执行A(1) 的时候,这步可以忽略 A = function (b) { console.log(a + b++) } // 如果在这里加上打印 a // 根据作用域查找规则,它会先在当前作用域查找,如果没有,就去上一级作用域查找 // 这里查找到了函数参数的a,并且值是传入进来的 1,因此下面的那句里的 a 的值就是1 console.log(a) // a++ 与 ++a 的区别,就非常简单了,一个后加在执行,一个先加在执行,这里是a++,所以肯定会打印1 console.log(a++) } A(1)接着看A(2)的执行顺序// 在A(1) 执行完后,a的值会自增1,变成了2,上面的代码就可以改成下面的 function A(b) { // 这里的 a 就是上次执行后,并自增了1的a // b++ 和上面的a++一样,先运算后执行 // 这里其实就是 console.log(2 + 2) console.log(a + b++) // 4 } A(2){/callout}非匿名自执行函数,函数名只读{callout color="#8cd0f3"}var b = 10; (function b(){ b = 20 console.log(b) // Function b })()解析:首先排除外面的b,因为作用域查找根本没有它什么事自执行函数内的b,根据作用域查找规则,这个b就是这个函数 function b接着将看似是将函数b改成了20,最后打印20但其实是:在自执行函数里面,如果这个自执行函数写了名字,例如b再去修改这个匿名函数,修改是没有反应的,即使没有报错,但是修改也不成功所以会打印这个函数b{/callout}稀疏数组{callout color="#8cd0f3"}var ary = [0,1,2]; ary[10] = 10; var r = ary.filter(function(x) { return x === undefined;}); console.log(r) // []0 in ary; => true3 in ary; => false10 in ary; => true也就是说 从 3 - 9 都是没有初始化的'坑'这些索引并不存在与数组中. 在 array 的函数调用的时候是会跳过这些'坑'的{/callout}包装类型与简单类型{callout color="#8cd0f3"}function showCase(value) { switch (value) { case 'A': console.log('Case A'); break; case 'B': console.log('Case B'); break; case undefined: console.log('undefined'); break; default: console.log('Do not know!'); } } showCase(new String('A')); // Do not know!这题只要知道包装类型是不等于非包装类型的就行了 new String("A") !== "A"而 String("A") === "A"{/callout}小学生题目{callout color="#8cd0f3"}1 + - + + + - + 1 = ?结果:2+- 是一元加和减操作符号,就是数学里的正负号。负负得正{/callout}
2021年06月18日
479 阅读
4 评论
9 点赞
1
2
...
5