懒人学前端
IP:
0关注数
0粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·107

全部
问答
动态
项目
学习
专栏
懒人学前端

17.阶段测 Ⅳ

1. 请简述过渡和动画在各方面的区别。transition• 事件驱动:需要访客或JS使状态发生变化后触发 • 一次性:想要循环,需要反复修改状态• 定义两个状态:开始状态和结束状态• 可指定唯一过渡属性:transition-property:all默认两个状态间所有可过渡属性,都会有补间动画。可以通过transition-property指定可以唯一过渡属性animation• 自动执行或事件驱动• 循环或指定执行次数• 定义多个状态• 不可指定唯一过渡属性• 可控制:暂停,播放等2. 为了提升动画体验,应该减少不必要动画元素和属性,避免使用什么语句?transition:all。3. 不同属性值引起改变,重新渲染有哪三种执行路径?layout > paint > composite;paint > composite;composite。4. 可通过合成属性,如transform在 compositor 线程实现动画的是哪个动画?CSS 动画。5. 请描述实现打字机效果的思路。• 给一个DIV添加动画,宽width从0到文字总长度变化• 有多少字,就从补间动画平均取多少个过渡状态,每状态对应显示一个字• 光标闪烁,只有显示和隐藏两个状态,期间不需要过渡效果,并且无限循环6. 用子绝父相的定位方式,在需要显示为骨架屏的元素内,放置两个伪元素,请描述这两个伪元素的用途。一个撑满父容器,表示未填充数据前的元素一个填充倾斜的渐变条纹,移动条纹位置,模拟进度条动画7. CSS有四种加载方式?style属性(内联样式);标签(嵌入样式);(外链样式);@import(导入样式)。8. 为什么CSS 会阻塞 JS 执行?JS 可能需要查询、修改 DOM 树和 CSS 样式,本身阻塞DOM 解析。9. CSS和不依赖 DOM 的JS应该放在什么标签前?。10. 请简单写出浏览器解析和渲染 CSS 的步骤。解析、级联、层叠、Render Tree、布局、绘制、合成、重新渲染。11. 浏览器是如何解析 CSS 的?将 CSS 字符串转换为包括选择器、属性名、属性值的数据结构,长度单位被转换为像素,关键字被转换为具体值,需要计算的函数转为具体值。12.重新渲染一般哪有三种执行路径?重排:布局 → 绘制 → 合并重绘:绘制 → 合并合并13. 请简要写出避免重排和重绘的6种方法。• 尽量使用仅引起合成的属性• 限制重新渲染区域• 减少使用display:table或table表格布局• 利用浏览器自身优化• 手动一次渲染• 优化 DOM 树14. 实现小于 1px 像素的边有哪两种方法?通过媒体查询,将边框宽度设为1px / devicePixelRatio伪类 +resolution+transform15. 实现圆角边框有哪几种方法?• 背景图片:绘制圆角边框,切成 4 个圆角 + 4 个边框的小图片,拼成圆角边框• border-radius• clip-path:创建裁剪区域16. 实现小三角有哪几种方法?伪类 + 特殊符号;特殊符号 + 缩进 + 溢出 + 旋转;宽高为 0 的边框。17. 实现文字描边有哪三种方法?text-shadow;-webkit-text-stroke;position: relative/position: absolute子绝父相。18. 实现渐变背景有哪两种方法?背景图片:一张宽 1px 像素,高度沿渐变方向固定,重复铺满即可linear-gradient
CSS
0
0
0
浏览量423
懒人学前端

第十五章:promise

一、promise 相关概念回调方法:就是将一个方法 func2 作为参数传入另一个方法 func1 中,当 func1 执行到某一步或者满足某种条件的时候才执行传入的参数 func2Promise 是 ES6 引入的异步编程的新解决方案。Promise 对象三种状态:初始化、成功、失败 pending-进行中、resolved-已完成、rejected-已失败就好像,你跟你女朋友求婚,她跟你说她要考虑一下,明天才能给你答案,这就是承诺(promise)。同时,这也是一个等待的过程(pending),然后你就等,等到明天你女朋友给你答复,同意(resolved)或者拒绝(rejected),如果同意就准备结婚了,如果不同意就等下次再求婚,哈哈哈。promise 是用来解决两个问题的:回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise 可以支持多个并发的请求,获取并发请求中的数据这个 promise 可以解决异步的问题,本身不能说 promise 是异步的二、promise 基本用法这样构造 promise 实例,然后调用 .then.then.then 的编写代码方式,就是 promise。let p = new Promise((resolve, reject) => { // 调用了Promise构造函数 // 做一些事情 // 然后在某些条件下resolve,或者reject if (/* 条件随便写^_^ */) { resolve() } else { reject() } }) p.then(() => { // 调用了promise实例的.then方法 // 如果p的状态被resolve了,就进入这里 }, () => { // 如果p的状态被reject }) 三、声明一个 Promise 对象new Promise((resolve, reject) => { // 这两个方法主要是用来修改状态的 console.log("开始求婚。") console.log("。。。。。") console.log("考虑一下。") setTimeout(() => { if (isHandsome || isRich) { // 当我们调用 resolve 函数的时候,Promise 的状态就变成 resolved resolve('我同意!') } else { // 当我们调用 reject 函数的时候,Promise 的状态就变成 reject reject("拒绝:我们八字不合") } }, 2000) }) // 如果一个 promise 已经被兑现(resolved)或被拒绝(rejected),那么我们也可以说它处于已敲定(settled)状四、Promise.prototype.then 方法已成功 resolved 的回调和已失败 rejected 的回调// 调用 Promise 对象的then方法,两个参数为函数 p.then(function(value){ // 成功 console.log(value); }, function(season){ // 失败 console.log(season); });getNumber()getNumber() .then(function(data){ console.log('resolved'); console.log(data); }) .catch(function(reason){ console.log('rejected'); console.log(reason); });五、Promise.prototype.catch 方法catch() 的作用是捕获 Promise 的错误其实它和 then 的第二个参数一样,用来指定 reject 的回调,用法是这样:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 方法中。请看下面的代码:promise.then( () => { console.log('this is success callback') } ).catch( (err) => { console.log(err) } )效果和写在 then 的第二个参数里面一样。不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js ,而是会进到这个 catch 方法中。请看下面的代码:getNumber() .then(function(data){ console.log('resolved'); console.log(data); console.log(somedata); //此处的somedata未定义 }) .catch(function(reason){ console.log('rejected'); console.log(reason); });六、Promise.all() 方法有了 all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。「谁跑的慢,以谁为准执行回调」Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调Promise .all([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });Promise.all 手撕代码题:const myPromiseAll =(arr)=>{ let result = []; return new Peomise ((resolve,reject)=>{ for (let i=0;i<arr.length;i++){ arr[i].then(data=>{ result[i] = data; if(result.length === arr.length)//所有的都成功才执行成功的回调 { resolve(result) }//这里可以用计数器更好点 },reject)//有一个失败则执行失败的回调 } }) }七、Promise.race() 方法「谁跑的快,以谁为准执行回调」1 秒后 runAsync1 已经执行完了,此时then里面的就执行了在 then 里面的回调开始执行时,runAsync2() 和 runAsync3() 并没有停止,仍旧再执行。于是再过 1 秒后,输出了他们结束的标志。//请求某个图片资源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxxxxx'; }); return p; } //延时函数,用于给请求计时 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('图片请求超时'); }, 5000); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });Promise.race()手撕代码用法:function promiseRace(promises) { if (!Array.isArray(promises)) { throw new Error("promises must be an array") } return new Promise(function (resolve, reject) { promises.forEach(p => Promise.resolve(p).then(data => { resolve(data) }, err => { reject(err) }) ) }) }八、Promise.any() 方法any() 方法还接受一组 promise 作为参数,并将它们打包到新的 promise 对象中。只要一个参数实例处于成功状态,新的 promise 就处于成功状态。参数实例均处于拒绝状态。新承诺处于拒绝状态。Promise.any(promises).then( (first) => { // Any of the promises was fulfilled. },(error) => { // All of the promises were rejected. } );注:同学们可以通过上面两道题目,举一反三,自己尝试写出手动实现 promise.any()九、Promise.prototype.finally() 方法finally 方法用于指定无论 Promise 对象的最终状态如何,都将执行 finally。Finally 不接受参数。Finally 独立于先前的执行状态,不依赖于先前的运行结果。 const promise4 = new Promise((resolve, reject) => { console.log(x + 1);}); promise4 .then(() => { console.log("你好");}).catch((err) => { console.log(err);}).finally(() => { console.log("finally");});// finally
0
0
0
浏览量2015
懒人学前端

项目题全解:购物车

Step 1:安装Vue Cli3脚手架安装 Nodejs保证 Node.js8.9 或更⾼版本终端中输⼊ node -v ,保证已安装成功安装淘宝镜像源npm install -g cnpm --registry=https://registry.npm.taobao.org以后的 npm 可以⽤ cnpm 代替安装 Vue Cli3 脚⼿架cnpm install -g @vue/cli检查其版本是否正确vue --versionStep 2:提供项目模板&基础数据页面效果:具体代码:App.vue<template> <div id="App"> <ul> <li v-for="index in cartList" :key="index.id"> 商品名字: {{ index.title }} <p>商品价格: {{ index.price }}</p> </li> <MyCart :toCart="cartList" /> </ul> </div> </template> <script> import MyCart from "./components/Cart.vue"; export default { name: "App", components: { MyCart }, data() { return { cartList: [ { id: 1, title: "夹克衫", price: 188, count: 1, active: false }, { id: 2, title: "西装裤", price: 128, count: 1, active: false }, { id: 3, title: "polo衫", price: 48, count: 1, active: false }, ], }; }, methods: {}, }; </script> <style scoped></style>Cart.vue<template> <div> <table border="1" class="mytab"> <tr> <th>#</th> <th>编号</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in toCart" :key="c.id"> <th><input type="checkbox" v-model="c.active" /></th> <td>{{ index + 1 }}</td> <td>{{ c.title }}</td> <td>{{ c.price }}</td> <td> <button @click="substract(index)">-</button> {{ c.count }} <button @click="add(index)">+</button> </td> <td>¥{{ c.price * c.count }}</td> </tr> </table> </div> </template> <script> export default { name: "App", components: {}, data() { return { Cart: this.toCart, }; }, methods: { substract(index) { this.Cart[index].count = this.Cart[index].count - 1; }, add(index) { this.Cart[index].count = this.Cart[index].count + 1; }, }, props: ["toCart"], }; </script> <style scoped> .mytab { border-collapse: collapse; } .mytab tr, .mytab td, .mytab th { text-align: center; border: 1px solid #bbbbbb; } .mytab th { background-color: #e6e6e6; } .mytab tr:hover { background-color: #eeeeee; } .mytab td, .mytab th { padding: 10px; } </style>现在我们已经有了每一件商品的单价,该如何统计共选择了几件商品,总价是多少呢?Step 3:计算选择商品数量和商品总价1、计算被选择的商品数量和商品总量基础操作:第一步:新增一行 tr ,表格新的一行:<tr> <td></td> <td colspan="2">{{ activeCount() }}/{{ count() }}</td> </tr>第二步:添加对应 count() 方法,判断 Cart 购物车中共有多少件物品count() { return this.Cart.length; },第三步:添加对应 activeCount() 方法,判断 active 的属性有多少个为 true,计算长度activeCount() { return this.Cart.filter((v) => v.active).length; },页面效果:具体代码:Cart.vue<template> <div> <table border="1" class="mytab"> <tr> <th>#</th> <th>编号</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in toCart" :key="c.id"> <th><input type="checkbox" v-model="c.active" /></th> <td>{{ index + 1 }}</td> <td>{{ c.title }}</td> <td>{{ c.price }}</td> <td> <button @click="substract(index)">-</button> {{ c.count }} <button @click="add(index)">+</button> </td> <td>¥{{ c.price * c.count }}</td> </tr> <tr> <td></td> <td colspan="2">{{ activeCount() }}/{{ count() }}</td> </tr> </table> </div> </template> <script> export default { name: "App", components: {}, data() { return { Cart: this.toCart, }; }, methods: { substract(index) { this.Cart[index].count = this.Cart[index].count - 1; }, add(index) { this.Cart[index].count = this.Cart[index].count + 1; }, count() { return this.Cart.length; }, activeCount() { return this.Cart.filter((v) => v.active).length; }, }, props: ["toCart"], }; </script> <style scoped> .mytab { border-collapse: collapse; } .mytab tr, .mytab td, .mytab th { text-align: center; border: 1px solid #bbbbbb; } .mytab th { background-color: #e6e6e6; } .mytab tr:hover { background-color: #eeeeee; } .mytab td, .mytab th { padding: 10px; } </style>2、计算商品总价知识储备:1、reduce() 方法reduce() 方法会遍历数组中的每一个元素,每遍历一次就会执行一次回调函数。当遍历完之后会将最后的结果返回出去。2、reduce() 函数的参数reduce() 函数,包含四个参数:previousValue:上一次调用 callbackFn 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]。currentValue:数组中正在处理的元素。在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],否则为 array[1]。currentIndex:数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。array:用于遍历的数组。3、举例说明这里我们用到 reduce() 方法的两个参数,第一个参数是累加函数,第二个是函数的 previousValue 的初始值。 const arr=[1,2,3,4,5] const newarr=arr.reduce((previousValue,current)=>{ console.log(previousValue+'-----'+current); //结果为: //1-----2 //3-----3 //6-----4 //10-----5 return previousValue + current // 求和运用 }); console.log(newarr); //输出 15如果设置为 0,则从 0 开始;不设置,默认从数组索引为 0 开始。 const arr = [1, 2, 3, 4, 5] const newarr = arr.reduce((previousValue, current) => { console.log(previousValue + '-----' + current); //结果为: // 0-----1 // 1-----2 // 3-----3 // 6-----4 // 10-----5 return previousValue + current // 求和运用 }, 0); // 0 就是上文提及的 initialValue,其作用是设置累加的初始值 console.log(newarr); //输出 15基础操作:第一步:新增一行展示总价<tr> <td></td> <td colspan="2">{{ activeCount() }}/{{ count() }}</td> <td colspan="2">¥{{ total() }}</td> </tr>第二步:添加对应 total() 方法,判断 Cart 购物车中共有多少件物品total() { let sum = 0; this.Cart.forEach((c) => { if (c.active) { sum += c.price * c.count; } }); return sum; },除此之外,也可以使用 reduce()return this.Cart.reduce((sum, c) => { if (c.active) { sum += c.price * c.count; } return sum; }, 0);页面效果:具体代码:Cart.vue<template> <div> <table border="1" class="mytab"> <tr> <th>#</th> <th>编号</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in toCart" :key="c.id"> <th><input type="checkbox" v-model="c.active" /></th> <td>{{ index + 1 }}</td> <td>{{ c.title }}</td> <td>{{ c.price }}</td> <td> <button @click="substract(index)">-</button> {{ c.count }} <button @click="add(index)">+</button> </td> <td>¥{{ c.price * c.count }}</td> </tr> <tr> <td></td> <td colspan="2">{{ activeCount() }}/{{ count() }}</td> <td colspan="2">¥{{ total() }}</td> </tr> </table> </div> </template> <script> export default { name: "App", components: {}, data() { return { Cart: this.toCart, }; }, methods: { substract(index) { this.Cart[index].count = this.Cart[index].count - 1; }, add(index) { this.Cart[index].count = this.Cart[index].count + 1; }, count() { return this.Cart.length; }, activeCount() { return this.Cart.filter((v) => v.active).length; }, // 此处可以进行性能优化 total() { // let sum = 0; // this.Cart.forEach((c) => { // if (c.active) { // sum += c.price * c.count; // } // }); // return sum; return this.Cart.reduce((sum, c) => { if (c.active) { sum += c.price * c.count; } return sum; }, 0); }, }, props: ["toCart"], }; </script> <style scoped> .mytab { border-collapse: collapse; } .mytab tr, .mytab td, .mytab th { text-align: center; border: 1px solid #bbbbbb; } .mytab th { background-color: #e6e6e6; } .mytab tr:hover { background-color: #eeeeee; } .mytab td, .mytab th { padding: 10px; } </style>注: 此处读者也可尝试使用 computed 属性对总金额 total 进行计算,性能更高。Step 4:当商品减到 0 时,从购物车中删除,避免 -1知识储备:splice() 的用法1、会改变原数组2、splice() 共有三个参数,可以实现新增、删除和替换功能。第一个参数是要修改的位置,第二个是删除的个数,第三个是要新增的元素。3、举例说明splice() 的删除功能,在这里使用两个参数。第一个参数为第一项位置,第二个参数为要删除几个。array.splice(``index``,num),返回值为删除内容,array 为结果值。let arr = ['1', '2', '3', '4']; arr.splice(0, 2); console.log(arr) // 3 4随堂提问:如果 index 超过了数组的长度,或者为负数会如何?答:如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从 -1 计数,这意味着 -n 是倒数第 n 个元素并且等价于 array.length-n);如果负数的绝对值大于数组的长度,则表示开始位置为第 0 位。基础操作:第一步:对 substract() 方法进行修改 <button @click="substract(index)">-</button>第二步:添加对应方法,判断两种不同的情况,当数量小于 1 时,直接删除该行。remove(index) { if (window.confirm("确定是否要删除?")) { this.Cart.splice(index, 1); } }, substract(index) { if (this.Cart[index].count > 1) { this.Cart[index].count = this.Cart[index].count - 1; } else { return this.remove(index); } },页面效果:具体代码:Cart.vue<template> <div> <table border="1" class="mytab"> <tr> <th>#</th> <th>编号</th> <th>单价</th> <th>数量</th> <th>总价</th> </tr> <tr v-for="(c, index) in toCart" :key="c.id"> <th><input type="checkbox" v-model="c.active" /></th> <td>{{ index + 1 }}</td> <td>{{ c.title }}</td> <td>{{ c.price }}</td> <td> <button @click="substract(index)">-</button> {{ c.count }} <button @click="add(index)">+</button> </td> <td>¥{{ c.price * c.count }}</td> </tr> <tr> <td></td> <td colspan="2">{{ activeCount() }}/{{ count() }}</td> <td colspan="2">¥{{ total() }}</td> </tr> </table> </div> </template> <script> export default { name: "App", components: {}, data() { return { Cart: this.toCart, }; }, methods: { remove(index) { if (window.confirm("确定是否要删除?")) { this.Cart.splice(index, 1); } }, substract(index) { if (this.Cart[index].count > 1) { this.Cart[index].count = this.Cart[index].count - 1; } else { return this.remove(index); } }, add(index) { this.Cart[index].count = this.Cart[index].count + 1; }, count() { return this.Cart.length; }, activeCount() { return this.Cart.filter((v) => v.active).length; }, total() { // let sum = 0; // this.Cart.forEach((c) => { // if (c.active) { // sum += c.price * c.count; // } // }); // return sum; return this.Cart.reduce((sum, c) => { if (c.active) { sum += c.price * c.count; } return sum; }, 0); }, }, props: ["toCart"], }; </script> <style scoped> .mytab { border-collapse: collapse; } .mytab tr, .mytab td, .mytab th { text-align: center; border: 1px solid #bbbbbb; } .mytab th { background-color: #e6e6e6; } .mytab tr:hover { background-color: #eeeeee; } .mytab td, .mytab th { padding: 10px; } </style>
0
0
0
浏览量1746
懒人学前端

2.核心概念—入口—下

二、Entry 和 Context配置入口context和entrywebpack 在构建打包时,通过 context 和 entry 这两个配置来找到打包入口路径。在配置入口时其实做了两件事:确认入口文件位置,告诉 webpack 从哪个文件开始打包描述 chunk name。如果传入一个字符串或字符串数组,那么默认的 chunk name 为 “main”,如果传入的是一个对象,则每个属性的 key 会是 chunk 的名称,该属性的值描述了 chunk 的入口点contextcontext 可以理解为配置资源入口的基础目录,在配置时要求必须使用绝对路径。如:现有的目录结构入口为 /src/script/index.js,则我们可以通过下面的配置来指定入口目录const path = require('path'); module.exports = { context: path.resolve(__dirname, './src/script'), entry: './index.js' }; module.exports = { context: path.resolve(__dirname, './src'), entry: './script/index.js' };这样配置后,命令行执行打包,发现依然成功的找到了入口并顺利打包(我们使用 hello world 的那个 demo 来执行现有配置)配置 context 的目的可以使 entry 的写法更加简洁,尤其是在多入口文件的情况下。不过 context 是可以省略的,默认值为当前工程的根目录。entry在 webpack 配置中有多种方式定义 entry 属性,如:字符串、数组、对象、函数,接下来我们展示下每种类型如何配置字符串类型直接定义入口名称module.exports = { entry: './src/script/index.js', }; // entry 单入口语法,是下面的简写 module.exports = { entry: { main: './src/script/index.js', }, }; 数组类型传入一个数组的作用是将多个文件预先合并,最终将多个依赖的内容绘制在一个 chunk 中,在打包时 webpack 会将数组中的最后一个元素作为实际的入口路径,其余文件会预先构建到入口文件。module.exports = { entry: ['lodash', './src/script/index.js'], };这种配置和下面是等效的// index.js import * from 'lodash' // webpack.config.js module.exports = { entry: './src/script/index.js', }这种写法会将 lodash 打包到我们的 bundle.js 中。这种写法类似于在 index.js 中引入 lodash,在控制台执行打包命令我们来看下生成的文件,从下面两张图可以看到在 index 中我们没有引入 lodash,但打包的文件中已经引入了 lodash对象类型如果要定义多入口文件则需要使用对象的形式,通过这种方式可以自定义 chunk name,其中对象的key即为 chunk name,对象的 value 为入口路径。在使用对象描述入口时,我们可以使用以下属性dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载filename: 指定要输出的文件名称import: 启动时需加载的模块library: 指定 library 选项,为当前 entry 构建一个 libraryruntime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunkpublicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址多入口配置本质上打包后生成多个js文件module.exports = { entry: { index: ['lodash', './src/script/index.js'], vendor: './vendor' } }函数类型使用函数类型定义入口时,只要返回上面介绍的几种形式即可,如// 返回字符串 module.exports = { entry: () => './src/script/index.js' } // 返回对象 module.exports = { entry: () => ({ index: ['lodash', './src/script/index.js'], vendor: './vendor' }) }传入函数的优点是我们可以通过函数中增加一些逻辑来动态改变打包入口总结本章我们梳理了 webpack 入口配置的几种方式,包括字符串、对象、数组、函数几种三、Entry 配置实例entry 配置实例webpack 的 entry 配置在实际的应用中可以分几个场景。单页应用多页应用分离应用程序和第三方库下面我们来介绍下这几种应用单页应用对于单页应用,我们一般来定义单一入口即可module.exports = { entry: './src/index.js', };通过单一入口打包文件,可以将所有入口文件依赖的 框架、引入的工具库、各个页面内容打包到一起。这样的好处是将所有内容都打包成一个 js 文件,依赖关系清晰。但是这种方式也有个弊端,即所有模块都打到一个包中,当应用规模上升到一定程度后导致打包资源体积过大,导致页面首次加载速度变慢多页应用对于多页应用的场景,为了尽可能减少打包资源的体积,我们希望每个页面都只加载各自必要的逻辑,而不是将所有内容都打包到一个 bundle 中,我们来看下多应用的配置const path = require('path'); module.exports = { entry: { index: './src/index.js', hello: './src/hello.js' }, output: { path: path.resolve(__dirname, 'dist') } };打包后的文件如下,可以看到打包的内容中包含了 index.js 和 hello.js 两个文件。分离应用程序和第三方库在一个应用中,我们使用的框架、库、第三方依赖等往往很少会有改动,如果我们将所有内容都打包到一个 bundle 文件,一旦业务代码有一点点变更,那用户就要重新下载整个资源,这对于页面的性能是很不友好的。为了解决这个问题,我们可以使用应用程序和第三方库分离的方式来打包文件。也就是将业务代码和不频繁变更的第三方依赖分 bundle 打包,示例如下webpack.config.js module.exports = { mode: 'development', entry: { index: './src/index.js', vendor: ['lodash'] } };index.jsimport * as _ from 'lodash'在上面的配置中,index.js 仍然和之前一样不做任何处理,只是我们新添加了一个 chunk name 为 vendor 的入口,并通过数组的形式将第三方依赖添加进去,执行打包命令我们看到输出了两个打包文件。其实上面的代码虽然打包成功了,也成功提取了 vender 文件,但是打开打包后的 dist/index.js 我们发现 lodash 还是被打到文件中了,对于这种情况我们可以配合使用 optimization.splitChunks,将 vender 和 index 中的公共代码提取出来,这个方法我们后面的文章在详细介绍。通过上面的配置,我们可以业务依赖的第三方模块抽取成一个独立的 bundle,由于这个 bundle 不经常变动,因此可以有效的利用客户端缓存,在用户后续请求页面时加快整体渲染速度。
0
0
0
浏览量2013
懒人学前端

10.布局—中

八、什么是浮动,如何清除浮动?定义在默认布局流中,浮动元素脱离文档流,沿内联布局的开始或结束位置放置与绝对和固定定位不同,浮动元素的宽高、内外边距和边框,依然影响相邻元素的位置,相邻元素环绕浮动元素流式布局创建float不为none即可创建浮动元素弹性布局的父元素不能浮动清除浮动浮动元素脱离文档流,只包含浮动元素的父元素高度为0,带来问题父元素高度不会随内容高度变化,内外边距、边框和背景无法自适应内容父元素后的元素,与父元素内的浮动元素重叠父元素后的元素,外边距属性无效解决问题的思路:设置高度通过JS将父元素高度设为获取浮动元素的最大高度通过CSS将父元素高度height设置固定值,然后设置溢出属性overflow,裁剪超出高度的部分或添加滚动条清除浮动通过HTML在父元素内部末尾添加清除浮动的元素<style> .box > div { float: left; width: 33.33%; } .clearfix { clear: both; } .margin-top { margin-top: 10px; } </style> <div class="box"> <div></div> <div></div> <div></div> <div class="clearfix"></div> </div> <div class="margin-top"></div>通过CSS在父元素内部末尾添加请出浮动的伪元素<style> .box::after { /** 清除浮动 **/ content: ' '; display: block; clear: both; /** 隐藏 **/ visibility:hidden; width:0; height:0; /** 隐藏:兼容IE低版本 **/ line-height: 0; } .margin-top { margin-top: 10px; } </style> <div class="box"> <div></div> <div></div> <div></div> </div> <div class="margin-top"></div>通过HTML在父元素下边添加清除浮动的元素(解决外边距问题)<style> .box > div { float: left; width: 33.33%; } .clearfix { clear: both; } .margin-top { margin-top: 10px; } </style> <div class="box"> <div></div> <div></div> <div></div> </div> <div class="clearfix"></div> <div class="margin-top"></div><!-- 清除浮动后,外边距生效 -->九、什么是滚动穿透,如何解决?滚动穿透是在移动端,尤其是 iOS 中,弹出模态框,用户模态框中的滚动/滑动操作,穿过弹窗,导致页面滚动的现象滚动穿透不是 BUG,只是运行环境对溢出、滑动事件处理略有差异示例:<style> body {margin:0;} .fixed { overflow: auto; margin: auto; position: fixed; width: 50vw; height: 50vh; top: 0; right: 0; bottom: 0; left: 0; background-color: #efefef; } .content, .list { height: 100vh; background-image: linear-gradient(to bottom, #efefef, #000); } .list { height: 200vh; } </style> <body> <div class="list"></div> <div class="fixed"> <div class="content"></div> </div> </body>请在真实的移动浏览器中,滑动中间的弹窗,你可能会发现页面也跟随滚动具体表现因浏览器而异在 CSS 中,有两个属性:pointer-events: none禁止元素成为鼠标事件的目标对象touch-action: none禁止元素响应任何触摸事件它们都不能阻止滚动事件,冒泡到父级,让父级继续滚动解决滚动穿透问题,比较的流行的方法是:当模态框弹出时,通过position:fixed创建层叠上下文,让不该滚动的父级的脱离文档流,设置width:100%保留宽度,并将body的卷曲高度保存起来。当模态框关闭时,通过position:static,让父级回归文档流,恢复之前的卷曲高度,即滚动位置让我们解决示例的滚动穿透问题:<style> body {margin: 0;} .modal { display: none; overflow: auto; margin: auto; position: fixed; width: 50vw; height: 50vh; top: 0; right: 0; bottom: 0; left: 0; background-color: #efefef; } .content, .list { height: 100vh; background-image: linear-gradient(to bottom, #efefef, #000); } .list { height: 200vh; } .open, .close { position: fixed; text-align: center; } </style> <body> <div class="open">打开</div> <div class="list"></div> <div class="modal"> <div class="close">关闭</div> <div class="content"></div> </div> </body> <script> var openBt = document.querySelector('.open'), closeBt = document.querySelector('.close'), modal = document.querySelector('.modal'), list = document.querySelector('.list'), scrollTop = 0 openBt.onclick = function() { scrollTop = document.body.scrollTop || document.documentElement.scrollTop modal.style.display = 'block' list.style.cssText = 'position: fixed; width: 100%; top: -' + scrollTop + 'px' } closeBt.onclick = function() { modal.style.display = 'none' list.style.cssText = 'position: static;' window.scroll({ top: scrollTop }) } </script>十、多方法实现水平居中text-align: center适合内联或内联块元素的父元素设置position:staicmargin: 0 auto适合宽度已知元素块元素display:block宽度固定width:100px块元素display:block宽度自适应内容width:fit-content表格元素display:tableposition:relative/position:absolute/position:fixed/position:sticky脱离文档流后偏移left:50%或right:50%margin-left或margin-right设置宽度一半transform: translateX(-50%)偏移left:0和right:0宽度固定width:100px或自适应width:fit-contentmargin: 0 autodisplay:flex父元素justify-content: center每行内部元素沿主轴居中排列十一、多方法实现垂直居中内联父元素line-height: 100px行高固定值内联块或块元素line-height等于 元素heightpadding-top等于padding-bottom内联或内联块元素vertical-align: middle元素与行的基线+字母x的高度的一半对齐表格单元格元素:display: table-cell或<td>vertical-align: middle内容或子元素垂直居中position:staicmargin-top: 50%适合宽度已知元素transform: translateY(-50%)块元素display:block高度固定height:100px块元素display:block宽度自适应内容height:fit-content表格元素display:tableposition:relative/position:absolute/position:fixed/position:sticky脱离文档流后偏移top:50%或bottom:50%margin-top或margin-bottom设置高度一半transform: translateY(-50%)偏移top:0和bottom:0宽度固定height:100px或自适应height:fit-contentmargin: auto 0display:flex父元素align-content: center多行内部元素整体沿交叉轴居中排列align-items: center每行内部元素整体沿交叉轴居中排列align-self: center单个内部元素沿交叉轴居中排列十二、多方法实现高度 100% 撑满视窗如果视窗高度是变化的,纯CSS撑满视窗,可以用相对单位百分比百分比相对于父元素设置,height默认为auto,即内容高度从根元素html向内到body,高度都设置为height:100%<style> html, body { height: 100%; } div { height: 100%; background-color: azure; } body { margin: 0; } </style> <div></div>vh,直接设置相对视窗高度<style> div { height: 100vh; background-color: azure; } body { margin: 0; } </style> <div></div>如果对于内容高度,多用视窗高度减去固定元素高度(如导航栏,状态栏),可用函数calc()calc(100% - 50px)calc(100vh - 50px)十三、圣杯布局在弹性布局以前,圣杯布局是通过浮动和定位实现三栏布局的一种方案之一圣杯布局的特点:上下为通栏,下通栏清浮动中间为三栏布局,子元素按中左右的顺序浮动float:left宽度左边栏宽度固定 = 父元素的左内边距padding-left右边栏宽度固定 = 父元素的右内边距padding-right中间内容宽度 = 100%位置左右边栏相对定位position:relative左边栏左外边距margin-left: -100%,right:宽度右边栏左外边距margin-left:-宽度,right:-宽度注意需设置最小宽度min-width,避免视窗宽度过小,浮动错位<style> body { margin: 0; } div { text-align: center; color: #fff; } .header, .footer { height: 50px; background-color: pink; line-height: 50px; } .content { padding-left: 200px; padding-right: 150px; min-width: 500px; line-height: 500px; } .content > div { float: left; height: 500px; } .center { width: 100%; background-color: mediumturquoise; } .left, .right { position: relative; } .left { width: 200px; right: 200px;十四、双飞翼布局双飞翼布局由淘宝UED发扬,是通过浮动和定位实现三栏布局的一种方案之一双飞翼布局的特点:上下为通栏,下通栏清浮动中间为三栏布局,子元素按中左右的顺序浮动float:left宽度左边栏宽度固定 = 中间内容子元素左外边距margin-left右边栏宽度固定 = 中间内容子元素右外边距margin-right中间内容宽度 = 100%位置左边栏左外边距margin-left: -100%右边栏左外边距margin-left:-宽度<style> body { margin: 0; } div { text-align: center; color: #fff; } .header, .footer { height: 50px; background-color: pink; line-height: 50px; } .content > div { float: left; height: 500px; line-height: 500px; } .center { width: 100%; background-color: mediumturquoise; } .inner { height: 500px; margin-left: 200px; margin-right: 150px; } .left { margin-left: -100%; width: 200px; background-color: skyblue; } .right { margin-left: -150px; width: 150px; background-color: lightsteelblue; } .footer { c lear: both; } </style> <div class="header">header</div> <div class="content"> <div class="center"> <div class="inner">center-inner</div> <div> <div class="left">left</div> <div class="right">right</div> </div> <div class="footer">footer</div>
CSS
0
0
0
浏览量2014
懒人学前端

第二章:深拷贝和浅拷贝

一、知识点讲解1、什么是数据类型?数据分为基本数据类型 (String, Number, Boolean, Null, Undefined,Symbol) 和引用数据类型。基本数据类型的特点:直接存储在栈 (stack) 中的数据引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。2、ES11新增了什么数据类型?bigint!bigint使用方式:let max=Number.MAX_SAFE_INTEGER console.log(BigInt(max))//9007199254740991n console.log(BigInt(max)+BigInt(1))//9007199254740992n 正确 console.log(BigInt(max)+BigInt(2))//9007199254740993n 正确 console.log(BigInt(max)+BigInt(4))//9007199254740995n 正确3、为什么要使用深拷贝?我们希望在改变新的数组(对象)的时候,不改变原数组(对象)4、赋值和浅拷贝的区别?当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。二、浅拷贝浅拷贝是一点一点地拷贝一个对象。它将创建一个新对象。此对象具有原始对象属性值的精确副本。如果属性是基本类型,它将复制基本类型的值;如果属性是内存地址(引用类型),则复制内存地址。因此,如果一个对象更改地址,另一个对象将受到影响。也就是说,默认复制构造函数只复制浅层复制中的对象(依次复制成员),即只复制对象空间,而不复制资源。 var obj1 ={ name:'张三', age:8, pal:['王五','王六','王七'] } var obj3 = shallowCopy(obj1) function shallowCopy (src){ var newObj = {}; for(var prop in src ){ console.log(prop) if(src.hasOwnProperty(prop)){ newObj[prop] = src[prop] } } return newObj } obj3.name = '李四' obj3.pal[0] = '王麻子' console.log("obj1", obj1); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']} console.log("obj3", obj3); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}三、深拷贝方法一:递归实现深拷贝实现深拷贝原理的递归方法:遍历对象、数组甚至内部都是基本的数据类型,然后复制它们,即深度复制。var obj = { //原数据,包含字符串、对象、函数、数组等不同的类型 name:"test", main:{ a:1, b:2 }, fn:function(){ }, friends:[1,2,3,[22,33]] } function copy(obj){ let newobj = null; //声明一个变量用来储存拷贝之后的内容 //判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可, //由于null不可以循环但类型又是object,所以这个需要对null进行判断 if(typeof(obj) == 'object' && obj !== null){ //声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存 newobj = obj instanceof Array? [] : {}; //循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数 for(var i in obj){ newobj[i] = copy(obj[i]) } }else{ newobj = obj } console.log('77',newobj) return newobj; //函数必须有返回值,否则结构为undefined } var obj2 = copy(obj) obj2.name = '修改成功' obj2.main.a = 100 console.log(obj) console.log(obj2)方法二:递归实现深拷贝递归处理 引用类型保持数组类型function deepCopy(target) { if (typeof target === 'object') { const newTarget = Array.isArray(target) ? [] : Object.create(null) for (const key in target) { newTarget[key] = deepCopy(target[key]) } return newTarget } else { return target } }方法三:递归实现深拷贝哈希表 Map 支持 循环引用Map 支持引用类型数据作为键function deepCopy(target, h = new Map) { if (typeof target === 'object') { if (h.has(target)) return h.get(target) const newTarget = Array.isArray(target) ? [] : Object.create(null) for (const key in target) { newTarget[key] = deepCopy(target[key], h) } h.set(target, newTarget) return newTarget } else { return target } }方法四:递归实现深拷贝哈希表 WeakMap 代替 MapWeakMap 的键是弱引用,告诉 JS 垃圾回收机制,当键回收时,对应 WeakMap 也可以回收,更适合大量数据深拷function deepCopy(target, h = new WeakMap) { if (typeof target === 'object') { if (h.has(target)) return h.get(target) const newTarget = Array.isArray(target) ? [] : Object.create(null) for (const key in target) { newTarget[key] = deepCopy(target[key], h) } h.set(target, newTarget) return newTarget } else { return target } }
0
0
0
浏览量2040
懒人学前端

项目题全解:音乐播放器—下

Step 6:自动下一首的播放基础知识:@ended 事件:audio 原生的 ended 事件,实现自动播放下一首基础操作:第一步 :添加结束按钮<audio :src="currentSrc" controls autoplay @ended='handleEnded'></audio>第二步 :添加对应方法handleEnded() { // 下一首的播放 this.currentIndex++; this.currentSrc = this.musicData[this.currentIndex].songSrc; },参考代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>音乐播放器</title> <style> * { padding: 0; margin: 0; } ul { list-style: none; } ul li { margin: 20px 20px; padding: 10px 5px; border-radius: 3px; } ul li.active { background-color: #D2E2F3; } </style> </head> <body> <div id='app'> <!-- 在这里添加基础html --> <audio :src="currentSrc" controls autoplay @ended='handleEnded'></audio> <ul> <li v-for='(item,index) in musicData' :key='item.id' @click="handleClick(item.songSrc,index)" :class='{active:index === currentIndex}'> 第{{item.id}}首歌 歌曲:{{item.name}} 的</p> <p> 作者:{{item.author}} </p> </li> </ul> </div> <script src="./vue.js"></script> <script> // 基础数据 const musicData = [{ id: 1, name: '大眠-王心凌', author: '王心凌', songSrc: './static/大眠-王心凌.mp3' }, { id: 2, name: '千千阙歌-陈慧娴', author: '陈慧娴', songSrc: './static/千千阙歌-陈慧娴.mp3' }, { id: 3, name: '圣诞结-陈奕迅', author: '陈奕迅', songSrc: './static/圣诞结-陈奕迅.mp3' }, { id: 4, name: '快乐崇拜-潘玮柏', author: '潘玮柏', songSrc: './static/快乐崇拜-潘玮柏.mp3' } ]; new Vue({ el: '#app', data: { musicData, currentSrc: './static/大眠-王心凌.mp3', currentIndex: 1, }, methods: { handleClick(src, index) { this.currentSrc = src; this.currentIndex = index; }, handleEnded() { // 下一首的播放 this.currentIndex++; if (this.currentIndex === this.musicData.length) { this.currentIndex = 0; } this.currentSrc = this.musicData[this.currentIndex].songSrc; }, } }) </script> </body> </html>Step 7:手动下一首的播放基础知识:绑定监听@click:(以监听click为例,其他如keyup,用法类似) v-on:click="fun" @click="fun" @click="fun(参数)"基础操作:第一步:添加按钮样式<button @click='handleNext'>下一首</button>第二步:添加按钮样点击事件handleNext() { this.currentIndex++; // 到底了,重新归 0 if (this.currentIndex === this.musicData.length) { this.currentIndex = 0; } this.currentSrc = this.musicData[this.currentIndex].songSrc }效果展示:参考代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>音乐播放器</title> <style> * { padding: 0; margin: 0; } ul { list-style: none; } ul li { margin: 20px 20px; padding: 10px 5px; border-radius: 3px; } ul li.active { background-color: #D2E2F3; } </style> </head> <body> <div id='app'> <!-- 在这里添加基础html --> <audio :src="currentSrc" controls autoplay @ended='handleEnded'></audio> <ul> <li v-for='(item,index) in musicData' :key='item.id' @click="handleClick(item.songSrc,index)" :class='{active:index === currentIndex}'> 第{{item.id}}首歌 歌曲:{{item.name}} 的</p> <p> 作者:{{item.author}} </p> </li> </ul> <button @click='handleNext'>下一首</button> </div> <script src="./vue.js"></script> <script> // 基础数据 const musicData = [{ id: 1, name: '大眠-王心凌', author: '王心凌', songSrc: './static/大眠-王心凌.mp3' }, { id: 2, name: '千千阙歌-陈慧娴', author: '陈慧娴', songSrc: './static/千千阙歌-陈慧娴.mp3' }, { id: 3, name: '圣诞结-陈奕迅', author: '陈奕迅', songSrc: './static/圣诞结-陈奕迅.mp3' }, { id: 4, name: '快乐崇拜-潘玮柏', author: '潘玮柏', songSrc: './static/快乐崇拜-潘玮柏.mp3' } ]; new Vue({ el: '#app', data: { musicData, currentSrc: './static/大眠-王心凌.mp3', currentIndex: 1, }, methods: { handleClick(src, index) { this.currentSrc = src; this.currentIndex = index; }, handleEnded() { // 下一首的播放 this.currentIndex++; if (this.currentIndex === this.musicData.length) { this.currentIndex = 0; } this.currentSrc = this.musicData[this.currentIndex].songSrc; }, handleNext() { this.currentIndex++; // 到底了,重新归 0 if (this.currentIndex === this.musicData.length) { this.currentIndex = 0; } this.currentSrc = this.musicData[this.currentIndex].songSrc } } }) </script> </body> </html>Step 8:代码更新,变得更简洁基础操作:第一步:代码复用handleEnded() { // 下一首的播放 // this.currentIndex++; // this.currentSrc = this.musicData[this.currentIndex].songSrc; handleNext(); },参考代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>音乐播放器</title> <style> * { padding: 0; margin: 0; } ul { list-style: none; } ul li { margin: 20px 20px; padding: 10px 5px; border-radius: 3px; } ul li.active { background-color: #D2E2F3; } </style> </head> <body> <div id='app'> <!-- 在这里添加基础html --> <audio :src="currentSrc" controls autoplay @ended='handleEnded'></audio> <ul> <li v-for='(item,index) in musicData' :key='item.id' @click="handleClick(item.songSrc,index)" :class='{active:index === currentIndex}'> 第{{item.id}}首歌 歌曲:{{item.name}} 的</p> <p> 作者:{{item.author}} </p> </li> </ul> <button @click='handleNext'>下一首</button> </div> <script src="./vue.js"></script> <script> // 基础数据 const musicData = [{ id: 1, name: '大眠-王心凌', author: '王心凌', songSrc: './static/大眠-王心凌.mp3' }, { id: 2, name: '千千阙歌-陈慧娴', author: '陈慧娴', songSrc: './static/千千阙歌-陈慧娴.mp3' }, { id: 3, name: '圣诞结-陈奕迅', author: '陈奕迅', songSrc: './static/圣诞结-陈奕迅.mp3' }, { id: 4, name: '快乐崇拜-潘玮柏', author: '潘玮柏', songSrc: './static/快乐崇拜-潘玮柏.mp3' } ]; new Vue({ el: '#app', data: { musicData, currentSrc: './static/大眠-王心凌.mp3', currentIndex: 1, }, methods: { handleClick(src, index) { this.currentSrc = src; this.currentIndex = index; }, handleEnded() { // 下一首的播放 // this.currentIndex++; // if (this.currentIndex === this.musicData.length) { // this.currentIndex = 0; // } // this.currentSrc = this.musicData[this.currentIndex].songSrc; handleNext(); }, handleNext() { this.currentIndex++; // 到底了,重新归 0 if (this.currentIndex === this.musicData.length) { this.currentIndex = 0; } this.currentSrc = this.musicData[this.currentIndex].songSrc } } }) </script> </body> </html>Step 9:用 computed 更新代码方式基础知识:computed 用来监控自己定义的变量,该变量不在 data里面声明,直接在 computed 里面定义,进行处理后返回一个结果值。基础操作:第一步:改变src属性<audio :src="getCurrentSongSrc" controls autoplay @ended='handleEnded'></audio>第二步:添加计算属性// 计算属性 computed: { getCurrentSongSrc() { return this.musicData[this.currentIndex].songSrc; } },第三步:更新方法<li v-for='(item,index) in musicData' :key='item.id' @click="handleClick(index)" :class='{active:index === currentIndex}'> 第{{item.id}}首歌 歌曲:{{item.name}} 的</p> <p> 作者:{{item.author}} </p> </li> ... handleClick(index) { this.currentIndex = index; }, handleEnded() { this.handleNext(); },参考代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>音乐播放器</title> <style> * { padding: 0; margin: 0; } ul { list-style: none; } ul li { margin: 20px 20px; padding: 10px 5px; border-radius: 3px; } ul li.active { background-color: #D2E2F3; } </style> </head> <body> <div id='app'> <!-- 在这里添加基础html --> <audio :src="getCurrentSongSrc" controls autoplay @ended='handleEnded'></audio> <ul> <li v-for='(item,index) in musicData' :key='item.id' @click="handleClick(index)" :class='{active:index === currentIndex}'> 第{{item.id}}首歌 歌曲:{{item.name}} 的</p> <p> 作者:{{item.author}} </p> </li> </ul> <button @click='handleNext'>下一首</button> </div> <script src="./vue.js"></script> <script> // 基础数据 const musicData = [{ id: 1, name: '大眠-王心凌', author: '王心凌', songSrc: './static/大眠-王心凌.mp3' }, { id: 2, name: '千千阙歌-陈慧娴', author: '陈慧娴', songSrc: './static/千千阙歌-陈慧娴.mp3' }, { id: 3, name: '圣诞结-陈奕迅', author: '陈奕迅', songSrc: './static/圣诞结-陈奕迅.mp3' }, { id: 4, name: '快乐崇拜-潘玮柏', author: '潘玮柏', songSrc: './static/快乐崇拜-潘玮柏.mp3' } ]; new Vue({ el: '#app', data: { musicData, currentIndex: 0, }, // 计算属性 computed: { getCurrentSongSrc() { // 当currentIndex改变时,computed 会自动计算 currentIndex 对应的 currentSrc return this.musicData[this.currentIndex].songSrc; } }, methods: { handleClick(index) { this.currentIndex = index; }, handleEnded() { this.handleNext(); }, handleNext() { this.currentIndex++; // 到底了,重新归 0 if (this.currentIndex === this.musicData.length) { this.currentIndex = 0; } } } }) </script> </body> </html>在这里是否可以使用 watch ?computed 和 watch 有什么区别?computed 和 watch 的区别:computed 支持缓存,相依赖的数据发生改变才会重新计算;watch 不支持缓存,只要监听的数据变化就会触发相应操作computed 不支持异步,当 computed 内有异步操作时是无法监听数据变化的;watch 支持异步操作computed 属性的属性值是一函数,函数返回值为属性的属性值,computed 中每个属性都可以设置 set 与 get 方法。watch 监听的数据必须是 data 中声明过或父组件传递过来的 props 中的数据,当数据变化时,触发监听器。
0
0
0
浏览量434
懒人学前端

第十二章:图片懒加载

方法一:滚动监听 + scrollTop + offsetTop + innerHeightscrollTop:指网页元素被滚动条卷去的部分。offsetTop:元素相对父元素的位置innerHeight:当前浏览器窗口的大小。需要注意兼容性问题。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> img { background: url('./img/loading.gif') no-repeat center; width: 250px; height: 250px; display: block; } </style> </head> <body> <img src="./img/pixel.gif" data-url="./img/1.jpeg"> <img src="./img/pixel.gif" data-url="./img/2.jfif"> <img src="./img/pixel.gif" data-url="./img/3.jfif"> <img src="./img/pixel.gif" data-url="./img/4.jfif"> <img src="./img/pixel.gif" data-url="./img/5.jfif"> <img src="./img/pixel.gif" data-url="./img/6.webp"> <script> let imgs = document.getElementsByTagName('img') // 1. 一上来立即执行一次 fn() // 2. 监听滚动事件 window.onscroll = lazyload(fn, true) function fn() { // 获取视口高度和内容的偏移量 let clietH = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; console.log(clietH, scrollTop); for (let i = 0; i < imgs.length; i++) { let x = scrollTop + clietH - imgs[i].offsetTop //当内容的偏移量+视口高度>图片距离内容顶部的偏移量时,说明图片在视口内 if (x > 0) { imgs[i].src = imgs[i].getAttribute('data-url'); //从dataurl中取出真实的图片地址赋值给url } } } // 函数节流 function lazyload(fn, immediate) { let timer = null return function () { let context = this; if (!timer) { timer = setTimeout(() => { fn.apply(this) timer = null }, 200) } } } </script> </body> </html>方法二:滚动监听 + getBoundingClientRect()rectObject.top:元素上边到视窗上边的距离;rectObject.right:元素右边到视窗左边的距离;rectObject.bottom:元素下边到视窗上边的距离;rectObject.left:元素左边到视窗左边的距离;rectObject.width:元素自身的宽度;rectObject.height:元素自身的高度;<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> * { margin: 0; padding: 0; } img { background: url('./img/loading.gif') no-repeat center; width: 250px; height: 250px; display: block; } </style> </head> <body> <img src="./img/pixel.gif" data-url="./img/1.jpeg"> <img src="./img/pixel.gif" data-url="./img/2.jfif"> <img src="./img/pixel.gif" data-url="./img/3.jfif"> <img src="./img/pixel.gif" data-url="./img/4.jfif"> <img src="./img/pixel.gif" data-url="./img/5.jfif"> <img src="./img/pixel.gif" data-url="./img/6.webp"> <script> let imgs = document.getElementsByTagName('img') // 1. 一上来立即执行一次 fn() // 2. 监听滚动事件 window.onscroll = lazyload(fn, true) function fn() { // 获取视口高度和内容的偏移量 let offsetHeight = window.innerHeight || document.documentElement.clientHeight Array.from(imgs).forEach((item, index) => { let oBounding = item.getBoundingClientRect() //返回一个矩形对象,包含上下左右的偏移值 console.log(index, oBounding.top, offsetHeight); if (0 <= oBounding.top && oBounding.top <= offsetHeight) { item.setAttribute('src', item.getAttribute('data-url')) } }) } // 函数节流 function lazyload(fn, immediate) { let timer = null return function () { let context = this; if (!timer) { timer = setTimeout(() => { fn.apply(this) timer = null }, 200) } } } </script> </body> </html>方法三: intersectionObserve()IntersectionObserver : 浏览器原生提供的构造函数,接收两个参数IntersectionObserver API 是异步的,不会与目标元素的滚动同步触发<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> img { background: url('./img/loading.gif') no-repeat center; width: 250px; height: 250px; display: block; } </style> </head> <body> <img src="./img/pixel.gif" data-url="./img/1.jpeg"> <img src="./img/pixel.gif" data-url="./img/2.jfif"> <img src="./img/pixel.gif" data-url="./img/3.jfif"> <img src="./img/pixel.gif" data-url="./img/4.jfif"> <img src="./img/pixel.gif" data-url="./img/5.jfif"> <img src="./img/pixel.gif" data-url="./img/6.webp"> <script> let imgs = document.getElementsByTagName('img') // 1. 一上来立即执行一次 let io = new IntersectionObserver(function (entires) { //图片进入视口时就执行回调 entires.forEach(item => { // 获取目标元素 let oImg = item.target // console.log(item); // 当图片进入视口的时候,就赋值图片的真实地址 if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) { oImg.setAttribute('src', oImg.getAttribute('data-url')) } }) }) Array.from(imgs).forEach(element => { io.observe(element) //给每一个图片设置监听 }); </script> </body> </html>
0
0
0
浏览量2022
懒人学前端

18.工程化

一、对比 Less、Sass、Stylus、PostCSS?Less、Sass 和 Stylus 是 CSS 预处理器,PostCSS 是转换 CSS 工具的平台Less变量:$标识符变量,使用{}插值嵌套:支持{ }大括号嵌套混入器:支持 选择器 混入 或 使用.selector(@param)创建纯混入器扩展 / 继承 / 运算符 / @import:支持流程:支持if条件判断,支持when递归模拟循环映射:支持@声明和使用 Map特有:提供 Less.js,直接在浏览器使用 LessSaSS变量:支持$标识符变量,使用#{}插值嵌套:SCSS 支持{ }大括号嵌套 SASS 支持缩进嵌套混入器:@mixin创建@include使用扩展 / 继承 / 运算符 / @import:支持流程:支持if else条件判断,支持for while each循环映射:支持$()声明 Map,提供map-get(map, key) map-keys(map) map-values(map)等一系列方法操作 Map,支持遍历 Map特有:支持 compass ,内含 自动私有前缀 等一系列有用SASS模块,支持压缩输出Stylus变量:支持$标识符变量 和 赋值表达式变量,使用{}插值嵌套:支持{ }大括号嵌套 和 缩进嵌套混入器:像函数一样创建和使用扩展 / 继承 / 运算符 / @import:支持流程:支持if else unless三元 条件判断,支持for循环映射:像 JS 一样创建和使用对象特有:支持中间件,自动分配函数变量,提供 JS API。支持压缩输出PostCSS接受 CSS 文件,把 CSS 规则转换为抽象语法树提供 API 供插件调用,对 CSS 处理插件:支持 Autoprefixer 自动添加私有前缀、css-modules CSS 模块 stylelint CSS 语法检查等插件,PostCSSS 是工具的工具二、Webpack 处理 SASS 文件时,sass-loader, css-loader,style作用sass-loader将 SASS / SCSS 文件编译成 CSS调用node-sass,支持options选项向node-sass传参css-loader支持读取 CSS 文件,在 JS 中将 CSS 作为模块导入支持 CSS 模块 @规则@import @import url()style-loader将 CSS 以<style>标签的方式,插入 DOM配置顺序Webpack中 loader 的加载顺序是从后往前在处理 SASS / SCSS 文件时,三者的配置顺序 style-loader css-loader sass-loader可以用插件 ExtractTextWebpackPlugin 替换 style-loader,生成新的 CSS 文件三、如何压缩 CSS 大小,如何去除无用的 CSS?压缩 CSS ,简单可分为 3 步骤:① 去除 CSS 中的换行和空格② 去除 每个选择器,最后一个属性值后的分号;② 去除 注释,正则/*[^*] [^/][^*]**+)*/此外,包括使用 缩写属性,z-index值的重新排序,也可以减少代码量。但通过工具进行时,结果不一定总是安全常用的 CSS 压缩工具有:YUI Compressor基于 Java , 本来是 JS 压缩,也兼容 CSS 压缩,配置较少,但保障压缩安全clean-css基于 NodeJS , 可以根据 浏览器版本、具体的 CSS 标准模块,详细配置兼容性cssnano基于 PostCSS ,是 Webpack5 推荐的 CssMinimizerWebpackPlugin 默认的 CSS 压缩工具如何去除无用的 CSS :Chrome 开发者工具 Lighthouse打开 http / https 网址,勾选 Performance 选项,在性能报告中,会列出 unused CSS,可以人工去除UnCSS通过 jsdom 加载 HTML 并执行 JS通过 PostCSS 解析样式通过document.querySelector查找 HTML 中不存在的 CSS 选择器返回 剩余样式PurgeCSS可以对 HTML JS 和 VUE 等框架中 CSS 使用情况分析,并删除无用 CSS提供 Webpack Gulp Grunt 等工程化工具的插件cssnano提供discardUnused选项,用于删除与当前 CSS 无关的规则不支持内容分析同样支持工程化工具四、什么是 CSS 模块化,有哪几种实现方式?什么是 CSS 模块化?CSS 模块化是将 CSS 规则 拆分成相对独立的模块,便于开发者在项目中更有效率地组织 CSS:管理层叠关系避免命名冲突提高复用度,减少冗余便于维护或扩展CSS 模块化的方式:基于文件拆分不拆分但设置作用域CSS in JS内联样式、Shadow DOM 等无论哪种方式,核心都是通过 保证CSS类命名唯一,或者 避免命名使用内联样式,来模拟出CSS模块作用域的效果基于文件的 CSS 模块的加载<link>将不同模块的 CSS 分文件存放,通过<link>标签按需引入@import@规则,将其它 CSS 嵌入当前 CSS除现代浏览器外,也得到了 CSS 预处理器 Less、Sass 和 Stylus 的支持import在 Webpack 中,将 CSS 作为资源引入,并可以通过 CSS Modules 为 CSS 生成独一无二的类名CSS 模块化的实现方式分层拆分将 CSS规则 分层存放,并约束不同层次间的命名规则SMACSS:按功能分成 Base Layout Module State Theme 五层ITCSS:按从通用到具体分成 Settings Tools Generic Base Objects Components 七层分块拆分将页面中视觉可复用的块独立出来,仅使用类选择器,并确保类名唯一OOCSS将盒模型规则与颜色、背景等主题规则拆分将视觉可复用的块类名与页面结构解耦BEM将页面按 Block Element Modifier 划分类名规则 block-name__element-name--modifier-name原子化拆分每个选择器只包含1个或少量属性,通过组合选择器定义复杂样式ACSS:属性名像函数,属性值像函数参数,像写内联样式一样组合类名Utility-First CSS:提供一套可配置的 CSS 实用类库,使用时按需编译CSS in JSCSS Modules将 CSS 作为资源引入根据内容的哈希字符串,计算出独一无二的类名,CSS 规则 只对该类名下的元素生效styled-components Aphrodite Emotion 等通过 JS 创建包含属性定义的组件生成独一无二的类名Radium 等通过 JS 创建包含属性定义的组件生成内联样式Shadow DOM通过attachShadow给元素的影子DOM,附加<style>标签,其中规则不会影响外部元素。代表的框架有 Ionic 等五、如何自动压缩图片?使用或调用软件使用 PhotoShop 自动批处理功能录制动作:打开文件,调整大小,另存为调整品质,关闭文件文件菜单,自动,批处理,选择 源文件夹,批量执行动作使用 开源图片处理软件 XnViewr,工具,批量转换功能XnConVenter 提供更复杂的图片批量处理功能使用 命令行工具,包括 NConvert ImageMagic 等以及类似 img2webp 的专门格式的转换工具工程化配置图片压缩这里以Webpack为例CSS 内联图片配置url-loader小于指定尺寸图片转 base64,减少请求module: { rules: [{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10, // 小于10KB图片,转base64编码 } }]}图片模块import / require 到 .js 或 .html 以及 require({图片路径}) 的图片这些图片被打包器看作是资源模块引入配置file-loader+image-webpack-loader调节品质rules: [{ test: /\.png$/i, // 以png为例 use: [ 'file-loader', { loader: 'image-webpack-loader', options: { pngquant: { quality: [0.65, 0.90] // 设置png的品质区间 }, } }, ], }]图片目录图片以静态资源附件的形式,放置在同一目录下通过CopyPlugin将图片从源目录(src)复制到发布目录(dist)可以安装imagemin-webpack-plugin,在复制时,批量调整图片尺寸npm i imagemin-webpack-plugin -D plugins: [ new CopyPlugin({ patterns: [{ from: resolve('src-es6/images'), to: resolve('dist/images'), }] }), new ImageminPlugin({ // 在 CopyPlugin 之后。如果是 merge 多配置文件,CopyPlugin 放 common 被合并的配置里 test: /\.(png|jpe?g)$/i, pngquant: { quality: '70-75' // 设置 png 的品质区间 }, plugins: [ // 可以引入其它图片压缩插件 ImageminMozjpeg({ quality: 70, // 设置 jpeg / jpg 的品质 progressive: true // 渐进式:true:由模糊到清晰加载 false:从上到下加载 }) ] }), ]代码中使用的ImageminMozjpeg需要额外安装,您也可以安装其它压缩插件npm i imagemin-mozjpeg -D使用反向代理部分CDN服务商,提供一键 图片瘦身,自动转 webp 等能压缩图片服务您可以自建反向代理节点,将图片缓存在节点上,根据客户端接受的MIME文件类型列表,如支持 image/webp 的客户端,将图片转成 webp 格式。根据用户 UserAgent,调节品质、尺寸后,再返回给客户端需要提醒的是,避免使用 defalte gzip 和 br 等压缩算法,再次压缩图片,这样带来的性能损耗通常高于传输收益六、如何自动添加浏览器私有前缀属性?使用 SASS自定义添加浏览器私有前缀的Mixins,不能适应所有属性@mixin autoPrefix($propertyName, $propertyValue, $prefixs: ()) { @each $prefix in $prefixs { -#{$prefix}-#{$propertyName}: $propertyValue; } #{$propertyName}: $propertyValue; } div { @include autoPrefix(box-shadow, 0 0 1px black, ('webkit', 'moz')) } 转换为 CSS div { -webkit-box-shadow: 0 0 1px black; -moz-box-shadow: 0 0 1px black; box-shadow: 0 0 1px black; }搭配compass或Bourbon@import "compass/css3"; div { @include box-shadow(0 0 1px black); } 使用 Less,搭配 LESS Elements@import "elements.less"; div { .box-shadow(0 0 1px black); }使用 Autoprefixer支持补全属性值,支持按浏览器兼容情况精确补全VsCode安装 Extensions:Autoprefixer进入 Vscode Perferences Settings 搜索 Autoprefixer配置支持的浏览器版本 "autoprefixer.options": { "browsers": [ "ie >= 6", "firefox >= 8", "chrome >= 24", "Opera >= 10" ] }F1 后,Run Autoprefixer,自动补全Webpack安装并添加 postcss-loadermodule.exports = { module: { rules: [ { test: /\.css$/, use: ["css-loader", "postcss-loader"] } ] } }创建 postcss.config.jsmodule.exports = { plugins: [ require('autoprefixer') ] }创建 .browserslistrcie >= 6 firefox >= 8 chrome >= 24 Opera >= 10或在 package.json 添加 browserslist 属性 { "browserslist": [ "ie >= 6", "firefox >= 8", "chrome >= 24", "Opera >= 10" ] }七·、对比媒体查询与按需引入 CSS?媒体查询允许我们通过设备、屏幕、使用场景、用户偏好来解析符合条件的 CSS,忽略不符合条件的 CSS即使通过 Link 标签附加媒体查询条件引入的 CSS,不符合条件时依然会被下载按需引入按需引入,可以避免冗余下载的问题,允许我们按照目标环境引入CSS但条件没有媒体查询丰富,多数时,无法及时对运行环境的变化作出响应IE 浏览器中,可以通过 HTML 注释,附加 if 条件,阻止其中的 HTML 被解析,相应的 CSS 也不会被下载<!--[if lt IE 9]><link rel="stylesheet" href="ielt9.css" /><![endif]-->工程化及ES6等模块化环境中,我们可以通过import或require只引入需要的css,通过PurgeCSS和cssnano去除未被使用的CSS在webpack配置DefinePlugin自定义环境变量,在uni-app中 使用条件注释等利用打包工具提供的 条件编译 功能
CSS
0
0
0
浏览量2020
懒人学前端

4.核心概念-Loader—中

五、CSS-Loadercss-loadercss-loader 会对 import 和 url() 进行处理,就像 js 解析 import/require() 一样。使用index.html<!DOCTYPE html> <html lang="en"> <head> <title>css-loader</title> </head> <body> </body> </html>index.jssrc/index.js 文件中使用 import 导入 index.css 并输出导入的样式内容。import indexCss from "./index.css"; console.log(indexCss.toString())index.csssrc/index.css 文件中使用 import 导入 index.css。body { background-color: aqua; width: 500px; height: 500px; border: 1px solid red; }安装 css-loadernpm install css-loader -D安装成功后,将 css-loader 配置到 webpack.config.js 中module.exports = { ... module: { rules: [ { test: /\.(css)$/, use: [ { loader: 'css-loader' } ] } ] } };执行打包命令,此时在浏览器中打开 dist/index.html 发现页面没有正确展示我们定义的样式,但是控制台中输出了我们定义的样式,到了这一步,css-loader 我们就正确引入并使用了,没有正确展示效果的原因是 css-loader 对 index.js 中的 import 进行处理,默认生成一个数组存放处理后的样式字符串,并将其导出。而 style-loader 负责将 css 插入到 html 中,style-loader 的使用我们在下一节展示。配置项css-loader 包含下面几个配置项url允许启用/禁用处理 CSS 函数 url 和 image-set。如果设置为false, css-loader 将不会解析 url 或 image-set 中指定的任何路径。还可以通过传递函数来根据资源路径动态地控制这种行为。从版本4.0.0开始,绝对路径是基于服务器根目录进行解析的。src/index.cssbody { background: url(./img/1.png); }webpack.config.jsmodule.exports = { ... { loader: 'css-loader', options: { url: false, // url: true } } };在控制台我们在 index.js 中输出了 index.css 导出的字符串,我们来看下 url 设置为 false 和 true 的 background 区别。当 url 设置为 false 时, url 中的图片地址没做任何处理,当 url 值为 true 时,编译后地址为图片的路径,并且 dist 文件夹下会生成一张图片。import允许启用/禁用 @import 处理。src/index.css@import url('./main.css'); body { border: 1px solid red; }src/main.cssbody { width: 200px; height: 200px; }webpack.config.jsmodule.exports = { ... { loader: 'css-loader', options: { import: false // import: true } } };为了方便看到样式,我这安装了 style-loader 并添加到了 loader 中,我们来看下 import 设置为 false 和 true 的区别。当设置为 false 时 index.css 中的 @import 没有解析导致运行代码时找不到 main.css。modules查询参数 modules 会启用 CSS 模块规范。默认情况下,这将启用局部作用域 CSS。(你可以使用 :global(...) 或 :global 关闭选择器 and/or 规则。详情可查看 moduleswebpack.config.js{ loader: 'css-loader', options: { modules: true } }sourceMap设置 sourceMap 选项查询参数来引入 source map。例如 extract-text-webpack-plugin 能够处理它们。默认情况下取决于compiler.devtool 值,值为 false 和 eval 时,不会生成 source map,一般情况下不启用它,因为它们会导致运行时的额外开销,并增加了 bundle 大小 (JS source map 不会)。此外,相对路径是错误的,你需要使用包含服务器 URL 的绝对公用路径。webpack.config.js{ ... loader: 'css-loader', options: { sourceMap: true } }esModulecss-loader 中有时生成 esModule 模块化的形式是有益的,比如 module-concatenation 和 tree-shaking 时必须使用 esModule 模式才会生效。如果想启用 CommonJS 模块语法,则 esModule 设置为 false。webpack.config.js{ ... options: { esModule: true } }importLoaders在 src/index.css 中使用的 @import './main.css',importLoaders 选项可以定义在 @import 时使用哪些插件编译。webpack.config.js{ ... use: [ "style-loader", { loader: "css-loader", options: { // 0 => no loaders (default); // 1 => postcss-loader; // 2 => postcss-loader, sass-loader importLoaders: 2, }, }, "postcss-loader", "sass-loader", ], } exportType允许将样式导出为带有模块的数组、字符串或可构造样式表(如CSSStyleSheet)。默认值是 'array'。webpack.config.js{ ... use: [ { loader: 'css-loader', options: { exportType: 'string' } } ] } }src/index.jsimport indexCss from "./index.css"; console.log(indexCss)打包后,执行 dist/index.html 可以看到控制台输出了 index.css 中定义的样式字符串。总结本节我们介绍了 css-loader 的使用方法和 css-loader 包含的几个常用参数配置。css-loader 可以将 js 中的 import 导入样式文件进行编译并且拿到导出内容供其他插件使用。六、Style-Loaderstyle-loaderstyle-loader 一般和 css-loader 配合使用,css-loader 识别模块,通过特定的语法规则进行内容转换最后导出,style-loader 将 css-loader 导出的内容插入到 DOM。使用index.html<!DOCTYPE html> <html lang="en"> <head> <title>style-loader</title> </head> <body> </body> </html>index.jssrc/index.js 文件中使用 import 导入 index.css。import indexCss from './index.css';index.css设置一个长宽均为 200 像素的带红色边框的正方形body { border: 1px solid red; width: 200px; height: 200px; }安装 style-loadernpm install style-loader -D安装成功后,将 style-loader 配置到 webpack.config.js 中,配置中我们使用 css-loader 和 style-loader 两个加载器,webpack 中 loader 的解析一般由右向左,由下向上解析,所以 webpack 会先执行 css-loader,css-loader 导出内容传给 style-loader,最后在执行 style-loader。module.exports = { ... module: { rules: [ { test: /\.css$/, use: ['style-loader','css-loader'] } ] } };执行打包命令,此时在浏览器中打开 dist/index.html 发现页面可以正常展示我们设置的样式,我们在控制台可以看到样式被插入到 head 标签中。配置项style-loader 包含下面几个配置项injectType设置样式如何注入 DOM,默认为 styleTag,即使用多个<style></style> 模式。styleTagsrc/index.css@import url('style.css'); .bar { color: blue; }src/style.css.foo { color: red; }webpack.config.jsmodule.exports = { ... { test: /\.(css)$/, use: [ { loader: 'style-loader', options: { injectType: "styleTag" } }, 'css-loader' ] } };index.jsimport index from "./index.css"; const divElement = document.createElement("div"); divElement.className = "foo"; divElement.innerHTML = 'style-loader' document.body.appendChild(divElement)执行打包命令,在浏览器中打开 dist/index.html 文件,我们可以看到 head 中插入了两个 style 标签,div 中的文字颜色可以正常显示。singletonStyleTag将多个样式文件内容在一个 style 标签中插入 DOM。webpack.config.jsmodule.exports = { ... { test: /\.(css)$/, use: [ { loader: 'style-loader', options: { injectType: "singletonStyleTag" } }, 'css-loader' ] } };lazyStyleTag<style></style> 按需注入到 DOM 。建议遵循 .lazy.css 惰性样式的命名约定和 .css 基本style-loader用法。当使用 lazyStyleTag 时,可以通过 style-loader 的 style.use()、style.unuse() 按需使用。src/style.lazy.css.foo { color: red; }src/index.jsimport styles from "./style.lazy.css"; styles.use(); const divElement = document.createElement("div"); divElement.className = "foo"; divElement.innerHTML = 'style-loader' document.body.appendChild(divElement)webpack.config.jsmodule.exports = { ... { test: /\.(css)$/, use: [ { loader: 'style-loader', options: { injectType: "lazyStyleTag" } }, 'css-loader' ] } };打包后,可以看到样式被插入到 DOM,并且颜色已生效,如果 index.js 中没有调用 styles.use(),则样式不会被插入到 DOM。attributes将指定的属性值附加到 <style> 或 <link> 标签webpack.config.js{ loader: 'style-loader', options: { attributes: {id: 'styleLoader'} } } insert默认情况下 style-loader 会将<style>、<link> 标签插入到 <head> 标签尾部,设置 insert 后,可以将样式标签插入到其他位置。webpack.config.js{ loader: 'style-loader', options: { insert: 'body' } }styleTagTransform当插入 style 标签到 DOM 时转换标签和 css,可以设置自定义方法解析 style 标签插入方式。webpack.config.js{ ... loader: 'style-loader', options: { styleTagTransform: function(css, style) { style.innerHTML = `${css}.modify{}\n` document.head.appendChild(style) } }打包后,在浏览器中打开 dist/index.html,我们可以看到 css 样式后面添加了我们在方法中书写的内容。esModulestyle-loader 中生成 esModule 模块化的形式是有益的,比如 tree-shacking 时必须使用 esModule 模式才会生效。如果想启用 CommonJS 模块语法,则 esModule 设置为 false。webpack.config.js{ ... options: { esModule: true } }base当使用一个或多个 DllPlugin 时,此设置主要用作 css 冲突的解决方法。允许您通过指定大于 DllPlugin1 使用的范围的 css 模块 ID 基数来防止 app 的 css(或DllPlugin2 的 css)覆盖 DllPlugin1 的 csswebpack.dll1.config.js{ ... use: ["style-loader", "css-loader"], }webpack.dll2.config.js{ ... use: [ { loader: "style-loader", options: { base: 1000 } }, "css-loader", ], }webpack.app.config.js{ ... use: [ { loader: "style-loader", options: { base: 2000 } }, "css-loader", ], }总结本节我们介绍了 style-loader 的使用方法和 style-loader 包含的几个常用参数配置。style-loader 一般和 css-loader 配合使用。七、Postcss-Loaderpostcss-loader在使用 postcss-loader 之前,我们先来了解下 PostCSS。以下摘自 PostCSS 简介。PostCSS 是一个允许使用 JS 插件转换样式的工具。 这些插件可以检查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优秀的功能。PostCSS 的 Autoprefixer 插件是最流行的 CSS 处理工具之一。由此我们可以知道通过使用 PostCSS 和 相应的插件,我们可以完成样式格式化、自动根据浏览器的支持情况增加样式前缀、使用先进的 CSS 特性等很多优秀的功能,截止到目前,PostCSS 有 200 多个插件。你可以在 插件列表 找到他们。如果我们想在 webpack 中使用 PostCSS 及 相应插件完成我们想要的功能,这时就需要通过 postcss-loader 来处理。使用index.html<!DOCTYPE html> <html lang="en"> <head> <title>postcss-loader</title> </head> <body> </body> </html>index.jssrc/index.js 文件中使用 import 导入 index.css 并输出导入的样式内容。import indexCss from "./index.css";index.cssbody { display: flex; }安装 postcss-loader 和 postcssnpm install postcss-loader postcss -D安装成功后,将 postcss-loader 配置到 webpack.config.js 中,postcss-loader 通过加载插件转换 css 内容,转换后的内容虽然是 .css 文件,但是仍需传递给 css-loader 做后续处理。module.exports = { ... module: { rules: [ { test: /\.(css)$/, use: ['style-loader', 'css-loader', 'postcss-loader'] } ] } };以上只是将 postcss-loader 配置到了 webpack 中,执行打包命令可以正常打包,但 css 内容不会发生变化,postcss-loader 需要通过插件来达到我们想要的效果,下面我们以自动添加前缀为例看下效果。安装 autoprefixernpm install autoprefixer -Dpostcss.config.js项目根目录下新建 postcss.config.js 文件,配置 autoprefixer 插件,和要对应的浏览器版本。其中 browsers 的配置可以配置到 package.json 或者 .browserslistrc 文件下,如果配置到 postcss.config.js 中打包时会有警告,不过我们为了演示 autoprefixer 效果,不对此做处理。module.exports = { plugins: [ require('autoprefixer')({ 'browsers': ['> 1%', 'last 2 versions'] }) ] };配置完成后,执行打包命令,打包成功后在浏览器中打开 dist/index.html 文件,在控制台中我们可以看到,样式代码已经自动增加前缀。配置项postcss-loader 包含下面几个配置项executepostcssOptionssourceMapimplementationexecute默认:undefined值类型:Boolean作用:在 CSS-in-JS 中,如果您想要处理在 JavaScript 中书写的样式,需要使用 postcss-js 解析器,添加 execute 选项并设置为 true。webpack.config.jsmodule.exports = { ... { loader: "postcss-loader", options: { postcssOptions: { parser: "postcss-js", }, execute: true, }, } };postcssOptions默认:undefined值类型:Object | Function作用:允许设置 PostCSS options 和插件。支持所有PostCSS选项。webpack.config.jsmodule.exports = { ... { loader: "postcss-loader", options: { // object postcssOptions: { ... } // function postcssOptions: (loaderContext) => { return { ... } } }, } };sourceMap默认:取决于 devtool 选项值类型:Boolean作用:是否开启 sourceMapwebpack.config.jsmodule.exports = { ... { loader: "postcss-loader", options: { sourceMap: true }, } }; implementation默认:postcss值类型:Function | String作用:implementation 选项决定使用哪个 PostCSS 实现。覆盖本地安装的 postcss 的 peerDependency 版本webpack.config.jsmodule.exports = { ... { loader: "postcss-loader", options : { implementation : require ( "postcss" ) } } };总结本节我们介绍了 postcss-loader 的使用方法,postcss-loader 主要是 PostCSS 在 webpack 环境下的使用方法,通过加载不同的插件来达到处理样式文件的效果,PostCSS 支持的插件非常丰富,本文只是通过自动添加前缀的例子做一个展示,想看其他插件的使用方法可以去 PostCSS 官网查看。八、Sass-Loadersass-loaderSass sass 是一种 css 的预编译语言。它提供了 变量(variables)、嵌套(nested rules)、 混合(mixins)、 函数(functions)等功能,并且完全兼容 css,sass 能够帮助复杂的样式表更有条理,并且易于在项目内部或跨项目共享设计。当 css 变得越来越臃肿、 越来越复杂、越来越难以维护时 sass 为我们提供了 css 中不存在的特性辅助我们编写健壮、 可维护的 css 代码。在使用 sass 之前,需要在项目中安装它,而 sass-loader 的作用是加载 sass/scss 文件并将其编译为 css,通过将 style-loader 和 css-loader 与 sass-loader 链式调用,可以立刻将样式作用在 DOM 元素。使用index.html<!DOCTYPE html> <html lang="en"> <head> <title>sass-loader</title> </head> <body> <p>hello sass</p> </body> </html>index.jssrc/index.js 文件中使用 import 导入 index.scss。import indexCss from "./index.scss";index.scsssass 和 scss 其实是同一种东西,我们平时都称之为 sass,不过两者之间写法也存在些区别,感兴趣的小伙伴儿可自行查阅,我们本案例都以 scss 为例。$primary-color: #f00; body { color: $primary-color; }安装 sass-loader 和 sassnpm install sass-loader sass --save-dev安装成功后,将 sass-loader 配置到 webpack.config.js 中。module.exports = { ... module: { rules: [ { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] } ] } };执行打包命令后,在浏览器打开 dist/index.html 文件,我们可以看到我们在 index.scss 文件中定义的颜色常量被正确的显示到了标签上面,在控制台可以看到,color 的颜色属性已由定义的变量转换成了定义的颜色值。配置项sass-loader 可以通过指定 options 参数,向 sass 传递选项参数。implementationsassOptionssourceMapadditionalDatawebpackImporterwarnRuleAsWarningimplementation默认值:sass值类型:Object | String作用:sass-loader 要使用的 sass 实现。默认情况下,sass-loader 会根据你的依赖解析需要使用的实现。 只需将必需的实现添加到 package.json(sass 或 node-sass 包)中并安装依赖项即可。package.json// sass-loader 将会使用 sass 实现 { "devDependencies": { "sass-loader": "^7.2.0", "sass": "^1.22.10" } }package.json// sass-loader 将会使用 node-sass 实现 { "devDependencies": { "sass-loader": "^7.2.0", "node-sass": "^5.0.0" } }同时安装 node-sass 和 sass 的情况下,sass-loader 默认会选择 sass。 为了避免这种情况,你可以使用 implementation 选项。webpack.config.jsmodule.exports = { ... { loader: "sass-loader", options: { // Object implementation: require('sass') // String implementation: require.resolve('sass') }, } }; sassOptions默认值:sass 实现的默认值值类型:Object | Function作用:设置 sass 实现的启动选项。在使用他们之前,请查阅有关文档:Dart Sass 文档提供了所有可用的 sass 选项。webpack.config.jsmodule.exports = { ... { { loader: "sass-loader", options: { // Object sassOptions: { includePaths: ['absolute/a', 'absolute/b'], }, // Function sassOptions: (loaderContext) => { // 有关可用属性的更多信息 https://webpack.js.org/api/loaders/ ... return { includePaths: ['absolute/a', 'absolute/b'], }; }, }, } } }; sourceMap默认值:取决于 complier.devtool值,值为 false 和 eval 时,不会生成 source map。值类型:Boolean作用:是否开启 sourceMapwebpack.config.jsmodule.exports = { ... { loader: "sass-loader", options: { sourceMap: true }, } }; additionalData默认值:undefined值类型: String | Function作用:在实际的文件之前要添加的 sass / scss 代码。下面的示例中,width、value 可以在 index.scss 中直接引用。webpack.config.jsmodule.exports = { ... { loader: "sass-loader", options : { // String additionalData: '$width:' + process.env.NODE_ENV + ';', // Function sync additionalData: (content, loaderContext) => { ... return '$value: 100px;' + content; } // Function async additionalData: async (content, loaderContext) => { ... return '$value: 100px;' + content; } } } }; webpackImporter默认值:true值类型: Boolean作用:开启 / 关闭默认的 Webpack importer。在某些情况下,可以提高性能。但是请谨慎使用,因为 aliases 和以 〜 开头的 @import 规则将不起作用。 你可以传递自己的 importer 来解决这个问题(参阅 importer docs)。webpack.config.jsmodule.exports = { ... { loader: "sass-loader", options : { webpackImporter: false, } } };warnRuleAsWarning默认值:false (在下一个大版本发布中它将默认设置为 true)值类型: Boolean作用:将 @warn 规则视为 webpack 警告而不是日志。index.scss$known-prefixes: webkit, moz, ms, o; @mixin prefix($property, $value, $prefixes) { @each $prefix in $prefixes { @if not index($known-prefixes, $prefix) { @warn "Unknown prefix #{$prefix}."; } -#{$prefix}-#{$property}: $value; } #{$property}: $value; } body { @include prefix('display', 'flex', 'a'); }webpack.config.jsmodule.exports = { ... { loader: "sass-loader", options : { warnRuleAsWarning: true, } } };在上面的例子中,我们 $prefixes 值传入 a,执行打包命令,虽然可以打包成功,但是控制台会输出警告,如果我们将 $prefixes 值传入 $known-prefixes 中定义的 o,则控制台不会显示警告。总结本节我们介绍了 sass-loader 的使用方法和一些配置参数。通过使用 sass-loader 和 sass 可以让我们让我们的样式表更有条理并且易于维护。
0
0
0
浏览量2014
懒人学前端

第三章:操作符

一、||= 、&&= 和 ??= 是什么?x ||= y逻辑或赋值运算符 ||= 的含义是:如果 x 为假,将 y 赋值给 x,即:if (!x) { x = y }逻辑或赋值 ||= 的应用:const a = { duration: 50, title: '' }; a.duration ||= 10; console.log(a.duration); // 50 a.title ||= 'title is empty.'; console.log(a.title); // "title is empty"x &&= y逻辑与赋值运算符 &&= 的含义是:如果 x 为真,将 y 赋值给 x,即:if (x) { x= y }逻辑与赋值运算符 &&= 的应用:const a = { duration: 50, title: '' }; a.duration &&= 10; console.log(a.duration); // 10 a.title &&= 'title is empty.'; console.log(a.title); // ""x ??= y逻辑空赋值运算符 x ??= y 的含义是:如果 x 为空值(null 或 undefined),将 y 赋值给 x,即if (x === null || x === undefined) { x = y }逻辑空赋值运算符 ??= 的应用:const a = { duration: 50, title: '' }; a.duration ??= 10; console.log(a.duration); // 50 a.title ??= 'title is empty.'; console.log(a.title); // "" 可选链 ?. 有什么用?面试高频指数:★★★☆☆MDN 上对可选链的定义:可选链运算符(?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 运算符的功能类似于 . 链式运算符,不同之处在于,在引用为空 (null 或者 undefined) 的情况下不会引起错误,返回 undefined。可选链可以帮助我们减少判断有效值的代码,特别是深层嵌套对象下访问某个属性,如果属性不存在,不会引起错误。如果调用对象上不存在的函数,会报错,如下图:如果使用 ?. 判断函数调用,就不会报错,如面代码:let person = {  name: "zhangsan",  details: { age: 20 }}console.log(person.getGender?.())空值合并操作符可以在使用可选链时设置一个默认值:let person = {  name: "zhangsan",  details: { age: 20 }}let add = person?.city ?? '默认值';console.log(add) // '默认值'作者:LeetCode链接:https://leetcode.cn/leetbook/read/javascript-interview-2/7m7w0v/来源:力扣(LeetCode)著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
0
0
0
浏览量1970
懒人学前端

项目题全解:登陆注册—上

Step 1:项目依赖安装安装依赖本项目用到的依赖可以在此预先安装好:antdredux与react-reduxreact-router-domaxiosless与less-loader命令行:npm i antd redux react-redux react-router-dom@6 axios less less-loader@6.0.0 --saveStep 2:登录注册的基本页面样式基础步骤:第一步:按照要求搭建项目结构页面效果:登录页面:注册页面:具体代码:登录页面 Login.jsximport React from 'react' import { Form, Input, Button, message } from 'antd'; import { UserOutlined, LockOutlined } from '@ant-design/icons'; import {Link, useNavigate} from 'react-router-dom' import "./less/Login.less" import logoImg from '../assets/logo.png' import {LoginApi} from '../request/api' export default function Login() { const navigate = useNavigate() const onFinish = (values) => { LoginApi({ username: values.username, password: values.password }).then(res=>{ if(res.errCode===0){ message.success(res.message) // 存储数据 localStorage.setItem('avatar', res.data.avatar) localStorage.setItem('cms-token', res.data['cms-token']) localStorage.setItem('editable', res.data.editable) localStorage.setItem('player', res.data.player) localStorage.setItem('username', res.data.username) // 跳转到根路径 setTimeout(()=>{ navigate('/') }, 1500) }else{ message.error(res.message) } }) }; return ( <div className="login"> <div className='login_box'> <img src={logoImg} alt="" /> <Form name="basic" initialValues={{ remember: true, }} onFinish={onFinish} autoComplete="off" > <Form.Item name="username" rules={[ { required: true, message: '请输入用户名!', }, ]} > <Input size="large" prefix={<UserOutlined />} placeholder="请输入用户名" /> </Form.Item> <Form.Item name="password" rules={[ { required: true, message: '请输入密码!', }, ]} > <Input.Password size="large" prefix={<LockOutlined />} placeholder="请输入密码" /> </Form.Item> <Form.Item> <Link to="/register">还没账号?立即注册</Link> </Form.Item> <Form.Item> <Button size='large' type="primary" htmlType="submit" block>登录</Button> </Form.Item> </Form> </div> </div> ) }登录页面 css 样式:Login.css.login { background: #fff; width: 100vw; height: 100vh; position: relative; } .login .login_box { width: 450px; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } .login .login_box img { display: block; margin: 0 auto 20px; width: 408px; }注册页面:Register.jsximport React from 'react' import { Form, Input, Button, message } from 'antd'; import { UserOutlined, LockOutlined } from '@ant-design/icons'; import { Link, useNavigate } from 'react-router-dom' import "./less/Login.less" import logoImg from '../assets/logo.png' import { RegisterApi } from '../request/api' export default function Register() { const navigate = useNavigate() const onFinish = (values) => { RegisterApi({ username: values.username, password: values.password }).then(res => { if (res.errCode === 0) { message.success(res.message); // 跳到登录页 setTimeout(() => navigate('/login'), 1500) } else { message.error(res.message); } }) }; return ( <div className="login"> <div className='login_box'> <img src={logoImg} alt="" /> <Form name="basic" initialValues={{ remember: true, }} onFinish={onFinish} autoComplete="off" > <Form.Item name="username" rules={[ { required: true, message: '请输入用户名!', }, ]} > <Input size="large" prefix={<UserOutlined />} placeholder="请输入用户名" /> </Form.Item> <Form.Item name="password" rules={[ { required: true, message: '请输入密码!', }, ]} > <Input.Password size="large" prefix={<LockOutlined />} placeholder="请输入密码" /> </Form.Item> <Form.Item name="confirm" dependencies={['password']} hasFeedback rules={[ { required: true, message: '请再次确认密码!', }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('请输入相同密码!')); }, }), ]} > <Input.Password size="large" prefix={<LockOutlined />} placeholder="请再次确认密码" /> </Form.Item> <Form.Item> <Link to="/login">已有账号?前往登录</Link> </Form.Item> <Form.Item> <Button size='large' type="primary" htmlType="submit" block>立即注册</Button> </Form.Item> </Form> </div> </div> ) }注册页面 css 样式(同登录页面):Login.cssStep 3:封装 axios 请求基础操作:第一步:在 src 下创建 request 目录,并在其中创建 request.js 及 api.js。第二步:在 Register.jsx 中封装 axios 请求import axios from 'axios' // 配置项 const axiosOption = { baseURL: '/api', timeout: 5000 } // 创建一个单例 const instance = axios.create(axiosOption); export default instance;第三步:在 api.js 中使用以下格式定义接口。当项目体积过大,api 数量增多时,可以在各个文件夹下建立单独的 api 文件,实现模块化管理。import request from './request' export const xxApi = () => request.get('/xx')页面效果:具体代码:request.js:import axios from 'axios' // 配置项 const axiosOption = { baseURL: '/api', timeout: 5000 } // 创建一个单例 const instance = axios.create(axiosOption); // 添加请求拦截器 instance.interceptors.request.use(function (config) { let token = localStorage.getItem('cms-token') if(token){ config.headers = { 'cms-token': token } } return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 instance.interceptors.response.use(function (response) { // 只需要 response 中的 data 数据 return response.data; }, function (error) { // 对于返回不同状态码的报错,前端可以针对不同的响应错误进行提示。 return Promise.reject(error); }); export default instance;api.jsimport request from './request' // 注册 export const RegisterApi = (params) => request.post('/register', params) // 登录 export const LoginApi = (params) => request.post('/login', params)
0
0
0
浏览量1861
懒人学前端

6.配置

一、开发环境开发环境调优在之前的章节中我们介绍了 Webpack 的基础配置,一些 Loader 和 Plugin 的使用方法,到此我们应该对 Webpack 有了一定的认识,Webpack 作为打包工具的重要使命之一就是提升效率,下面我们介绍一些对日常开发有一定帮助的 Webpack 插件、配置。source map插件介绍使用 webpack-dev-server模块热替换source mapWebpack 打包编译后的代码基本不具备可读性,工程发布或启动后此时若代码跑出一个错误,想要回溯它的调用栈是非常困难的。而有了 source map 在加上浏览器的调试工具,要做到追踪错误和警告就容易的多了。source map 指的是将编译、打包、压缩等操作后的代码映射回原文件的过程。开发环境通过 source map 我们可以直接看到源代码调试,生产环境通过 source map 我们可以通过工具回溯到报错的代码位置。为我们进一步分析错误提供了便利。在开发环境即 mode 选项设置为 development 模式下,Webpack 将自动生成映射文件,source map 除了将 Javascript 映射回原文件外,还同样适用于样式文件。source map 配置Javascript 的 source map 通过配置项 devtool 来启用,只要在 webpack.config.js 中添加 devtool 即可。index.jsconst divElement = document.createElement("div"); divElement.className = "demo"; divElement.innerHTML = 'mini-css-extract-plugin'; document.body.appendChild(divElement);webpack.config.jsconst path = require('path'); module.exports = { // ... devtool: 'source-map', }对于 CSS、SCSS、Less 来说,需要在 loader 中添加 source map 配置。webpack.config.jsconst path = require('path'); module.exports = { // ... module: { rules: [ { test: /\.(css)$/, use: [ 'style-loader', { loader: 'css-loader', options: { sourceMap: true } } ] } ] }, }打包后,在 dist 文件夹下除了 index.js 外还生成了一个 index.js.map 文件。在生成 mapping 文件的同时,还为 index.js 添加了一个引用注释,以便开发工具知道在哪里可以找到它。dist/index.js(function() { // 内容 })(); //# sourceMappingURL=index.js.map当我们打开浏览器的开发者工具时,map 文件会同时被加载,这时浏览器会使用它来对打包后的 bundle 进行解析。分析出源码的内容。当我们打断点进行调试时,可以直接调试源码。source map 分类Webpack 支持的 source map 大概分为两种,inline(内连) 和 separate(独立)两种方式。inline 类型即生成的映射关系内容保存在 bundle 中不生成单独的文件,separate 类型即可生成单独的 map 文件可以独立使用。source map 支持类型很多,下面只介绍其中几种,详细信息可官网查看,source map官网inline source map 类型Webpack 提供了多种内联映射文件类型。通常 eval 是起点,因为它是速度和质量之间的良好折衷,同时在 Chrome 和 Firefox 浏览器中可以可靠地工作。下面我们介绍两个内连类型的例子。注意:为了查看效果我们去掉 webpack.config.js 中的 mode 配置。devtool: "eval"eval生成代码,其中每个模块都包装在一个eval函数中。并且都包含 //# sourceURL,此选项会非常快的构建。(()=>{var __webpack_modules__={ 138:()=>{ eval('const divElement = document.createElement("div");\ndivElement.className = "demo";\ndivElement.innerHTML = \'mini-css-extract-plugin\';\ndocument.body.appendChild(divElement);\n\n//# sourceURL=webpack://6.1/./src/index.js?') } },__webpack_exports__={};__webpack_modules__[138]()})(); devtool: "eval-source-map"每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。(() => {var __webpack_modules__={ 138: ()=>{ eval('const divElement = document.createElement("div");\ndivElement.className = "demo";\ndivElement.innerHTML = \'mini-css-extract-plugin\';\ndocument.body.appendChild(divElement);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTM4LmpzIiwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vNi4xLy4vc3JjL2luZGV4LmpzP2I2MzUiXSwic291cmNlc0NvbnRlbnQiOlsiY29uc3QgZGl2RWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XG5kaXZFbGVtZW50LmNsYXNzTmFtZSA9IFwiZGVtb1wiO1xuZGl2RWxlbWVudC5pbm5lckhUTUwgPSAnbWluaS1jc3MtZXh0cmFjdC1wbHVnaW4nO1xuZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChkaXZFbGVtZW50KTsiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///138\n') } },__webpack_exports__={};__webpack_modules__[138]()})();separate source map 类型Webpack 提供了多种独立映射文件类型。我们常用的为 source-map 类型,source-map 类型可以生成单独的 map 文件,有了 source map 也就意味着任何人都可以通过浏览器调试工具看到工程源码,这对于安全性来说有了极大隐患。那么如何能保证线上问题可以追踪又能防止源码泄漏,Webpack 提供了 hidden-source-map 和 nosources-source-map 两种策略来提升 source map 的安全性。devtool: "hidden-source-map"hidden-source-map 与 source-map 作用相同都会生成一个 map 文件,唯一区别是不会为 bundle 添加引用注释。我们在浏览器中直接打开工程,是看不到原文件的,生成的 map 文件只是作为我们追踪错误信息的依据。(()=>{const e=document.createElement("div");e.className="demo",e.innerHTML="mini-css-extract-plugin",document.body.appendChild(e)})();警告:你不应将 source map 文件部署到 web 服务器。而是只将其用于错误报告工具。devtool: "nosources-source-map"nosources-source-map 创建的 source map 不包含 sourcesContent(源代码内容)。虽然在 bundle 中增加了 //# sourceMappingURL,但是当我们在浏览器中打开源码文件时是看不到源码内容的。index.js(()=>{const e=document.createElement("div");e.className="demo",e.innerHTML="mini-css-extract-plugin",document.body.appendChild(e)})(); //# sourceMappingURL=index.js.map这仍然会暴露反编译后的文件名和结构,但它不会暴露原始代码。插件介绍Webpack 拥有非常强大的生态系统,社区中相关的工具也是数不胜数,这里我们介绍两个项目中常用的插件,可以节省开发效率和减少我们操作步骤。html-webpack-pluginhtml-webpack-plugin 可以自动创建 html 文件,也支持使用 html 文件模版。html-webpack-plugin 会自动将所有必要的 css、javascript、manifest 和 favicon 文件注入到生成的 html 文件中。之前在 5.2 章节做过 html-webpack-plugin 的介绍,所以这里不在详细介绍。安装 html-webpack-pluginnpm install html-webpack-plugin --save-devwebpack.config.jsconst HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { ... plugins: [ ... new HtmlWebpackPlugin() ] };clean-webpack-pluginclean-webpack-plugin 插件用于在每次构建工程时清除上次构建生成的文件,有了这个插件我们再也不用手动清除构建目录了。安装 clean-webpack-pluginnpm install clean-webpack-plugin --save-devwebpack.config.jsconst { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { ... plugins: [ ... new CleanWebpackPlugin() ] };使用 webpack-dev-server在之前的案例中,我们一直都是更改代码后执行打包命令,然后手动去打包文件中打开 html 文件查看效果,这种方式在实际的项目发开中会增加很多额外的重复工作,并且打包后的内容需要发布到服务上才能被其他伙伴访问。为了提升开发效率,在构建代码并部署到生产环境之前,我们需要一个本地环境,用于运行我们开发的代码。这个环境相当于提供了一个简单的服务器,用于访问 webpack 构建好的静态文件,我们日常开发时可以使用它来调试前端代码。webpack-dev-server 可以很好的帮我们解决这个需求,webpack-dev-server 是 webpack 官方提供的一个工具,可以基于当前的 webpack 构建配置快速启动一个静态服务。当 mode 为 development 时,会具备 hot reload 的功能,即当源码文件变化时,会即时更新当前页面,以便我们实时看到效果。webpack-dev-server 仅应用于开发环境。下面简单介绍下 webpack-dev-server 的使用,详细配置可查看官网。安装npm install webpack-dev-server -Dpackage.json 中增加 scripts "scripts": { ... "serve": "webpack serve" },配置成功后,命令行执行 npm run serve,项目启动成功并在控制台输出了网址,打开此网址即可运行我们现有代码。当我们修改 index.js 内容时页面会自动刷新。模块热替换当项目功能体量很大页面元素较多时,使用 webpack-dev-server 实现页面整体刷新会影响开发体验,这时我们会考虑使用模块热替换。模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。HMR 功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:重新加载页面期间保留应用程序状态。只更新变更部分内容,节省开发时间。在源代码 CSS/JS 内容产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。使用 HMRHMR 是 Webpack 内置的插件,我们可以通过 webpack-dev-server 配置来开启 HMR。webpack.config.js module.exports = { ... devServer: { hot: true, } }上面的配置产生的结果是 Webpack 为每个模块绑定一个 module.hot 对象,这个对象可以调用 HMR 的 API。通过这些 API 我们可以对特定模块开启或关闭热替换。index.jsconst add = (a, b) => { return a + b; }; const addRes = add(3, 5); const divElement = document.createElement('div'); divElement.innerHTML = addRes; document.body.appendChild(divElement); if (module.hot) { module.hot.accept() }启动项目后可以在页面上看到结果 8,当修改 add 函数中的参数时,页面上 8 并没有清空,HMR 会使应用在当前浏览器环境下又执行来一遍 index.js (包括其依赖的模块)内容,但是页面本身并没有刷新。调用 HMR API 可以如上面例子中手动调用,我们还可以借助现成的工具去调用,如 react-hot-loader、vue-loader 等。喜欢的小伙伴可以自行研究。总结本章我们介绍了 Webpack 开发环境下的常用的配置和插件以及如何使用 HMR。篇幅有限只写了些项目中用到的,Webpack 周边实用插件很多,感兴趣的小伙伴儿可以选择一些去使用一下,这对于了解 Webpack 也会有很大帮助。
0
0
0
浏览量1718
懒人学前端

6.配置—下

二、生产环境生产环境调优配置文件在实际的项目中,我们经常会有区分环境的需求,如本地环境、测试环境、生产环境。不同的环境往往对应一些不同的配置,如 mode、环境变量等,如何通过 webpack 按照不同环境采用不同配置呢?一般有以下两种方式。1、使用同一个配置文件,不同环境传入不同变量来控制配置webpack.config.js... const NODE_ENV = process.env.NODE_ENV; const isDev = NODE_ENV === 'development'; module.exports = { mode: NODE_ENV, devtool: isDev ? 'source-map' : 'inline-source-map', ... }package.json{ ... "scripts": { "dev": "NODE_ENV=development webpack", // 开发环境 "build": "NODE_ENV=production webpack" // 生产环境 } }上面的配置中,通过在 package.json 中修改 NPM Scripts,传入 NODE_ENV 变量。开发环境参数值为 development,生产环境参数值为 production。在 webpack 配置文件中使用 Node.js 的 process 对象获取传入的参数。执行 npm run dev 即执行开发环境配置,生成的打包文件中包含 source map 文件同时 index.js 中包含 source map 映射路径。执行 npm run build 同理执行生产环境配置,生成的打包文件中 index.js 文件不包含 source map 映射路径。至此我们成功的区分了不同环境。2、为不同环境创建各自的配置文件生产环境创建一个 prod.config.js 文件,开发环境配置一个 dev.config.js 文件,修改 package.json 中的 scripts,不同命令执行不同的配置文件。prod.config.jsconst path = require('path'); module.exports = { mode: 'production', devtool: 'hidden-source-map', entry: { index: './src/index.js', }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, }dev.config.jsconst path = require('path'); module.exports = { mode: 'development', devtool: 'source-map', entry: { index: './src/index.js', }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, }package.json{ ... "scripts": { "dev": "webpack --config=dev.config.js", // 开发环境 "build": "webpack --config=prod.config.js" // 生产环境 } }上面的两个配置文件中,项目入口出口都一样,只有打包模式和 source map 模式不同,分别执行 npm run dev 和 npm run build 可以看到生成文件内容不同。对于需要多种环境配置的文件中,我们可以将环境中共用的部分如入口、出口等配置提取出来放在一个公共配置中,将公共配置提取到 common.config.js 中,个性化配置放在不同环境对应的配置中,通过 webpack-merge 插件将配置组合起来。下面以生产环境配置为例。prod.config.jsconst commonConfig = require('./common.config.js'); const { merge } = require('webpack-merge'); module.exports = merge(commonConfig, { mode: 'production', devtool: 'hidden-source-map' });common.config.js... module.exports = { entry: { index: './src/index.js', }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, ... };执行打包命令后查看输出文件,可以看到输出内容与单独配置无异。区分环境变量通常我们需要为生产环境和本地环境添加不同的环境变量,在 Webpack 中可以使用 DefinePlugin 进行设置,在 5.1 章节我们已经对 DefinePlugin 配置做过详细的介绍,所以下面我们只加一个区分服务地址的配置。prod.config.js... const webpack = require('webpack'); module.exports = { ... plugins: [ ... new webpack.DefinePlugin({ BASE_URL: 'http://ip:8201' }) ] }dev.config.js... const webpack = require('webpack'); module.exports = { ... plugins: [ ... new webpack.DefinePlugin({ BASE_URL: 'http://localhost:8201' }) ] }index.js... console.log(BASE_URL)执行不同环境的打包命令,即可区分不同的服务地址。资源压缩在将项目资源发布到线上环境前,我们通常会进行代码压缩,或者叫 uglify,意思是移除空格并混淆代码,一般在压缩后代码体积会变小同时代码变得不可读,在一定程度上增加了安全性。压缩 JavaScriptWebpack 5 中已经集成了 terser-webpack-plugin 来压缩我们的代码,当我们在 webapck.config.js 中 mode 设置为 production 时,打包后的代码会被压缩。如果想自定义添加压缩配置,我们需要安装 terser-webpack-plugin,下面以删除代码中 console.log 为例。安装 terser-webpack-pluginnpm install terser-webpack-plugin -Dprod.config.jsconst TerserPlugin = require('terser-webpack-plugin'); ... module.exports = merge(commonConfig, { ... optimization: { // 在不设置 mode 的情况下,minimize 设置为 true 也会开启压缩 // minimize: true, minimizer: [ new TerserPlugin({ terserOptions: { compress: { pure_funcs: ['console.log'], }, }, }), ], }, });index.jsconst add = (a, b) => { return a + b } const addRes = add(3, 7); const divElement = document.createElement('div'); divElement.innerHTML = addRes; document.body.appendChild(divElement); console.log(addRes);执行打包命令后,可以看到生成的 bundle 文件中已经移除了 console.log 代码。压缩 CSS在压缩 CSS 文件之前,我们要先使用 MiniCssExtractPlugin 插件将样式文件提取出来,接着使用 css-minimizer-webpack-plugin 插件进行压缩。css-minimizer-webpack-plugin 插件开启压缩的配置方式也是传入到 minimizer 中,与 terser-webpack-plugin 类似,只是传入压缩插件本身的配置不同。安装 css-minimizer-webpack-pluginnpm install css-minimizer-webpack-plugin -Dprod.config.js... const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = merge(commonConfig, { devtool: 'hidden-source-map', module: { rules: [ { test: /\.css$/i, use: [ { loader: MiniCssExtractPlugin.loader, options: { esModule: true, }, }, 'css-loader', ], }, ], }, optimization: { minimizer: [ new CssMinimizerPlugin(), ], }, plugins: [new MiniCssExtractPlugin()], });index.jsimport styles from './index.css'; const divElement = document.createElement('div'); divElement.innerHTML = 'demo'; divElement.className = 'demo'; document.body.appendChild(divElement);index.css.demo { color: red; }执行 npm run build,可以看到打包文件中的 index.css 文件内容已被压缩。资源体积监控在实际的项目开发中,webpack 打包的体积速度往往是我们比较关注的问题,我们可以通过打包后输出文件包的大小来分析每个模块的体积,但这种反向分析往往会花费很多时间。VS Code 中有一个插件 Import Cost 可以帮助我们持续监控引入模块(主要是node_module中的模块)的大小。它会为我们计算该模块压缩后及 gzip 后将占多大体积。当我们发现某些包过大就可以采取一些措施,比如 lodash 中只引入使用到的子模块。另外一个很有用的工具是 webpack-bundle-analyzer,它能帮助我们分析 bundle 的构成,webpack-bundle-analyzer 可以帮我们生成一张 bundle 的模块组成结构图,每个模块所占的体积一目了然。安装 webpack-bundle-analyzernpm install webpack-bundle-analyzer -Dprod.config.js... const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = merge(commonConfig, { ... plugins: [ new BundleAnalyzerPlugin() ] });执行打包命令后,浏览器中会启动 bundle 组成结构图,包括各模块体积。最后我们还可以自动化的对资源体积进行监控,bundlesize 可以帮我们做到这一点。安装 bundlesizenpm install bundlesize -Dpackage.json{ ... "scripts": { "test": "bundlesize", "dev": "webpack --config dev.config.js", "build": "webpack --config prod.config.js" }, "bundlesize": [ { "path": "./dist/index.js", "maxSize": "1 kB" } ], }执行 npm test 控制台中可以看到检测结果。总结本章我们介绍了在生产环境下可以做哪些配置。我们介绍了使用配置文件区分环境、传入不同环境变量、如何开启压缩并且如何做自定义压缩、监控输出资源体积等方法。对于使用的插件都只是简单介绍了如何使用,详细的配置可以参照官网。
0
0
0
浏览量1233
懒人学前端

8.阶段测 Ⅱ

1. 选择器与元素的相关度越高,优先级越高还是低?高。2. 请从选择器来源的角度,写出三种选择器之间的优先级关系。开发者定义选择器 > 用户定义选择器 > 浏览器默认选择器。3. 同级时,复合选择器优先级更高还是单选择器优先级更高?复合选择器。4. 选择器与元素的相关度越高,优先级越高还是低?高。5. 请从选择器来源的角度,写出三种选择器之间的优先级关系。开发者定义选择器 > 用户定义选择器 > 浏览器默认选择器。6. 同级时,复合选择器优先级更高还是单选择器优先级更高?复合选择器。7. !important 的作用是?!important 可以忽略选择器 CSS 选择器优先级,让声明的属性总是生效。8. !important 的弊端有哪些?破坏原 CSS 级联规则,增加调试难度修改样式变得困难污染全局样式9. 如何避免 !important 的弊端?• 用 CSS 选择器优先级解决样式冲突• 不在全局、会被复用的插件中使用 !important• 通过 CSS 命名或 Shadow DOM 限制 CSS 作用域10. 如何计算 CSS 选择器的优先级?将 CSS 选择器优先级量化为权重,为不同类型的 CSS 选择器设置初始权重。选择器的组合,即初始权重的累加。累加值越高,优先级越高。11. 限制 CSS 选择器的作用域有几种方法?分别是什么?4种。通过 CSS 命名限制;通过 Shadow DOM 限制;通过 @document 限制;通过 CSS Modules 限制。12. 如何通过 Shadow DOM 限制 CSS 选择器的作用域?通过 JS 给已有元素创建影子 DOM,将样式通过style 标签写入影子 DOM。13. CSS 中的单位分为哪几类?绝对长度单位和相对长度单位。14. 百分比 % 相对于谁?父元素。15. 颜色值都有哪几种表示方法?关键字、RGB color model、HSL color model。16. 盒模型的组成是怎样的?盒模型由内向外:内容 content + 内边距 padding + 边框 border + 外边距 margin。17. 简单描述一下盒模型有哪两类。• 标准盒模型box-sizing:content-box:width 和 height 设置内容 content 的宽和高• 替代盒模型box-sizing:border-box:width 和 height 设置内 content + 内边距 padding + 边框 border 的宽和高18. 块盒子和内联块盒子的差异在于?块盒子换行而内联块盒子不换行。19. 块盒子、内联盒子和内联块盒子三者中,width和height生效的是?块盒子和内联块盒子。20. 块盒子、内联盒子和内联块盒子三者中,竖直方向padding和margin无效的是?内联盒子。21. 可以如何给父元素设置让子元素成为弹性盒子?给父元素设置 display:flex 或 display:inline-flex。
CSS
0
0
0
浏览量635
懒人学前端

第五章:函数—上

一、什么是闭包?MDN 定义如下:闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。闭包的特性如下面的例子:function person() { var name = "John"; function showName() { console.log(name); // 'John' } return showName; } const getPerson = person(); getPerson();showName() 函数中并没有定义 name 变量,但是却可以访问到外部函数中 name 变量的值,这就是利用了闭包的特性,即嵌套函数可访问声明于它们外部作用域的变量。词法作用域词法作用域是静态作用域,通俗讲就是它可以访问到变量的范围。var name = 'John'; function greeting() { let message = 'Hi'; console.log(message + ' '+ name); }在上面的例子中:变量 name 是个全局变量,它可以在任意地方访问到,包括函数 greeting()变量 message 是个局部变量,只能在函数 greeting() 中访问到闭包在循环中下面这段代码很常见:for (var index = 1; index <= 3; index++) { setTimeout(function () { console.log('after ' + index + ' second(s):' + index); }, index * 1000); }这里输出如下:// after 4 second(s):4 // after 4 second(s):4 // after 4 second(s):4这是因为,var 定义的变量是个全局变量,当 setTimeout 调用时,全局变量index 的值被修改为 4,所以每次打印中获取到的 index 值是 4。针对上面的问题,可以使用闭包和立即执行函数(IIFE)搭配解决。闭包for (var index = 1; index <= 3; index++) { (function (index) { setTimeout(function () { console.log('after ' + index + ' second(s):' + index); }, index * 1000); })(index); } // 输出 // after 1 second(s):1 // after 2 second(s):2 // after 3 second(s):3上面我们在 setTimeout 函数外使用了立即执行函数,立即执行函数 index 是局部变量,与全局变量 index 处于不同的作用域,此时 setTimeout 函数中能拿到立即执行函数中的 index 变量的值,因此能够得到正确的输出结果。闭包的应用缓存变量的值闭包和函数内传入的值有关联。下面的代码中 createInc 函数调用后,返回一个箭头函数,箭头函数中的 index 和 startValue 是外部变量,箭头函数内可以拿到两个变量的值,并且可以对变量的值进行修改。我们可以使用闭包的特性缓存变量的值:function createInc(startValue) { let index = -1; return (step) => { startValue += step; index++; return [index, startValue]; }; } const inc = createInc(5); console.log(inc(2)); // [0, 7] console.log(inc(2)); // [1, 9] console.log(inc(2)); // [2, 11]用闭包模拟私有方法我们可以使用闭包来模拟私有方法。私有方法可以限制对代码的访问,而且可以用于管理全局变量命名,避免扰乱公共代码。let Counter = (function() { let privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); console.log(Counter.value()); // 0 Counter.increment(); Counter.increment(); console.log(Counter.value()); // 2 Counter.decrement(); console.log(Counter.value()); // 1 观察上面代码,Counter 函数立即调用之后,返回一个对象,其中包含三个函数: increment、decrement、value,它们都能访问 Counter 函数内部的私有项:privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。小练习下面代码输出什么?const foo = (function() { var v = 0 return () => { return v++ } }()) for (let i = 0; i < 10; i++) { foo() } console.log(foo())答案:10。因为 foo 是个立即执行函数,返回的是一个箭头函数。当的 foo 调用时,就是箭头函数调用,箭头函数执行了 10 次,其中变量 v 自增到 9,之所以输出 10 是因为最后一次 console.log 中的调用。二、this 的指向有哪些?函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。this 的指向有以下七种:全局上下文this 指向全局对象console.log(this === window); // true a = 37; console.log(window.a); // 37函数上下文this 指向取决于函数被调用的方式:作为对象的方法调用,this 指向该对象作为普通函数调用严格模式下,指向全局对象,浏览器中就是 window非严格模式下,为 undefinedcall、apply、bind 调用,this 指向绑定的对象作为构造函数调用,如使用 new,this 指向新的对象function f1(){ return this; } //在浏览器中:this 指向全局对象 window f1() === window; //在 Node 中:this 指向 global f1() === global; // 严格模式下:this 指向 undefined function f2(){ "use strict"; return this; } f2() === undefined; // true // call 方法:this 指向传入的指定对象 o function add(c, d) { return this.a + this.b + c + d; } var o = {a: 1, b: 3}; add.call(o, 5, 7); // 16类上下文this 指向类。在类的构造函数中,this 是一个常规对象。类中所有非静态的方法都会被添加到 this 的原型中:class Car { constructor() { // 使用 bind() 方法改变 this 指向 this.sayBye = this.sayBye.bind(this); } sayHi() { console.log(`Hello from ${this.name}`); } sayBye() { console.log(`Bye from ${this.name}`); } get name() { return 'Ferrari'; } } class Bird { get name() { return 'Tweety'; } } const car = new Car(); const bird = new Bird(); // this 指向调用者 car.sayHi(); // Hello from Ferrari bird.sayHi = car.sayHi; bird.sayHi(); // Hello from Tweety // bind() 方法改变了 this 指向,this 指向类 Car bird.sayBye = car.sayBye; bird.sayBye(); // Bye from Ferrari箭头函数this与封闭词法环境的 this 保持一致。在全局代码中,它将被设置为全局对象。var window = this; var foo = (() => this); console.log(foo() === window); // true原型链中的 this如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象。var o = { f: function() { return this.a + this.b; } }; var p = Object.create(o); p.a = 1; p.b = 4; console.log(p.f()); // 5作为一个 DOM 事件处理函数当函数被用作事件处理函数时,它的 this 指向触发事件的元素。function bluify(e) { console.log(this === e.currentTarget); // true this.style.backgroundColor = 'blue' } // 获取 id 为 test 的 button let testBtn = document.getElementsById('test'); // 将 bluify 作为元素的点击监听函数,当元素被点击时,就会变成蓝色 testBtn.addEventListener('click', bluify, false); getter 与 setter 中的 this用作 getter 或 setter 的函数都会把 this 绑定到设置或获取属性的对象。var o = { a: 1, b: 2, get sum() { return (this.a + this.b); } }; console.log(o.sum); // 输出 3小练习给出下面代码的输出结果:var length = 10; function fn() { return this.length + 1; } var obj = { length: 5, test1: function() { return fn(); } }; obj.test2 = fn; console.log(obj.test1.call()); // (1) console.log(obj.test1()); // (2) console.log(obj.test2.call()); // (3) console.log(obj.test2()); // (4)答案:输出如下:1 处为:11, this 指向 window, 因为 call 方法没有执行具体要绑定的对象。2 处为:11,this 指向 window,返回的 fn 函数的 this 指向 window3 处为:11,原因同 1 处4 处为: 6,this 指向 obj,此处函数作为对象 obj 的方法调用三、类数组的转化方式有哪些?类数组不是数组,是对象。例如 document.getElementsByTagName() 返回的 NodeList 或 arguments 等 JavaScript 对象,有与数组相似的行为,但它们并不共享数组的所有方法。arguments 对象提供了 length 属性,但没有实现如 forEach() 等数组方法。不能直接在类数组对象上调用数组方法。在函数中,arguments 对象代表函数实参。arguments 对象是一个类数组对象,类数组不是数组,因此它不是 Array 类型的实例,它是 Object 类型的实例。function add (one, two) { console.log(arguments); console.log(Array.isArray(arguments)); console.log(Object.prototype.toString.call(arguments)); } add(1, 2); 结果如下:arguments 特点arguments 既然是类数组对象,它有两个特点:可以通过 [] 获取参数, 如 arguments[0]可以使用 length 属性获取实参的个数。如 arguments.lengthfunction add() { let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } console.log(add(1, 2)); // 3类数组转成数组我们可以使用三种方式将类数组转成数组:扩展运算符Array.from()slice(),使用以下两种方式都可以:Array.prototype.slice.call(arguments)[].slice.call(arguments);// 扩展运算符 function checkArgs() { return [...arguments]; }; let result = checkArgs(1, 2); console.log(result); // [1, 2] // Array.from() function checkArgs() { return Array.from(arguments); }; let result1 = checkArgs(1, 2); console.log(result1); // [1, 2] // slice() function checkArgs() { return Array.prototype.slice.call(arguments); }; let result2 = checkArgs(1, 2); console.log(result2); // [1, 2]
0
0
0
浏览量175
懒人学前端

项目题全解:组件通信—下

Step 2:平行组件通信Step 2.1:提供三个组件的基础模板页面效果基础代码student1(A)组件<template> <div> <h2>平行组件之间通信</h2> <button>Student1</button> <hr /> </div> </template> <script> export default { name: "StudentA", data() { return {}; }, methods: {}, }; </script> <style scoped></style>student2(B)组件<template> <div> <p>这是子组件学生2</p> <p></p> </div> </template> <script> export default { name: "StudentB", data() { return {}; }, }; </script> <style scoped></style>App组件<template> <div> <StudentA></StudentA> <StudentB></StudentB> </div> </template> <script> import StudentA from "./components/Student1"; import StudentB from "./components/Student2"; export default { name: "App", // 引入的组件需要在 components 里面注册 components: { StudentB, StudentA }, data() { return {}; }, methods: {}, }; </script> <style scoped></style>Step 2.2:student1 向 App 子向父传值基础步骤第一步: 在 stuedent1 中添加点击事件 <button @click="toApp()">Student1</button>第二步:添加传递的数字data() { return { count: 70, }; },第三步:使用emit进行传递methods: { toApp() { this.$emit("getApp", 70); }, },具体代码:student1(A)组件<template> <div> <h2>平行组件之间通信</h2> <button @click="toApp()">Student1</button> <hr /> </div> </template> <script> export default { name: "StudentA", data() { return { count: 70, }; }, methods: { toApp() { this.$emit("getApp", 70); }, }, }; </script> <style scoped></style>Step 2.3:App 接收传值基础步骤第一步: 接受 sutdent1 的值 <StudentA @getApp="receiveApp"></StudentA>第二步:把原来的 count 加上传过来的 countdata() { return { count: 5, }; }, methods: { receiveApp(num) { this.count += num; }, },第三步:在 receiveApp 添加 console.log 打印查看效果console.log("原来+现在", this.count);效果展示:具体代码:App组件<template> <div> <StudentA @getApp="receiveApp"></StudentA> <StudentB ></StudentB> </div> </template> <script> import StudentA from "./components/Student1"; import StudentB from "./components/Student2"; export default { name: "App", components: { StudentB, StudentA }, data() { return { count: 5, }; }, methods: { receiveApp(num) { this.count += num; console.log("原来+现在", this.count); }, }, }; </script> <style scoped></style>Step 2.4:App 向 student2 传值基础步骤第一步: 传给 sutdent2 的值 <StudentB :toStudentB="count"></StudentB>代码展示:App 组件<template> <div> <StudentA @getApp="receiveApp"></StudentA> <StudentB :toStudentB="count"></StudentB> </div> </template> <script> import StudentA from "./components/Student1"; import StudentB from "./components/Student2"; export default { name: "App", components: { StudentB, StudentA }, data() { return { count: 5, }; }, methods: { receiveApp(num) { this.count += num; console.log("原来+现在", this.count); }, }, }; </script> <style scoped></style>Step 2.5:Student2 接受传值基础步骤第一步: 添加 props 接受参数 props: ["toStudentB"]第二步:接收值并展示 <p>{{ toStudentB }}</p>效果展示Step 2.6:最终实现具体代码:student1(A)组件<template> <div> <h2>平行组件之间通信</h2> <button @click="toApp()">Student1</button> <hr /> </div> </template> <script> export default { name: "StudentA", data() { return { count: 70, }; }, methods: { toApp() { this.$emit("getApp", 70); }, }, }; </script> <style scoped></style>student2(B)组件<template> <div> <p>这是子组件学生2</p> <p>{{ toStudentB }}</p> </div> </template> <script> export default { name: "StudentB", data() { return {}; }, props: ["toStudentB"], }; </script> <style scoped></style>App 组件<template> <div> <StudentA @getApp="receiveApp"></StudentA> <StudentB :toStudentB="count"></StudentB> </div> </template> <script> import StudentA from "./components/Student1"; import StudentB from "./components/Student2"; export default { name: "App", components: { StudentB, StudentA }, data() { return { count: 5, }; }, methods: { receiveApp(num) { this.count += num; console.log("原来+现在", this.count); }, }, }; </script> <style scoped></style>
0
0
0
浏览量2023
懒人学前端

4.图片

一、为什么要设置 alt 属性?为什么要设置 alt 属性,原因是:增强可访问性:用户有视力障碍,可以通过屏幕阅读器浏览图片描述图片加载无法的替换文本:例如图片路径、文件名拼错,资源下载失败用户使用不支持图片的浏览器,如 Lynx用户主动关闭图片显示以减少数据的传输SEO:便于网页搜索、图片搜索等索引图片和排名二、alt 属性应该填写什么内容 ?alt 属性填写内容根据图片类型决定装饰类型填写空 alt。此类图片不应使用<img> 标签,CSS background images 是更优方案文本类型填写文本本身。应避免将文本放到图像里,CSS 修饰文本是更优方案链接类型提供无障碍连接文本。如链接到 XX内容类型搜索引擎会通过图片 alt title figure 及上下文,机器或人工标注读取图片信息屏幕阅读软件也会朗读上下文 所以,如果已经在上下文中说明了图片,`alt` 可以留空 如果没有,`alt` 应是对图片本身内容的描述,语句通顺。不应过长,更不应堆砌关键字三、为什么要指定图片高度和宽度属性 ?width 和 height 用来声明图片的宽度和高度如果图片地址错误或者下载被禁用,浏览器会显式地为图片留下一定空间无需等待图片元数据下载完毕,先行渲染图片占位符,加载更快速更流畅,避免版式跳动width 和 height 应始终设置真实尺寸,不应该使用 HTML 属性来改变图片的大小如果需要改变图片尺寸,应使用 CSS 而不是 HTML四、title 属性应该填写什么内容 ?类似于超链接,title 属性用来提供进一步的支持信息省略该属性:这个元素与最近祖先的标题仍然是相关的,可以用作元素的提示信息值为空字符串:这个元素与最近祖先的标题是不相关的,不应用于这个元素的提示信息与 alt 同时存在,alt 用于图片描述,title 用于进一步支持信息,比如点击放大五、如何设置图片的说明文字 ?设置图片的说明文字的方法如下:在图片的上下文中描述图片使用图片的 alt 标签描述图片使用图片的 title标签配合祖先标题描述图片省略该属性:这个元素与最近祖先的标题仍然是相关的,可以用作元素的提示信息值为空字符串:这个元素与最近祖先的标题是不相关的,不应用于这个元素的提示信息与 alt 同时存在,alt 用于图片描述,title 用于进一步支持信息,比如点击放大使用 <div> 和 <p> 组合<div> <img> <p>图片说明文字</p> </div>使用 <figure> 和 <figcaption> 组合<figure> <img> <figcaption>图片说明文字</figcaption> </figure>六、HTML 图像和 CSS 图像的区别是什么 ?HTML 图像指用 <img> 标签插入的图像,可以设置 alt 属性,提供备选文本,可以被屏幕阅读器、蜘蛛识别CSS 图像指用 background-image 和其它 background-* 属性共同放置的图像。用来提升视觉效果,没有语义,不能提供备选文本,难以被屏幕阅读器、蜘蛛识别如果图像对内容有意义,利于 SEO,提高可访问性,应使用 HTML 图像如果图像对内容没意义,装饰作用,或者故意提高图片被采集、保存的难度,应使用 CSS 图像七、什么是矢量图形,它和位图的区别是什么 ?矢量图形使用算法定义,包含了图形和路径的定义,电脑可以根据这些定义计算出它们在屏幕上渲染时应该呈现的样子。SVG 格式可以让我们创造用于 Web 的精彩的矢量图形。位图使用像素网格定义,包含了每个像素额位置和它的色彩信息。流行的位图格式包含 Bitmap( .bmp )PNG( .png )JPEG( .jpg )和 GIF( .gi``f )区别矢量图通常体积更小,放大后,效果很好且清晰,透明无毛边。位图通常体积较大,放大后,图片变得像素化,透明有毛边。八、什么是 SVG?SVG,全称是 Scalable Vector Graphics,可缩放矢量图形,是用于描述二维的矢量图形,基于 XML 的标记语言,是基于文本由 W3C 自 1999 年起开始开发的开放网络标准SVG 能够优雅而简洁地渲染不同大小的图形SVG 与 CSS、DOM、JavaScript 和 SMIL 等其它网络标准无缝衔接SVG 可以被搜索、索引、编写脚本和压缩,利于 SEOSVG 可以使用任何文本编辑器和绘图软件创建和编辑SVG 能被无限放大而不失真或降低质量SVG 相较于同样的位图体积更小SVG 可以适应样式 / 脚本,图像每个组件都是可以通过 CSS 或 JavaScript 编写样式的元素九、SVG 对比光栅图形的优缺点是什么 ?SVG 对比光栅图形的优点:SVG 能够优雅而简洁地渲染不同大小的图形SVG 与 CSS、DOM、JavaScript 和 SMIL 等其他网络标准无缝衔接SVG 可以被搜索、索引、编写脚本和压缩,利于 SEOSVG 可以使用任何文本编辑器创建和编辑SVG 能被无限放大而不失真或降低质量SVG 相较于同样的位图体积更小SVG 可以适应样式 / 脚本,图像每个组件都是可以通过 CSS 或 JavaScript 编写样式的元素SVG 对比光栅图形的缺点:SVG 容易变得复杂,文件大小会增加,复杂 SVG 会在浏览器中,会占用很长的处理时间SVG 可能比栅格图像更难创建,具体取决于您尝试创建哪种图像SVG 不被旧版浏览器(IE8 及以下浏览器)支持由于上述原因,光栅图形更适合照片那样复杂精密的图像十、如何在 HTML 中引入 SVG?(1)方式一:使用 <img> 标签或设置 background: url() 或 background-image: url() 属性设置 <img> 的 src 属性嵌入 SVG优点:快速、熟悉的图像语法与 alt 属性中提供的内置文本等效可以通过在 <a> 元素嵌套 <img> 使图像轻松成为超链接缺点:无法使用 JavaScript 操作图像使用 CSS 控制 SVG 内容,必须在 SVG 代码中包含内联 CSS 样式 从 SVG 文件调用的外部样式表不起作用不能使用 CSS 伪类来重设图像样式( 如 :focus)(2)方式二:使用 <svg> 标签在文本编辑器中打开 SVG 文件,复制 SVG 代码,并将其粘贴到 HTML 文档中优点:SVG 内联减少 HTTP 请求,可以减少加载时间SVG 可以分配 class 和 id,并使用 CSS 修改样式。无论是在 SVG 中,还是 HTML 文档中的 CSS 样式规则。可以使用任何 SVG 外观属性作为 CSS 属性SVG 内联是唯一让你在 SVG 图像上使用 CSS 交互( 如::focus )和 CSS 动画的方法SVG 可以包在 <a> 元素中,使其成为超链接缺点:SVG 只适用于在一个地方使用,多次使用会导致资源密集型维护SVG 会增加 HTML 文件的大小SVG 内联后,不能像普通图片一样被缓存SVG 的 <foreignObject> 元素中包含回退,但支持 SVG 浏览器仍然会下载任何后备图像。需要考虑支持过时的浏览器,增加额外开销是否真的值得(3)方式三:使用 <iframe> 标签嵌入 SVG不推荐,缺点:<iframe> 支持回退机制切换 <iframe> 的 src 后,点浏览器后退按钮,<iframe> 回退,主页面不会回退同源策略除非 SVG 和您当前的网页具有相同的 origin否则不能在主页面上使用 JavaScript 来操纵 SVG十、如何创建响应式的图片 ?对于装饰性图片可以使用 CSS 创建响应式图片image-set 支持声明一组图像的地址,分辨率和类型 type 例如background-image: image-set("1x.png" 1x, "2x.png" 2x, "3x.png" 3x)* `media queries` 支持媒体查询@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 264dpi) { /* 高分辨率下,如 iPad 2 的背景图 */ background-image: url("3x.png") }对于非装饰性图片,可以使用 HTML 标签创建响应式图片添加 <meta> 标签,强制浏览器,特别是手机浏览器以设备宽度来加载网页<meta name="viewport" content="width=device-width">分辨率切换:不同的尺寸设置 srcset 属性,声明浏览器允许的图像集和每个图像的大小格式:文件名 + 半角空格 + 图像自身宽度(以像素为单位,写作 w )多张图像用英文半角逗号 , 分隔设置 sizes 属性,声明的设定媒体条件为真时最佳尺寸图像格式:媒体查询条件 + 半角空格 + 图像填充槽的宽度多个媒体查询条件用英文半角逗号 , 分隔槽的宽度支持固定值 px em 或者相对于视口的长度 vw 不支持百分比最后一个槽的宽度不设置媒体查询条件,当没有任何一个媒体条件为真时生效浏览器如何匹配响应式图片查询设备宽度检查 sizes 列表中媒体查询条件找到第一个为真的条件对应槽的宽度找到没有条件为真时对应槽的宽度加载 srcset 列表中引用的最接近槽的宽度的图像Chrome 总是优先加载已缓存的图像分辨率切换:相同的尺寸,不同的分辨率书写 CSS 属性,声明图像的显示宽度 width设置 srcset 属性,格式:文件名 + 半角空格 + 像素密度描述符( x 符号)根据当前设备的像素密度加载图像美术设计:不同内容的图像使用 <pictrue> 标签包裹 <img> 标签作为默认值包裹多个 <source> 标签设置 <media> 属性,声明媒体查询条件设置 srcset 属性,声明条件为真时显示图片。可以设置多张不同分辨率图像可选设置 sizes 属性,声明的设定媒体条件为真时最佳尺寸图像美术设计:不同格式的图像,优先使用现代图像格式,并兼容老浏览器使用 <picture> 标签包裹 <img> 标签作为默认值设置 type 属性,声明文件类型(MIME Type)设置 srcset 属性,声明条件为真时显示图片。可以设置多张不同分辨率图像可选设置 sizes 属性,声明的设定媒体条件为真时最佳尺寸图像十一、为什么不能使用 JavaScirpt 来实现响应式图片 ?图片会在主解析器加载和解析 JavaScript 之前预加载,用于加快页面加载速度当用 JavaScript 检测可视窗口的宽度,想要改变图片时,原来 <img> 标签中的 src 引用的图片已经被加载,从而产生了重复加载违背了响应式图像的理念使用 JavaScript 较难判断当前浏览器支持的图片类型,实现响应式加载图片类型使用 HTML 的响应式图片属性可以让浏览器自动选择最合适的图片预加载,无需等待 CSS 和 JavaScript 加载和解析开启缓存的场景下,优先返回满足条件已被缓存的图片,进而节省流量,提高加载速度十二、如何调试响应式 ?使用浏览器的开发者工具可以调试响应式通过 Firefox 的 Tools > Web Developer > Responsive Design View 或 Chrome 的 DevToos 的 Toggle device toolbar 模拟不同设备,或者人工调整屏幕宽度判断响应式图片配置是否已经生效相同图片,通过 DOM 检查工具,查看当前加载的图片宽度通过 Network 面板,查看当前下载的图片地址Chrome 浏览器需要在 Network 面板下勾选 Disable cache,禁用缓存避免 Chrome 优先选择缓存图片,而不是响应式地适配图
0
0
0
浏览量1687
懒人学前端

2.选择器

一、CSS 中哪些选择器?二、CSS 选择器有哪些组合方式?① 运算符组合选择器(关系选择器)② 属性组合选择器③ 群组选择器三、对比伪类,伪元素伪类和伪元素都是选择器,适用于:无法更改 HTML要选择的元素不固定要选择的元素状态或位置关系固定示例:::first-line可以选中块级元素的第一行。由于内容、设备宽度变化,第一行的内容也在变化。比起更改 HTML,直接定位某个元素的第一行在实际应用中更加方便。两者区别如下:伪类用于选择元素的特定状态。单冒号开头。分为:位置关系伪类举例:用户行为伪类举例:其它状态伪类举例:伪元素用于选择元素的 特定部分。双冒号开头。举例:为兼容早期浏览器,实际使用时,多将双冒号写作单冒号原因:早期规范没有明确伪元素必须双冒号,当时浏览器多用单冒号,现代浏览器同时兼容单和双冒号如果不考虑兼容,规范应使用双冒号四、CSS 选择器命名规则大小写字母,数字,连字符(-),下划线(_)以及 ISO 10646 字符编码 U+00A1 及以上不能以数字、连字符(-)开头类名或 ID 以数字、连字符(-)开头.或#后加\3ID 选择器以一个(#)开头类选择器以一个(.)开头五、什么是 CSS 无效选择器?无效选择器,即运行环境无法解析的 CSS 选择器运行环境会跳过无效选择器群组选择器包含无效选择器,该群组选择器都被跳过开发者工具中,调试 CSS 时,现代浏览器会自动更正错误命名六、无效选择器有什么用途?利用浏览器跳过无效选择器和无效属性的特性无效选择器,无效属性常用于区分运行环境(多为浏览器),进而:改善 CSS 兼容性。常用的 CSS Hacks:① 选择器② 属性使用运行环境的 私有属性,渐进性增强样式:七、什么是渐进性增强样式?通过组合 CSS hacks 和私有属性,在较低版本的浏览器中,保持页面布局和基本样式,在高版本浏览器,使用更高级属性或运行环境的私有属性,增强页面样式,提升用户体验。渐进性增强,是改善 CSS 兼容性,开发框架的设计思想之一,核心是:低版本的用户少的浏览器,保障基本功能,舍弃不必要的 CSS hacks 和 Pollyfills高版本,利用新属性,新接口,提高性能和体验示例:<style> div { /* All */ -width: expression(this.width > 500 ? '500px' : 'auto'); /* IE6 */ max-width: 500px; /* IE7+ */ color: black; /* IE6 黑 */ } p + div { /* IE7+ */ color: blue; /* IE7 - IE8 蓝 */ } :not(p) { /* IE9+ */ color: red; /* IE9+ 红 */ -webkit-font-smoothing: antialiased; /* Webkit浏览器 MacOS 平滑文字 */ -moz-osx-font-smoothing: grayscale; /* 火狐浏览器 MacOS 平滑文字 */ } </style> <p>p</p> <div>div</div>八、如何优化选择器,提高性能?CSS 选择器效率从高到底:ID > 类 > 类型(标签) > 相邻 > 子代 > 后代 > 通配符 > 属性 > 伪类CSS 选择器的读取顺序:从右到左,逐级匹配示例:.content * {},会先匹配到所有元素,再向上筛选出父元素 className 为 content 的子元素根据效率和读取顺序,提高性能的方法如下:多使用高效率选择器工作中,多使用类名,少使用关系、伪类选择器,可读和可维护性也会提高2.  减少选择器层级工作中,选择器层次不建议超过 4 级。用 SASS 或 LESS 时,应避免不必要的嵌套3.  高效率选择器在前工作中,避免使用类、标签选择器限制 ID 选择器,避免使用标签选择器限制类选择器。低效率选择器在前,通常可以优化。示例 .content #id {} 可直接写成 #id {},div .content 可以给 div 起类名4.  避免使用通配符选择器通配符选择器效率低,且使用 * 的场景,通常有其他解决方案5.  多用 继承示例:<style> div * { /* bad case */ font-size: 12px; } div { /* good case */ font-size: 12px; } </style>   <div> <p></p> <p></p> <p></p> </div>
CSS
0
0
0
浏览量2010
懒人学前端

4.核心概念-Loader—下

九、Svg-URL-Loadersvg-url-loadersvg-url-loader 可以将 svg 文件加载为 utf-8 编码的 data-uri 字符串。url-loader 也可以加载 svg 文件,和 svg-url-loader 的区别是 url-loader 将 svg 文件加载为 base64 编码的字符串。utf-8 编码相对于 base64 编码有一些优势。编译结果字符串更短(对于 2K 大小的图标,可以缩短约 2 倍);使用 gzip 压缩时,生成的字符串将被更好地压缩;浏览器解析 utf-8 编码的字符串比 base64 编码的字符串更快;使用index.html<!DOCTYPE html> <html lang="en"> <header> <title>svg-url-loader</title> </header> <body> <img id="svg-loader" /> </body> </html>index.jsimport svgContent from './img/headIcon.svg'; window.document.getElementById('svg-loader').src = svgContent;安装 svg-url-loadernpm install svg-url-loader --save-dev安装成功后,将 svg-url-loader 配置到 webpack.config.js 中。module.exports = { ... module: { rules: [ { test: /\.svg/, use: { loader: "svg-url-loader", }, } ] } };执行打包命令后,在浏览器打开 dist/index.html 文件,我们可以看到 svg 图片被展示在页面是上,打开 dist/main-[hash].js 文件可以看到 svg 图片被编译成了 utf-8 编码的字符串。配置项svg-url-loader 包含下面参数。limitstripdeclarationsiesafeencodinglimit默认值:无值类型:Number作用:当设置 limit,如果源文件的内容大于这个限制,svg-url-loader 将不编码源文件。如果文件大于 limit 设置的限制,将使用 file-loader 加载文件,svg-url-loader 中设置的参数会传递给 file-loader。webpack.config.jsmodule.exports = { ... { test: /\.svg/, use: { loader: "svg-url-loader", options: { limit: 1024 // 文件大小 1M } } } };stripdeclarations默认值:true值类型:Boolean作用:它将在下一个主要版本中被删除,删除所有 XML 声明。例如:svg 图片开头的 <?xml version="1.0" encoding="UTF-8"?>。webpack.config.jsmodule.exports = { ... { test: /\.svg/, use: { loader: "svg-url-loader", options: { stripdeclarations: false } } } }; iesafe默认值:无值类型:Boolean作用:当 iesafe 选项设置为 true,svg-url-loader 在编译文件时,如果文件包含一个样式元素并且编码大小超过 4kB,则无论指定的限制如何,都使用 file-loader 编译。因为 ie 浏览器包括 ie11 已经停止解析 svg 数据中的样式元素和大小超过 4kb 的文件,会导致所有样式的形状都是黑色填充。webpack.config.jsmodule.exports = { ... { loader: "svg-url-loader", options: { iesafe: true }, } };encoding默认值:"none"值类型:"none" | "base64"作用:设置 svg-url-loader 构造数据 URI 时要使用的编码。webpack.config.jsmodule.exports = { ... { loader: "svg-url-loader", options: { encoding: "base64" }, } };总结本节我们介绍了 svg-url-loader 的使用方法和一些配置参数。svg-url-loader 主要用来编译 svg 格式文件,默认采用 utf-8 编码。十、Svg-Sprite-Loadersvg-sprite-loadersvg-sprite-loader 作用是合并多个单个的 svg 图片为一个 sprite 雪碧图,并把合成好的内容,插入到 html 内,其原理是利用 svg 的 symbol 元素,将每个 svg 图片 包括在 symbol 中,通过 use 元素使用该 symbol。svg 元素参考。使用index.html在 index.html 中,通过使用 svg 的 use 元素渲染两张 svg 图片,分别对应 img 文件夹下的 headIcon.svg 和 home.svg。<!DOCTYPE html> <html> <header> <title>svg-sprite-loader</title> </header> <body> <svg> <use xlink:href="#headIcon"></use> </svg> <svg> <use xlink:href="#home"></use> </svg> </body> </html>index.js导入 index.html 中 use 元素加载的两张 svg 图片。import svgContent from './img/headIcon.svg'; import svgHome from './img/home.svg';安装 svg-sprite-loadernpm install svg-sprite-loader --save-dev安装成功后,将 svg-sprite-loader 配置到 webpack.config.js 中。module.exports = { ... module: { rules: [ { test: /\.svg/, use: { loader: "svg-sprite-loader" }, } ] } };执行打包命令后,在浏览器打开 dist/index.html 文件,我们可以看到 svg 图片被展示在页面上,在控制台查看 elements 选项,可以看到两个 svg 图片被包裹在 两个 symbol 标签中,使用时通过 use 标签传入 symbol 元素 id 来显示不同的 svg 图片。配置项svg-sprite-loader 包含下面基础参数配置。symbolIdsymbolRegExpesModulesymbolId默认值:[name]值类型:String | Function作用:设置 svg 标签中 symbol 元素的 id 值。html<svg> <use xlink:href="#icon-headIcon"></use> </svg> webpack.config.jsmodule.exports = { ... { test: /\.svg/, use: { loader: "svg-sprite-loader", options: { // string symbolId: 'icon-[name]' // function symbolId: filePath => path.basename(filePath) } } } }; symbolRegExp默认值:''值类型:String作用:传递给 symbolId 插值器以支持 loader-utils 名称插值器中的 [N] 模式。esModule默认值:true值类型:Boolean作用:是否使用 esModule 语法。如果使用 CommonJS 语法则参数设置为 false。webpack.config.jsmodule.exports = { ... { loader: "svg-sprite-loader", options: { esModule: true }, } };svg-sprite-loader 还支持运行时配置和提取配置,想了解的小伙伴儿可自行查阅文档。总结本节我们介绍了 svg-sprite-loader 的使用方法和一些配置参数。svg-sprite-loader 主要用来将从 css/scss/sass/less/styl/html 导入的图像生成外部 sprite 文件,通过使用 svg 的 use 元素展示 图像内容。达到统一管理的目的。十一、VUE-Loadervue-loaderVue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。Vue Loader 还提供了很多酷炫的特性:允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在 <style> 的部分使用 Sass 和在 <template> 的部分使用 Pug;允许在一个 .vue 文件中使用自定义块,并对其运用自定义的 loader 链;使用 webpack loader 将 <style> 和 <template> 中引用的资源当作模块依赖来处理;为每个组件模拟出 scoped CSS;在开发过程中使用热重载来保持状态;使用在下面的例子中,正常要使用 Vue.js 将组件渲染到页面上。但是这样会增加一些额外的代码,容易混淆,所以下面的例子只是完成打包不报错即认为 vue-loader 起到了作用。index.html<!DOCTYPE html> <html> <head> <title>vue-loader</title> </head> <body> </body> </html>index.vue<template> <div class="demo">{{ msg }}</div> </template> <script> export default { data () { return { msg: 'Hello world!' } } } </script> <style> .demo { color: blue; } </style>安装 vue-loadervue-loader 需要配合 vue-template-compiler 一起使用。npm install vue-loader vue-template-compiler --save-dev安装成功后,将 vue-loader 配置到 webpack.config.js 中。module.exports = { ... // webpack.config.js const { VueLoaderPlugin } = require('vue-loader'); module.exports = { ... module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', }, // 它会应用到普通的 `.css` 文件 // 以及 `.vue` 文件中的 `<style>` 块 { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] }, plugins: [ // 请确保引入这个插件! // 这个插件是必须的!它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块。 new VueLoaderPlugin() ] };执行打包命令后,控制台显示编译成功。配置项vue-loader 参数配置。transformAssetUrlscompilercompilerOptionstranspileOptionsoptimizeSSRhotReloadproductionModeshadowModecacheDirectory / cacheIdentifierprettifyexposeFilenamevue-loader 的配置项官网显示很清楚,大家可以去官网查看,vue-loader配置。总结vue-loader 的总结大部分源自官网介绍,通过上面的使用我们可以完成一个简单的 vue-loader 配置及使用。十二、VUE-Style-Loadervue-style-loadervue-style-loader 是基于 style-loader 的分支,功能与 style-loader 类似,都可以与 css-loader 链接将 style 标签注入到文档中,vue-style-loader 一般不需要自己配置加载,因为他已经作为依赖项包含在 vue-loader 中。vue-style-loader 除了将 style 注入到文档中,还做了一些服务端渲染的支持,所以如果我们 vue 项目中需要做服务端渲染,可能就要使用 vue-style-loader 来插入样式了。使用vue-style-loader 的使用方法与 style-loader 类似。都是与 css-loader 链接起来使用。index.html<!DOCTYPE html> <html> <head> <title>vue-style-loader</title> </head> <body> </body> </html>index.jsimport indexCss from "./index.css"; const divElement = document.createElement("div"); divElement.className = "demo"; divElement.innerHTML = 'vue-style-loader'; document.body.appendChild(divElement);index.css.demo { color: red; }安装 vue-style-loadernpm install vue-style-loader --save-dev安装成功后,将 vue-style-loader 配置到 webpack.config.js 中。module.exports = { ... rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ] } ] };打包成功后,打开 dist/index.html 可以看到浏览器中文字颜色已经变成红色,在控制台 element 中可以看到 index.css 中的样式属性已经被包裹了 style 标签并插入到 head 中。配置项vue-style-loader 参数配置。manualInjectssrIdmanualInject默认值:无值类型:Boolean作用:当 manualInject 参数值为 true 时,导入的样式对象会提供一个__inject__方法,然后可以在适当的时间手动调用该方法。此方法接收一个对象参数,最后将样式文件内容绑定到传入的对象上。注意:只有运行环境为 Node.js 且 manualInject 为 true 时,样式对象才会提供__inject__方法。webpack.config.jsmodule.exports = { ... { test: /\.svg/, use: [ { loader: 'vue-style-loader', options: { manualInject: true, } }, ] } }; ssrId默认值:无值类型:Boolean作用:向 style 标签添加 data-vue-ssr-id 属性,可以用作预渲染避免样式重复注入。webpack.config.jsmodule.exports = { ... { test: /\.svg/, use: [ { loader: 'vue-style-loader', options: { ssrId: true } }, ] } };区别于 style-loader如果你正在构建一个 Vue SSR 应用程序,你可能也应该使用这个加载器来处理从 JavaScript 文件导入的 CSS不支持 url 模式和引用计数模式。还删除了 singleton 和insertAt 查询选项。不支持样式懒加载。如果您需要这些功能,您可能应该使用原始功能 style-loader。总结本章节我们介绍了 vue-style-loader 的使用、配置和它与 style-loader 的区别,vue-style-loader 支持 vue 中的 ssr(服务端渲染),所以如果需要支持服务端渲染的 vue 项目,就需要用到 vue-style-loader 了。但是如果是一般的项目,style-laoder 的功能会更多些。
0
0
0
浏览量2013
懒人学前端

第四章—中

三、继承有哪几种方式?JavaScript 继承方式有以下五种:原型继承借用构造函数继承组合继承寄生式继承寄生组合式继承一、原型链构造函数、原型和实例的关系是:每个构造函数都有一个原型对象 protptype,原型有一个属性 constructor 指向构造函数,而实例有一个内部指针 __proto__ 指向原型。如果原型是另一个类型的实例呢?原型链就是基于此构想。function Parent () { this.name = "kevin"; } Parent.prototype.getName = function () { console.log(this.name); return this.name } function Child () { } Child.prototype = new Parent(); let child1 = new Child(); console.log(child1.getName()) // kevin缺点:引用类型的属性被所有实例共享不能向 Parent 传参二、借用构造函数继承function Parent () { this.names = ["kevin", "daisy"]; } function Child () { Parent.call(this); } let child1 = new Child(); child1.names.push("Lucy"); console.log(child1.names); // ["kevin", "daisy", "Lucy"] let child2 = new Child(); console.log(child2.names); // ["kevin", "daisy"]优点:解决了原型链继承存在的两个缺点。避免了引用类型的属性被所有实例共享能向 Parent 传参缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。三、组合继承function Parent (name) { this.name = name; this.colors = ["red", "blue", "green"]; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); Child.prototype.constructor = Child; let child1 = new Child("kevin", "18"); child1.colors.push("black"); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // ["red", "blue", "green", "black"] let child2 = new Child("daisy", "20"); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // ["red", "blue", "green"]优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。四、寄生式继承创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。function createObj (o) { let clone = Object.create(o); clone.sayName = function () { console.log("hi"); } return clone; }缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。五、寄生组合式继承function Parent (name) { this.name = name; this.colors = ["red", "blue", "green"]; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } // 封装创建新的对象的方法,以传入的原型作为新对象的原型 function object(o) { function F() {} F.prototype = o; return new F(); } function prototype(child, parent) { var prototype = object(parent.prototype); prototype.constructor = child; child.prototype = prototype; } prototype(Child, Parent); let child1 = new Child("kevin", "18"); child1.colors.push("black"); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // ["red", "blue", "green", "black"] let child2 = new Child("daisy", "20"); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // ["red", "blue", "green"] 这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。小练习谈谈 JavaScript 的几种继承方式各有什么优缺点。答:见正文。四、如何判断一个对象属于某个类?判断对象属于某个类有四种方式:typeofinstanceofObject.prototype.construcor()Object.prototype.toString()我们具体分析一下这四种方式。一、typeoftypeof 是用于判断数据的基本类型的,判断对象属于某个类不是特别准确。它可以判断的对象只有函数和对象。除此之外,需要注意的 typeof null 的结果是 "object",这是一个历史设计缺陷。typeof "1"; // "string" typeof 1; // "number" typeof true; // "boolean" typeof null; // "object" typeof undefined; // "undefined" typeof 11n; // "bigint" typeof Symbol(); // "symbol" typeof function() {}; // "function" typeof {}; // "object" typeof []; // "object"二、instanceofinstanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } const auto = new Car('Honda', 'Accord', 1998); console.log(auto instanceof Car); // true console.log(auto instanceof Object); // true我们可以利用原型链来模拟实现 instanceof:function instanceOf(objA, objB) { // 获取构造函数的原型对象 objB = objB.prototype; // 获取对象的原型 let proto = Object.getPrototypeOf(objA); // 查找,直到 null 为止 while (proto !== null) { if (proto === objB) { return true; } proto = Object.getPrototypeOf(proto); } return false; }三、Object.prototype.construcor()MDN 上对 Object.prototype.construcor() 定义:constructor 属性返回 Object 的构造函数(用于创建实例对象)。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。const o = {} o.constructor === Object // true const o = new Object o.constructor === Object // true const a = [] a.constructor === Array // true const a = new Array a.constructor === Array // true const n = new Number(3) n.constructor === Number // true四、Object.prototype.toString()toString() 方法返回一个表示该对象的字符串。可以使用它检查对象类。const toString = Object.prototype.toString; toString.call(new Date()); // [object Date] toString.call(new String()); // [object String] // Math has its Symbol.toStringTag toString.call(Math); // [object Math] toString.call(undefined); // [object Undefined] toString.call(null); // [object Null]以这种方式检查类是不可靠的,因为继承自 Object 的对象可能用它们自己的实现重写它。const myDate = new Date(); Object.prototype.toString.call(myDate); // [object Date] myDate[Symbol.toStringTag] = "myDate"; Object.prototype.toString.call(myDate); // [object myDate]小练习请模拟实现 instanceof。答案:见正文。
0
0
0
浏览量916
懒人学前端

第九章:JavaScript运行时

一、谈谈对执行上下文的理解?JavaScript 执行上下文当一段 JavaScript 代码在运行的时候,它实际上是运行在执行上下文中。下面 3 种类型的代码会创建一个新的执行上下文:全局执行上下文:只有一个,为存在于 JavaScript 函数之外的任何代码而创建,浏览器中的全局对象就是 window 对象。函数执行上下文:存在无数个,函数调用时创建。这个上下文就是通常说的“本地上下文”。eval 函数: 指的是运行在 eval 函数中的代码,很少用而且不建议使用。每一个上下文在本质上都是一种作用域层级。每个代码段开始执行的时候都会创建一个新的上下文来运行它,并且在代码退出的时候销毁掉。JavaScript 引擎通过创建执行上下文栈(Execution Context Stack,ECS) 管理执行上下文。创建阶段和执行阶段当我们调用一个函数时,一个新的执行上下文就会被创建。一个执行上下文可分为创建阶段和执行阶段。我们用下面的代码分析一下两个过程:let x = 10; function timesTen(a){ return a * 10; } let y = timesTen(x); console.log(y); // 100 创建阶段当 JavaScript 引擎第一次执行代码时,它会创建全局执行上下文,在创建阶段会进行以下操作:创建全局对象,如浏览器中的 window, Node.js 中的 global(第一次)创建变量和函数,使用 undefined 作为变量的初始值确定 this 对象指向,第一次执行代码 this 绑定在全局对象上。确定作用域链如果 JavaScript 引擎执行上面的代码:首先,在全局执行上下文中存储变量 x 和 y, 以及函数声明 timesTen使用 undefined 作为变量 x 和 y 的初始值执行阶段代码执行阶段进行以下操作:变量赋值函数引用执行其他代码函数经过创建阶段和执行阶段,并在执行后返回结果。如下面图所示:二、单介绍一下垃圾回收机制垃圾回收是删除任何其他对象未使用的对象的过程。垃圾收集通常缩写为 "GC"。它是内存生命周期的一部分:内存生命周期分为:分配需要的内存使用分配到的内存(读、写)不需要时将其释放垃圾回收需要去寻找内存“不需要”的对象,这里涉及到引用概念。我们也可以简单理解为“链接”。一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。引用计数垃圾回收这是最初级的垃圾收集算法,只判断“对象有没有其他对象引用到它”。如果没有,对象就会被垃圾回收机制回收。// 创建两个对象,一个变量 o1 和另一个被引用的属性 a let o1 = { a: { b: 2 } }; let o2 = o1; // o2 是 o1 的引用如下图:我们执行下面代码后:o2 = 1; o1 = null; // 可以被垃圾回收了限制:循环引用引用计数垃圾回收无法处理循环引用的问题。因为对象始终互相应用,形成循环,无法被回收。function f() { var o1 = {}; var o2 = {}; o1.a = o2; // o1 的属性 a 引用 o2 o2.a = o1; // o2 的属性 a 引用 o1 return "azerty"; } f();标记清除算法这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。算法假定设置一个叫做根的对象,从根开始,找对象的引用,以此类推。循环引用的问题也得以解决。整个标记清除算法大致过程就像下面这样:垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为 0从各个根对象开始遍历,把不是垃圾的节点改成 1清理所有标记为 0 的垃圾,销毁并回收它们所占用的内存空间把所有内存中对象标记修改为 0,等待下一轮垃圾回收除此之外,JavaScript 引擎还有一些优化处理:分代收集(Generational collection)—— 对象被分成两组:“新的”和“旧的”。在典型的代码中,许多对象的生命周期都很短:它们出现、完成它们的工作并很快死去,因此在这种情况下跟踪新对象并将其从内存中清除是有意义的。那些长期存活的对象会变得“老旧”,并且被检查的频次也会降低。增量收集(Incremental collection)—— 如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟。因此,引擎将现有的整个对象集拆分为多个部分,然后将这些部分逐一清除。这样就会有很多小型的垃圾收集,而不是一个大型的。这需要它们之间有额外的标记来追踪变化,但是这样会带来许多微小的延迟而不是一个大的延迟。闲时收集(Idle-time collection)—— 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响。三、如何判断当前脚本运行在浏览器还是 Node 环境中?在浏览器中,顶级作用域是全局作用域,即可以使用 var 定义一个全局变量。然而 Node.js 却不一样,它的顶级作用域不是全局作用域,var 声明的变量也是该模块的局部变量。var foo = "foobar"; foo === window.foo; // trueObject.prototype.toString.call() 可以用于判断当前脚本运行的环境:Object.prototype.toString.call(window); // "[object Window]"Object.prototype.toString.call(global); // "[object global]"因此,我们如下判断:let isBrowser = typeof window !== "undefined" && ({}).toString.call(window) === "[object Window]"; let isNode = typeof global !== "undefined" && ({}).toString.call(global) == "[object global]";四、JavaScript 事件循环是什么?JavaScript 是单线程JavaScript 是单线程编程语言,意味着它一个时间点只能做一件事情,即“一心不可二用”。JavaScript 引擎执行代码时,从文件的上面开始,到文件结束为止。它创建执行上下文、解析、在执行阶段将函数压入和弹出调用栈等。如果函数花费太长时间执行,浏览器页面就会“卡死”。这对用户来说体验太糟糕了。我们可以模拟一个执行时间略长的函数function task(message) { // 模拟一段长时间 let n = 10000000000; while (n > 0){ n--; } console.log(message); } console.log("Start script..."); // "Start script.." task("Call an API"); // "Call an API" console.log("Done!"); // "Done"task 函数要遍历结束后,才会继续执行下面的 console.log("Done!")。事件循环我们可以将需要长时间执行的代码放到回调函数稍后执行。console.log("Start script..."); setTimeout(() => { task("Download a file."); }, 1000); console.log("Done!"); // Start script... // Done! // Download a file.之前提到 JavaScript 是单线程的,然而更准确的说,JavaScript 运行时一次只能做一件事情。当我们调用 setTimeout() 时,或者向服务器发送一个请求,或者触发 DOM 事件,都是浏览器中 Web API 的一部分。在我们上面的例子里,调用 setTimeout() 时, Web API 创建一个 1 秒后过期的定时器。定时器到时间后,JavaScript 引擎将 task 放到的任务队列里面:更详细的事件循环如下:从 宏任务 队列中出队并执行最早的任务,宏任务有:scriptDOM 事件,如 mousemovesetTimeoutsetInterval执行所有 微任务:当微任务队列非空时,出队(dequeue)并执行最早的微任务。微任务有:queueMicrotask(f)promise如果有变更,则将变更渲染(render)出来。如果宏任务队列为空,则休眠直到出现宏任务。转到步骤 1。小练习给出下面代码的输出顺序:console.log(1); setTimeout(() => { console.log(2); }, 0); let promise = new Promise((resolve) => { console.log(3); resolve(); }) .then((res) => { console.log(4); }) .then((res) => { console.log(5); }); console.log(6);答案: 依次输出 1 3 6 4 5 2。按照顺序执行代码:遇到 console.log 打印 1setTimeout 是 Web API,设置定时器,时间到后加入调用队列中new Promise() 中传递的函数立即执行,因此打印 3.then() 调用都加入任务队列遇到 console.log 打印 6查看任务队列,有两个 Promise 创建的微任务,依次打印。setTimeout 的任务加入任务队列。最后执行打印 2。五、JavaScript 中内存泄漏有哪几种情况?内存泄漏指申请的内存执行完后没有及时的清理或者销毁,占用空闲内存,内存泄漏过多的话,就会导致后面的进程申请不到内存。因此内存泄漏会导致内部内存溢出。一般是堆区内存泄漏,栈区不会泄漏。JavaScript 原始数据类型的值保存在栈中,引用数据类型保存在堆中。所以对象、数组等才会发生内存泄漏。常见的内存泄漏的原因:隐式全局变量没有被清除的定时器游离 DOM 的引用闭包我们可以使用下面的方式进行调试:打开 Chrome 浏览器 -> More Tools -> Developer Tools -> Performance/Memory,一般先在 Performance 面板录制页面内存占用情况随时间变化的图像,对内存泄漏有个直观的判断,然后在 Memory 面板定位问题发生的位置。1.隐式全局变量这些场景可能存在内存泄漏隐患:function foo(arg) { bar = "this is a hidden global variable"; }bar 被添加到 window 对象上了,如果 bar 指向一个巨大的对象或 DOM 节点,那就是安全隐患。2.没有被清除的定时器function getData() { return "Hello World!"; } var someResource = getData(); setInterval(function () { var node = document.getElementById("Node"); if (node) { node.innerHTML = JSON.stringify(someResource); } }, 1000);如果后续 id 为 Node 的节点被移除了,定时器里的 Node 变量仍然持有其引用,导致游离的 DOM 子树无法释放。3.游离DOM的引用var elements = { button: document.getElementById("button") }; function doStuff() { button.click(); } function removeButton() { // button 是 body 的子节点. document.body.removeChild(document.getElementById("button")); // 因为 elements 对象中缓存了 DOM 节点引用,这里我们始终有对 id 是 button 的引用 } 4.闭包var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) console.log("hi"); }; theThing = { longStr: new Array(1000000).join("*"), someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000);定义在 replaceThing 里的函数都实际使用了 originalThing,那就有必要保证让它们都取到同样的对象,即使 originalThing 被一遍遍地重新赋值,所以这些(定义在 replaceThing 里的)函数都共享相同的词法环境。只要变量被任何一个闭包使用了,就会被添到词法环境中,被该作用域下所有闭包共享。这是闭包引发内存泄漏的关键。我们可以打开浏览器的开发者工具查看内存数据,选择如下图所示:点击 Start 按钮后:图中有两列:Shallow Size:指的是对象本身的内存大小。Retained Size:指的是在删除对象本身及其从 GC 根无法访问的依赖对象后释放的内存大小。可以看到图中蓝色的条均匀分布,这些蓝色条代表新的内存分配。这些新的内存分配是内存泄漏的候选对象,需要重点关注。除此之外,还可以通过对比内存变化等手段查出具体内存泄漏的原因。七、JavaScript 的本地存储有哪些方式?javaScript 本地存储的方法我们主要讲述以下四种:cookielocalStoragesessionStorageindexedDBcookiecookie 指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 HTTP 无状态导致的问题。作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 cookie 有效期、安全性、使用范围的可选属性组成。localStorage一个可被用于访问当前源的本地存储空间的 Storage 对象可以进行以下操作:// 增加数据 localStorage.setItem('myCat', 'Tom'); // 读取数据 let cat = localStorage.getItem('myCat'); // 移除单个数据 localStorage.removeItem('myCat'); // 移除所有数据 localStorage.clear();localStorage 有两个缺点:无法像 Cookie 一样设置过期时间只能存入字符串,无法直接存对象sessionStoragesessionStorage 和 localStorage 使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage 将会删除数据。indexedDBMDN 对其的定义如下:indexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。优点:储存量理论上没有上限所有操作都是异步的,相比 localStorage 同步操作性能更高,尤其是数据量较大时原生支持储存 JavaScript 的对象是个正经的数据库,意味着数据库能干的事它都能干缺点:操作非常繁琐本身有一定门槛应用在了解了上述的前端的缓存方式后,我们可以看看针对不对场景的使用选择:标记用户与跟踪用户行为的情况,推荐使用 cookie适合长期保存在本地的数据(令牌),推荐使用 localStorage敏感账号一次性登录,推荐使用 sessionStorage存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用 indexedDB
0
0
0
浏览量887
懒人学前端

第四章:for..in和for..of用法

一、for..infor...in 循环只遍历可枚举属性(包括它的原型链上的可枚举属性)。像 Array 和 Object 使用内置构造函数所创建的对象都会继承自 Object.prototype 和 String.prototype 的不可枚举属性,例如 String 的 indexOf() 方法或 Object 的 toString() 方法。循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)。二、for..ofQ: ES6 的 for of 可以遍历对象吗?A: ES6 的 “for of” 不能遍历对象。原因:ES6 中引入了 Iterator 接口,只有提供了 Iterator 接口的数据类型才可以使用 “for-of” 来循环遍历;而普通对象默认没有提供 Iterator 接口,因此无法用 “for-of” 来进行遍历。
0
0
0
浏览量2014
懒人学前端

第十章:应用

一、如何实现大文件上传?如果我们想要实现大文件上传,最简单的思路就是把大文件切成小文件,然后确保小文件都上传给后端,后端再把小文件拼装起来。具体到每个步骤,我们都需要保证结果的正确性并考虑性能。大文件上传步骤如下:读取文件文件切片上传文件我们分别讲解各个实现步骤:1. 读取文件我们先用 HTML 实现一个简单的文件上传界面:HTML 代码如下:<input type="file" id="input" /><button id="upload">上传</button>我们接下来需要实现读取文件:let input = document.getElementById("input"); let upload = document.getElementById("upload"); // 创建一个文件对象 let files = {}; // 读取文件 input.addEventListener("change", (e) => { files = e.target.files[0]; });2. 文件切片文件信息包含文件的名字、大小、文件类型等信息。File 对象部分属性如下图:接下来创建切片:function createChunk(file, size = 1 * 1024 * 1024) { // A const chunkList = []; let cur = 0; while (cur < file.size) { chunkList.push({ file: file.slice(cur, cur + size) // B }); cur += size; } return chunkList; }上面的代码中:A 处:file 是获取到的大文件size 这里我们设置每个小切片的大小为 1 * 1024 * 1024B 处:slice() 方法帮助我们将大文件切成多个小文件我们把大文件按照每个 1 * 1024 * 1024 大小进行切割,调用 createChunk(files) 后, 切割好的小文件列表就可以上传到服务器上了。如果需要优化,可以将计算部分使用 Web Worker 计算。3. 上传文件我们点击“上传”按钮之后,将文件上传到服务器。我们先给按钮添加事件:upload.addEventListener("click", () => { const uploadList = chunkList.map(({ file }, index) => ({ file, size: file.size, chunkName: `${files.name}-${index}`, fileName: files.name, index })); uploadFile(uploadList); });接下来,我们就需要实现 uploadFile() 函数。我们需要把文件切片列表变成 FormData。FormData 表示表单数据的键值对,我们将切片使用这种格式上传到服务器。创建表单类型数据:list.map(({ file, fileName, index, chunkName }) => { const formData = new FormData(); formData.append("file", file); formData.append("fileName", fileName); formData.append("chunkName", chunkName); return { formData, index }; })发送上传文件请求,此处我们需要使用 Promise.all 来获取发送结果状态,如果每个请求都成功了将返回一个数组,如果其中一个抛出错误,将立即抛出错误。我们封装一个 uploadFile 函数:async function uploadFile(list) { const requestList = list .map(({ file, fileName, index, chunkName }) => { const formData = new FormData(); formData.append("file", file); formData.append("fileName", fileName); formData.append("chunkName", chunkName); return { formData, index }; }) .map(({ formData, index }) => fetch(url, { method: "POST", body: formData }).then((res) => { console.log(res) }) ); await Promise.all(requestList); }完整代码HTML 部分:<input type="file" id="input"> <button id="upload">上传</button>JavaScript 部分:let input = document.getElementById("input"); let upload = document.getElementById("upload"); let files = {}; let chunkList = []; // 1. 读取文件 input.addEventListener("change", (e) => { files = e.target.files[0]; chunkList = createChunk(files); }); // 2. 创建切片 function createChunk(file, size = 1 * 1024 * 1024) { const chunkList = []; let cur = 0; while (cur < file.size) { chunkList.push({ file: file.slice(cur, cur + size) }); cur += size; } console.log("chunkList", chunkList); return chunkList; } // 3.文件上传 async function uploadFile(list) { const requestList = list .map(({ file, fileName, index, chunkName }) => { const formData = new FormData(); formData.append("file", file); formData.append("fileName", fileName); formData.append("chunkName", chunkName); return { formData, index }; }) .map(({ formData, index }) => // url 为请求地址 fetch(url, { method: "POST", body: formData }).then((res) => { console.log(res); }) ); await Promise.all(requestList); } upload.addEventListener("click", () => { const uploadList = chunkList.map(({ file }, index) => ({ file, size: file.size, percent: 0, chunkName: `${files.name}-${index}`, fileName: files.name, index })); uploadFile(uploadList); }); 二、如何实现树形结构列表和扁平列表的互相转换?要实现两者的互相转换,我们先看一下两者的结构。扁平的数据内容如下:const flat = [ { id: 1, name: "部门1", pid: 0 }, { id: 2, name: "部门2", pid: 1 }, { id: 3, name: "部门3", pid: 1 }, { id: 4, name: "部门4", pid: 3 }, { id: 5, name: "部门5", pid: 4 } ]数状结构:const tree = [ { id: 1, name: "部门1", pid: 0, children: [ { id: 2, name: "部门2", pid: 1, children: [] }, { id: 3, name: "部门3", pid: 1, children: [ { id: 4, name: "部门4", pid: 3, children: [ { id: 5, name: "部门5", pid: 4, children: [] } ] } ] } ] } ];扁平结构转树状结构观察两者的结构后,从扁平结构转树状结构需要做以下事情:树形结构按照 pid 的大小重新排列, pid 为 0 的是根,包含其他所有项目新增 children 属性同一个 pid 的项目都放在 children 数组中如果 pid 下无项目,则 children 为空接下来,我们就来实现这个数状结构。主要步骤如下:遍历 flatList,把数据转成 Map 结构,方便查找同时,直接从 Map 中查找相同的 pid 项目,放在同一层 children 中处理根节点和非根节点的情况const flatToTree = (list) => { const result = []; const itemMap = {}; // 遍历 list.forEach((item) => { const { id, pid } = item; // children 不存在则添加 children if (!itemMap[id]?.children) { itemMap[id] = { children: [] }; } // 从 `Map` 中查找相同的 `pid` 项目,放在同一层 `children` 中 itemMap[id] = { ...item, children: itemMap[id]["children"] }; const treeItem = itemMap[id]; // 判断是不是根 if (pid === 0) { result.push(treeItem); } else { if (!itemMap[pid]?.children) { itemMap[pid] = { children: [] }; } itemMap[pid]["children"].push(treeItem); } }); return result; }; flatToTree(flat);树状结构转扁平结构树状结构转扁平结构只需要按照 id 排列即可,同时删除 children 属性。步骤如下:创建一个数组 queue,将 data 存入,首次遍历的时候只有一个元素,即根节点(pid 为 0)从数组 queue 头部取出元素,判断有无 children 属性有 children,再次添加到 queue 的尾部,并删除 childrenconst treeToFlat = (data) => { const result = []; const queue = [...data]; // 遍历 while (queue.length) { // 从数组头部取出数据 const node = queue.shift(); const children = node.children; // 如果有 children, 继续添加到 queue 中 if (children) { queue.push(...children); } // 删除 children 属性 delete node.children; // 将处理好的节点添加到 result 中 result.push(node); } return result; }; treeToFlat(tree);三、什么是单点登录?单点登录(Single Sign On,简称 SSO)简单来说就是用户只需在一处登录,不用在其他多系统环境下重复登录。用户的一次登录就能得到其他所有系统的信任。为什么需要单点登录单点登录在大型网站应用频繁,比如阿里旗下有淘宝、天猫等,用户的一次操作或交易就可能涉及到其他众多子系统的协作。使用单点登录就可以让用户登录一次,免去频繁登录授权的苦恼。早期的单系统登录用户在登录界面输入自己的用户名和密码之后,浏览器向服务器发送登录请求,服务器验证通过用户信息后放入 session,并将 sessionId 放入 Cookie,随后返回给浏览器。登录过后的请求都将在 Cookie 中携带 sessionId,服务器通过 sessionId 获取用户信息,最后返回响应信息。单点登录的实现方式如果一处登录的 session 能够共享,那么多个应用系统之间的登录状态也可以共享了。所以单点登录的关键在于,如何让 sessionId (或 Token)在多个域中共享。同域下的单点登录一个企业一般情况下只有一个域名,子系统使用二级域名。比如百度搜索是一级域名:https://www.baidu.com,贴吧使用的是二级域名https://tieba.baidu.com。如果我们要实现单点登录,可以将 Cookie 的域设置为顶域,即 https://www.baidu.com,这样其余的子域系统都可以访问顶域的 Cookie 了。当然,上面做法缺点也很明显,它不支持跨域。这还不是真正的单点登录。不同域下的单点登录要实现不同域下的单点登录, 我们需要一个 SSO 认证中心来专门处理登录请求。所有的请求(登录、退出、获取用户信息、当前用户状态)都请求 SSO 系统,SSO 系统维护用户信息。流程如下:首次登录时:用户登录某应用网站,浏览器将用户的登录重定向到 SSO 认证中心SSO 进行检查和校验是否有现有的 SSO Cookie由于首次登录,并且用户浏览器不存在 SSO Cookie,因此请求用户使用 SSO 配置的连接登录。用户登录后,将设置 SSO Cookie,用户将被重定向到应用程序,并使用包含用户相关的身份信息的令牌(Token)后续登录中:应用网站将用户重定向到 SSO 认证中心SSO 认证中心 检查是否存在现有的 SSO CookieSSO 认证中心验证 SSO Cookie 是否有效无效,用户被重定向到应用网站的登录页面有效,用户将被重定向到应用程序,并带有包含用户相关身份信息的令牌(Token)。其实,SSO 认证中心相当于一个登录中介,它统一管理用户在多系统下的登录操作。使用 SSO 的好处就是简化登录流程,用户友好且安全。四、Web 常见的攻击方式有哪些?常见的 Web 攻击方式有以下几种:跨站脚本攻击(XSS 攻击)跨站请求伪造(XSRF 攻击)SQL 注入XSS 攻击MDN 定义如下:跨站脚本攻击(Cross-site scripting,XSS)是一种安全漏洞,攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码,攻击者就可以突破网站的访问限制并冒充受害者。简单来说,跨站脚本攻击利用恶意脚本发起攻击,通常这些恶意脚本可以任意读取 cookie、session tokens,或其他敏感网站信息。以下两种情况容易发生 XSS 攻击:从一个不可靠的链接进入到一个 Web 应用程序。没有过滤掉恶意代码的动态内容被发送给 Web 用户。如果要过滤恶意代码,提交给后端,就能尽可能避免此类攻击。DOM 中的内联事件监听器,如 location、onclick 等,<a> 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就能在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。XSRF 攻击CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。如果你登录了 bank.com 网站,此时,你有了来自该网站的身份验证 cookie。浏览器将会在你的每次请求上带上 cookie,以便识别你,执行所有的敏感财务操作。当你在另一个窗口中浏览网页时,不小心访问了一个 eval.com 的网站,该网站有一段提交表单的 JavaScript 代码: <form action="https://bank.com/pay">,它含有交易字段。这时候,提交表单请求虽然由 eval.com 发起,当它携带了 bank.com 的 cookie,因此验证通过,攻击就完成了。那么我们怎么预防呢?方案有:阻止不明外域的访问同源检测Samesite cookie提交时要求附加本域才能获取的信息CSRF Token双重 cookie 验证SQL 注入SQL 注入攻击,是通过将恶意的 SQL 查询或添加语句插入到应用的输入参数中,再在后台 SQL 服务器上解析执行进行的攻击。比如,后端使用 SQL 查询时,篡改的查询参数或其他 SQL 代码,就会造成 SQL 注入攻击。可以使用预编译方式或者 ORM 框架避免此类攻击发生。
0
0
0
浏览量659
懒人学前端

21.阶段测 Ⅴ

1. Webpack 处理 SASS 文件时,sass-loader的作用是?• 将 SASS / SCSS 文件编译成 CSS• 调用node-sass,支持options选项向node-sass传参2. 压缩 CSS ,简单可分为哪3个步骤?① 去除 CSS 中的换行和空格② 去除 每个选择器,最后一个属性值后的分号;③ 去除 注释,正则/*[^*] [^/][^*]**+)*/3. 去除无用的 CSS 有哪4种方法?Chrome 开发者工具 Lighthouse;UnCSS;PurgeCSS;cssnano。4. CSS 模块化的方式有哪几种?基于文件拆分不拆分但设置作用域CSS in JS内联样式、Shadow DOM 等5. 自动压缩图片有哪几种方法?• 使用 PhotoShop 自动批处理功能• 使用 开源图片处理软件 XnViewr,工具,批量转换功能• 使用 命令行工具,包括 NConvert ImageMagic 等以及类似 img2webp 的专门格式的转换工具• 工程化配置图片压缩• 图片模块• 图片目录• 使用反向代理6. 工程化及ES6等模块化环境中,我们可以通过什么只引入需要的css,通过什么去除未被使用的CSS?import或require;PurgeCSS和cssnano。7. 什么是稳定模块?已经进入到候选推荐、建议推荐和推荐 的模块被称为稳定模块。8. 什么是 CSS3 新特性?新特性通常指稳定模块中新增的标准。9. CSS3 命名空间模块中增加了哪个新特性?新增 @规则:@namespace。10. cursor: hand在标准浏览器无效时怎么办?IE 也支持的标准属性值cursor:pointer替代。11. CSS3 新特性兼容性问题如何解决?• 人工或使用混入器添加私有前缀○ 工作量大,开发阶段冗余代码多○ 同一属性,有无私有前缀,后面接收的属性值类型、顺序等可能不同○ 部分私有前缀,需要加到属性值,而不是属性名上• PostCSS 的 autoprefixer 可以配置 Browserslist,按需对浏览器及其不同版本添加私有前缀12. 想要让 APP 用户获得体验更一致,减少兼容性问题应该怎么做?• 将渲染引擎与源码一起打包,比如使用腾讯基于 X5 内核的浏览服务,安装包会变大• 采用 React Native Flutter UniApp + Weex 等使用 HTML + CSS + JS 开发,原生渲染的方案13. 当你忘记一个 CSS 属性时,一般有哪四种方法?Emmet;查看说明;搜索;记忆。14. 为什么要规定 CSS 属性的书写顺序?① 有助于提升代码的可读性,可维护性,利于团队协作② 有效避免重复或遗漏声明属性③ 避免实验中的私有属性与已加入规范的属性在具体实现的冲突
CSS
0
0
0
浏览量279
懒人学前端

第六章 :Promise & Async/await & Generators—上

一、如何模拟实现 Promise?为什么需要 Promise?为什么我们需要 Promise, 最直接的答案就是它更直观、好用。Promise 出现之前,我们使用事件和回调。我们创建并加载一个脚本文件, 监听 onload 事件, 执行对应的回调函数。如下所示:const loadScript = (src, callback) => { // 创建一个 <script> 标签, 并将其添加到页面 let script = document.createElement("script"); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); }; loadScript( "https://unpkg.com/vue@3/dist/vue.global.js", function (error, script) { if (error) { console.log(error); } else { console.log("loading Vue"); // "loading Vue" } } );此处只加载一个脚本文件,回调看起来还比较清晰,但是如果我们想加载多个脚本文件呢?如下所示:loadScript( "https://unpkg.com/vue@3/dist/vue.global.js", function (error, script) { console.log("loading Vue"); // "loading Vue" loadScript( "https://unpkg.com/react@18/umd/react.development.js", function (error, script) { console.log("loading React"); // "loading React" } ); } );在上面的代码中:我们加载 Vue, 如果没有发生错误,我们加载 React......随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,这就被称为“回调地狱”。此时 Promise 闪亮登场了,它可以很好地解决回调地狱问题,且代码简单易读。如果改成 Promise:const loadScript = (src) => { return new Promise(function (resolve, reject) { let script = document.createElement("script"); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error(`Script load error for ${src}`)); document.head.append(script); }); }; loadScript("https://unpkg.com/vue@3/dist/vue.global.js") .then((script) => { console.log("loading Vue"); // "loading Vue" return loadScript("https://unpkg.com/react@18/umd/react.development.js"); }) .then((script) => console.log("loading React")) // "loading React" .catch((error) => console.log(error));Promise 介绍Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。— MDN它的构造器语法如下:let promise = new Promise(function(resolve, reject) { // executor })executor 会自动运行并尝试执行。尝试结束后,如果成功则调用 resolve,如果出现 error 则调用 reject。所以,executor 最终将 Promise 移至以下状态之一:由图可知,new Promise 构造器返回的 pending 对象具有以下内部属性:state:初始值是 pending,resolve 调用后变为 fulfilled, reject 调用之后变为 rejectedresult:初始值是 undefined,resolve(value) 调用后变为 value, reject(error) 调用时变为 errorPromise 支持链式调用,.then、.catch 和 .finally 方法均支持链式调用。其中,.then 方法接收两个参数,第一个参数处理已决议状态(fulfilled)的回调函数,第二个参数则处理已经拒绝(rejected)的回调函数。每一个 .then() 方法返回的是一个新生成的 Promise 对象,这个对象可被用作链式调用。如:const promise = doSomething(); const promise2 = promise.then(successCallback, failureCallback);模拟实现 Promise第一版:简易版 Promise我们的第一版 Promise 具有以下功能:创建一个 Promiseresolve 或 reject 一个 Promise,仅可敲定一次.then() 方法接收回调函数,满足无论 Promise 的状态是否已经敲定,都需要单独处理回调函数.then() 方法暂不支持链式调用,我们将在第二版实现这个功能MyPromise 是一个类,它有下面三个原型方法:MyPromise.prototype.resolve(value)MyPromise.prototype.reject(reason)MyPromise.prototype.then(onFulfilled, onRejected)希望可以如下使用我们实现的 Promise,也可作为我们稍后的测试代码:// .resolve() 先于 .then() 执行, 无论它们代码位置的先后 // .resolve() 在 .then() 前 const tp1 = new MyPromise(); tp1.resolve("abc"); tp1.then((value) => { console.log(value === "abc"); // true }); // .then() 在 .resolve() 前 const tp2 = new MyPromise(); tp2.then((value) => { console.log(value === "def"); // true }); tp2.resolve("def");class MyPromise { // 处理 .then 中状态是 pending 的所有任务,加入任务数组中 _fulfillmentTasks = []; _rejectionTasks = []; _preomiseResult = undefined; _promiseState = "pending"; then(onFulfilled, onRejected) { const fulfillmentTask = () => { if (typeof onFulfilled === "function") { onFulfilled(this._preomiseResult); } }; const rejectionTask = () => { if (typeof onRejected === "function") { onRejected(this._preomiseResult); } }; switch (this._promiseState) { case "pending": this._fulfillmentTasks.push(fulfillmentTask); this._rejectionTasks.push(rejectionTask); case "fulfilled": addToTaskQueue(fulfillmentTask); break; case "rejected": addToTaskQueue(rejectionTask); default: throw new Error(); } } resolve(value) { if (this._promiseState !== "pending") return this; this._promiseState = "fulfilled"; this._preomiseResult = value; this._clearAndEnqueueTask(this._fulfillmentTasks); return this; // 可以链式调用 } reject(error) { if (this._promiseState !== "pending") return this; this._promiseState = "rejected"; thsi._promiseResult = error; this._clearAndEnqueueTask(this._rejectionTasks); return this; // 可以链式调用 } _clearAndEnqueueTask(tasks) { this._fulfillmentTasks = undefined; this._rejectionTasks = undefined; tasks.map(addToTaskQueue); } } function addToTaskQueue(task) { setTimeout(task, 0); }resolve()resolve() 方法的工作原理如下:如果 Promise 已经敲定,即 Promise 已经兑现(fulfilled)或已拒绝(rejected),什么都不做。因为一个 Promise 仅可以被敲定一次。改变 Promise 的状态: 由 pending 变为 fulfilled;并将结果缓存到 this._promiseResult 中。所有的回调函数都加入队列中,用 setTimeout() 模拟异步执行。reject()原理同 resolve(),只不过它处理的是 Promise 的 rejected 情况。then()then() 方法需要处理下面两种情况:如果 Promise 是 pending 状态,需要把它封装成待处理的函数: onFulfilled 和 onRejected,添加到对应的任务数组中(_fulfillmentTasks 和 _rejectionTasks),它们将在 Promise 已经敲定后再执行。如果 Promise 已经敲定,onFulfilled、onRejcted 可以直接触发。Promise 必须始终是异步敲定,这就是我们为什么不直接执行 task,而是添加到事件循环(event loop)的任务队列中。真正的 Promise 不是使用 setTimeout 模拟的,它使用的是微任务(microtasks)。第二版:实现链式调用我们将实现链式调用,先看一段代码:new MyPromise() .resolve("result1") .then((x) => { console.log(x === "result1"); // true return "result2"; }) .then((x) => { console.log(x === "result2"); // true return new Error("error1"); }) .then((x) => { console.log(x.message === "error1"); // true });在上面的例子中:第一个 .then() 方法在传递了一个箭头函数,返回一个值 result2第二个 .then() 方法在中接收了 result2.then() 方法如何实现链式调用呢?.then() 返回一个 Promise,无论前一个 Promise 返回的是 onFullfilled 还是 onRejected,它都接收其状态。如果 onFulfilled 或 onRejected 都缺失,都通过 .then() 方法返回给该 Promise。下面的图更为直观:因此,我们将 .then() 方法改进如下:then(onFulfilled, onRejcted) { const resultPromise = new MyPromise(); // new const fulfillmentTask = () => { if (typeof onFulfilled === "function") { const returned = onFulfilled(this._preomiseResult); resultPromise.resolve(returned); } else { resultPromise.resolve(this._promiseResult); // new } }; const rejectionTask = () => { if (typeof onRejected === "function") { const returned = onRejected(this._promiseResult); resultPromise.resolve(returned); // new } else { resultPromise.reject(this._promiseResult); } }; // ... return resultPromise; // new }.then() 返回一个新的 Promise,此外,我们需要关注:fulfillmentTask 会有两种不同表现:如果提供了 onFullfilled 函数,调用它并将其结果传递给 resultPromise 的 resolve 方法。如果没提供,使用当前 Promise 的敲定结果 this._promiseResult 传递给 resultPromise 的 resolve 方法。rejectionTask 也有两种表现:如果提供了 onRejected 函数,调用它并将其结果传递给 resultPromise 的 resolve 方法。注意:resultPromise 并没有被拒绝,我们将在后文详细解释。如果没提供,使用当前 Promise 的敲定结果 this._promiseResult 传递给 resultPromise 的 resolve 方法。第三版:处理 thenablethenable 是一个对象,它具有方法 .then,被当作一个 Promise 对待。.then() 方法中还可以创建并返回一个 Promise,在这种情况下,其他的处理程序将等待它被敲定后再获得其结果。asyncFunc1() .then((result) => { console.log(result1 === "Result of asyncFun1()"); // true return asyncFunc2(); // A }) .then((result2Promise) => { result2Promise .then((result2) => { // B console.log(result2 === "Result of asyncFun2"); // true }) })在 A 处,如果返回了一个 Promise,然后在第二个 .then() 中作为参数传入,并敲定了这个 Promise。官方的 Promise 可以直接敲定嵌套的 Promise,如下面的代码:asyncFunc1() .then((result) => { console.log(result1 === "Result of asyncFun1()"); // true return asyncFunc2(); // A }) .then((result) => { // result2 是一个已兑现的结果,而非 Promise console.log(result2 === "Result of asyncFun2"); // true })我们将这样处理 then() 方法中返回的 thenable,如下图:如果第一个 Promise 处理的是一个 thenable 对象 x1,它锁定 x1 并且用 x1 敲定的结果作为敲定结果。如果第一个 Promise 处理的不是 thenable 对象,则像之前一样。我们可以这样判断 thenable 对象:function isThenable(value) { return ( typeof value === "object" && value !== null && typeof value.then === "function" ); }我们再来完善 .resolve() 方法:resolve(value) { if (this._alreadyResolved) return this; this._alreadyResolved = true; if (isThenable(value)) { value.then( (result) => this._doFulfill(result), (error) => this._doReject(error); ) } else { this._doFulfill(value); } return this; } _doFulfill(value) { // [new] console.log(!isThenable(value)) // true this._promiseState = "fulfilled"; this._promiseResult = value; this._clearAndEnqueueTasks(this._fulfillmentTasks); }this._alreadyResolved 是为了锁定 Promise 的状态的。返回一个已敲定状态的 Promise,那么 then() 返回的 Promise 的状态也会是已敲定。返回已拒绝状态同理。第四版:处理错误我们希望错误也能够得到正确处理:new MyPromise() .resolve("a") .then((value) => { console.log(value === "a"); // true throw "b"; // 抛出一个错误 }) .catch((error) => { console.log(error === "b"); // true })如果我们要处理错误,只需要在 .then() 方法中添加相关代码即可:const fulfillmentTask = () => { if (typeof onFulfilled === "function") { this._runReactionSafely(resultPromise, onFulfilled); // [new] } else { // `onFulfilled` 没有传 // => 我们需要传已敲定的结果 resultPromise.resolve(this._promiseResult); } }; _runReactionSafely(resultPromise, reaction) { // [new] try { const returned = reaction(this._promiseResult); resultPromise.resolve(returned); } catch (e) { resultPromise.reject(e); } }错误处理过程如图所示:总结至此,我们模拟实现了 Promise,包含 .then()、.resolve()、 .reject() 方法的实现。class MyPromise { _fulfillmentTasks = []; _rejectionTasks = []; _promiseResult = undefined; _promiseState = "pending"; _alreadyResolved = false; then(onFulfilled, onRejected) { const resultPromise = new MyPromise(); const fulfillmentTask = () => { if (typeof onFulfilled === "function") { this._runReactionSafely(resultPromise, onFulfilled); } else { resultPromise.resolve(this._promiseResult); } }; const rejectionTask = () => { if (typeof onRejected === "function") { this._runReactionSafely(resultPromise, onRejected); } else { resultPromise.reject(this._promiseResult); } }; switch (this._promiseState) { case "pending": this._fulfillmentTasks.push(fulfillmentTask); this._rejectionTasks.push(rejectionTask); break; case "fulfilled": addToTaskQueue(fulfillmentTask); break; case "rejected": addToTaskQueue(rejectionTask); break; default: throw new Error(); } return resultPromise; } catch(onRejected) { return this.then(null, onRejected); } _runReactionSafely(resultPromise, reaction) { try { const returned = reaction(this._promiseResult); resultPromise.resolve(returned); } catch (e) { resultPromise.reject(e); } } resolve(value) { if (this._alreadyResolved) return this; this._alreadyResolved = true; if (isThenable(value)) { value.then( (result) => this._doFulfill(result), (error) => this._doReject(error) ); } else { this._doFulfill(value); } return this; } _doFulfill(value) { this._promiseState = "fulfilled"; this._promiseResult = value; this._clearAndEnqueueTasks(this._fulfillmentTasks); } reject(error) { if (this._alreadyResolved) return this; this._alreadyResolved = true; this._doReject(error); return this; } _doReject(error) { this._promiseState = "rejected"; this._promiseResult = error; this._clearAndEnqueueTasks(this._rejectionTasks); } _clearAndEnqueueTasks(tasks) { this._fulfillmentTasks = undefined; this._rejectionTasks = undefined; tasks.map(addToTaskQueue); } } function isThenable(value) { return ( typeof value === "object" && value !== null && typeof value.then === "function" ); } function addToTaskQueue(task) { setTimeout(task, 0); }小练习如何使用 Promise 封装 XMLHttpRequest?答案:参考如下function load(url) { return new Promise(function (resolve, reject) { const request = new XMLHttpRequest(); request.onreadystatechange = function () { if (this.readyState === 4 && this.status == 200) { resolve(this.response); } else { reject(this.status); } }; request.open("GET", url, true); request.send(); }); } // test load("testUrl") .then((response) => { const result = JSON.parse(response); // ... }) .catch((error) => { // ... });
0
0
0
浏览量2015
懒人学前端

项目题全解:组件通信—上

Step 1:父子组件通信Step 1.1:提供装着爸爸和儿子的初始模板基础知识:页面效果:父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信具体代码:爸爸的模板:app.vue<!--父组件--> <template> <div id="App"> <h2>我是爸爸</h2> <ChildFirst></ChildFirst> </div> </template> <script> import ChildFirst from "./components/ChildFirst.vue"; export default { components: { ChildFirst, }, data() { return {}; }, }; </script> <style scoped> </style儿子的模板:ChildFirst.vue<!--子组件--> <template> <div> <h3>我是儿子</h3> </div> </template> <script> export default {}; </script> <style scoped> </style>Step 1.2:爸爸喊儿子吃饭了基础操作:第一步:爸爸需要在template中联系上儿子 <ChildFirst :msg="msgData"></ChildFirst>爸爸需要在data中写出自己要说的话:data() { return { msgData: "儿子快回家吃饭了!" }; },第二步:儿子需要在props里接受爸爸来的信息 props: ["msg"]儿子需要在template显示爸爸说的话 <p>{{msg}}</p>页面效果:具体代码:爸爸的模板:app.vue<!--父组件--> <template> <div id="App"> <h2>我是爸爸</h2> <ChildFirst :msg="msgData"></ChildFirst> </div> </template> <script> import ChildFirst from "./components/ChildFirst.vue"; export default { components: { ChildFirst, }, data() { return { msgData: "儿子快回家吃饭了!" }; }, }; </script> <style scoped> </style>儿子的模板:ChildFirst.vue<!--子组件--> <template> <div> <h3>我是儿子</h3> <p>{{ msg }}</p> </div> </template> <script> export default { props: ["msg"], }; </script> <style scoped> </style>Step 1.3:通过函数给儿子传递一个警报基础操作:第一步:爸爸需要在html中加上函数消息 <son :msg="msgData" :fn="fnData"></son>爸爸需要在methods中写出自己的方法:methods: { fnData() { alert('还不回家吗') } },第二步:儿子需要在props里接受爸爸来的方法 props: ["msg", "fn"]儿子需要在html添加一个点击事件 <p @click="fn">{{msg}}</p>页面效果:具体代码:爸爸的模板:app.vue<!--父组件--> <template> <div id="App"> <h2>我是爸爸</h2> <ChildFirst :msg="msgData" :fn="fnData"></ChildFirst> </div> </template> <script> import ChildFirst from "./components/ChildFirst.vue"; export default { components: { ChildFirst, }, data() { return { msgData: "儿子快回家吃饭了!" }; }, methods: { fnData() { alert("还不回家吗"); }, }, }; </script> <style scoped> </style>儿子的模板:ChildFirst.vue<!--子组件--> <template> <div> <h3>我是儿子</h3> <p @click="fn">{{ msg }}</p> </div> </template> <script> export default { props: ["msg", "fn"], }; </script> <style scoped> </style>Step 1.4:儿子对爸爸的回应基础操作:第一步:儿子需要添加一个点击事件 <p @click="emitIndex">儿子回复:</p>儿子需要添加一个方法methods: { emitIndex() { // 触发父组件的sonAnswer方法,并传递参数 “我知道了” this.$emit('sonAnswer', "我知道了") } },第二步:爸爸需要用@接受儿子传来的信息<son :msg="msgData" :fn="fnData" @sonAnswer="sonAn"></son>爸爸需要用methods新写一个方法,改变自己原来的数据爸爸需要新增一个h4标签用来展示数据<h4>{{ currentIndex }}</h4>data() { return { msgData: "儿子吃饭了!", currentIndex: "", } }, ... ... methods: { fnData() { alert('还不回家吗') }, sonAn(cn) { this.currentIndex = cn } },页面效果:具体代码:爸爸的模板:app.vue<!--父组件--> <template> <div id="App"> <h2>我是爸爸</h2> <ChildFirst :msg="msgData" :fn="fnData" @sonAnswer="sonAn"></ChildFirst> <h4>{{ currentIndex }}</h4> </div> </template> <script> import ChildFirst from "./components/ChildFirst.vue"; export default { components: { ChildFirst, }, data() { return { msgData: "儿子快回家吃饭了!", currentIndex: "" }; }, methods: { fnData() { alert("还不回家吗"); }, sonAn(cn) { this.currentIndex = cn; }, }, }; </script> <style scoped> </style>儿子的模板:ChildFirst.vue<!--子组件--> <template> <div> <h3>我是儿子</h3> <p @click="fn">{{ msg }}</p> <p @click="emitIndex">儿子回复:</p> </div> </template> <script> export default { props: ["msg", "fn"], methods: { emitIndex() { // 触发父组件的sonAnswer方法,并传递参数 “我知道了” this.$emit("sonAnswer", "我知道了"); }, }, }; </script> <style scoped> </style>
0
0
0
浏览量622
懒人学前端

第三章:防抖节流

一、防抖防抖是指短时间内大量触发同一事件,只会在最后一次事件完成后延迟执行一次函数。例如,在输入用户名的过程中,需要反复验证用户名。此时,您应该等待用户停止输入,然后进行验证,否则将影响用户体验。 防抖实现的原理是在触发事件后设置计时器。在计时器延迟过程中,如果事件再次触发,则重置计时器。在没有触发事件之前,计时器将再次触发并执行相应的功能。声明定时器返回函数一定时间间隔,执行回调函数回调函数已执行:清空定时器未执行:重置定时器function debounce(fn, delay) { let timer = null return function (...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { timer = null fn.apply(this, args) }, (delay + '') | 0 || 1000 / 60) } }二、节流节流是指每隔一段时间就执行一次函数。就像未拧紧的水龙头一样,水龙头每隔一段时间就会滴水。即使在这段时间管道里有更多的水,水龙头也不会掉更多的水。节流的原理是在触发事件后设置计时器。在计时器延迟过程中,即使事件再次触发,计时器的延迟时间也不会改变。在计时器执行功能之前,计时器不会复位。声明定时器返回函数一定时间间隔,执行回调函数回调函数已执行:清空定时器未执行:返回function throttle(fn, interval) { let timer = null return function (...args) { if (timer) return timer = setTimeout(() => { timer = null fn.apply(this, args) }, (interval +'')| 0 || 1000 / 60) } }
0
0
0
浏览量2017
懒人学前端

第四章:对象—上

一、JavaScript 创建对象有哪些方式?JavaScript 中有八种数据类型,有七种基本数据类型和对象(Object),对象就是引用类型。这里我们介绍八种创建对象的方式:三种最基本的方式Object 构造函数对象字面量Object.create()类(语法糖)四种运用原型式继承的方式。ES6 开始正式支持类和继承,涵盖了之前规范设计的基于原型的继承模式。工厂模式构造函数模式原型模式组合模式一、Object 构造函数创建自定义对象可以创建 Object 的一个新实例,再添加属性和方法,如下所示:let person = new Object(); // 添加属性 person.name = "Lucy"; // 添加方法 person.sayName = function() { console.log(this.name) }二、对象字面量对象字面量创建新对象更为简单直接。如下所示:let person = { name: "Lucy", sayName() { console.log(this.name) } }这个例子中的 person 对象和上文例子中的 person 对象是等价的,它们的属性和方法都一样。三、Object.create()Object.create()方法创建一个新的对象,使用现有的对象作为新创建对象的原型。let person = { name: "Lucy", sayName() { console.log(this.name) } } let person1 = Object.create(person); console.log(person1.name); // "Lucy"四、类(ES6)类用于创建对象的模版,它建立在原型上。类是“特殊的函数”,类语法有两个组成部分:类表达式和类声明。我们用类声明来创建一个对象:class Person { constructor(name) { this.name = name; } // 定义方法 sayName() { console.log(this.name) } } const person1 = new Person("Lucy"); person1.sayName(); // "Lucy"注意:函数声明和类声明的一个重要区别是:函数声明会提升,类声明不会。我们用类表达式来创建一个对象,类表达式可以是命名或不命名的,如下用匿名类创建对象:let Person = class { constructor(name) { this.name = name; } // 定义方法 sayName() { console.log(this.name) } } const person1 = new Person("Lucy"); person1.sayName(); // "Lucy"类相比原型模式,具备原型模式的优点,代码封装更好。五、工厂模式工厂模式是一种运用广泛的设计模式,用于抽象创建特定对象的过程。如下所示:function createPerson(name) { let o = new Object(); o.name = name; o.sayName = function() { console.log(this.name); }; return o; } let person1 = createPerson("Lucy"); let person2 = createPerson("Joe");函数 createPerson()接收 1 个参数 name, 根据这个参数创建了一个包含 Person 信息的对象,可以用不同的参数多次调用这个函数,每次都会返回包含 1 个属性和 1 个方法的对象。在 createPerson() 中可以根据实际需求给 Person 对象添加更多属性和方法。它的优缺点如下:优点:可以解决创建多个类似对象的问题缺点:没有解决对象标识问题(即新创建的对象是什么类型,类型如 Array)六、构造函数模式构造函数是用于创建特定类型对象的。像 Object 和 Array 这样的原生构造函数,运行时可以直接在执行环境中使用。我们可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。function Person(name) { this.name = name; this.sayName = function() { console.log(this.name) } } let person1 = new Person("Lucy"); let person2 = new Person("Joe"); person1.sayName(); // "Lucy" person2.sayName(); // "Joe"在这个例子中,Person() 构造函数代替了 createPerson() 工厂函数。实际上,Person() 内部的代码和 createPerson() 基本是一样的,只是有如下区别:没有显式地创建对象属性和方法直接赋值给了 this没有 return另外,需要注意函数名 Person 大写了。按照惯例,**构造函数名称的首字母都是要大写的,非构造函数则以小写字母开头。**这是为了区别构造函数和普通函数,毕竟 ECMAScript 的构造函数就是能创建对象的函数。要创建 Person 的实例,应使用 new 操作符,用 new 操作符调用构造函数会执行如下操作:在内存中创建一个新对象这个新对象内部的 [[Prototype]] 特性被赋值为构造函数的 prototype 属性构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)执行构造函数内部的代码如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象。那么 new 是如何具体实现的呢?我们可以模拟一个 new 的实现,由于 new 是个关键字,我们把构造函数当作函数的参数传入:function newOperator(Constructor, ...args) { let thisValue = Object.create(Constructor.prototype); // 对应上文操作步骤: 1、2 let result = Constructor.apply(thisValue, args); // 对应上文操作步骤: 3、4 return typeof result === 'object' && result !== null ? restult : thisValue; // 对应上文操作步骤: 5 } // 测试代码 function Person(name) { this.name = name; this.sayName = function() { console.log(this.name) } } let person1 = newOperator(Person, "Lucy"); let person2 = newOperator(Person, "Joe"); person1.sayName(); // "Lucy" person2.sayName(); // "Joe"上文中创建的 person1 和 person2 都分别保存着 Person 的不同实例。这两个对象都有一个 constructor 属性指向 Person:console.log(person1.constructor === Person) // true console.log(person2.constructor === Person) // trueconstructor 是用于标识对象类型的,instanceOf 操作符也可以确定对象类型。如下所示:console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // true自定义构造函数模式的优缺点如下:优点:可以确保实例被标识为特定类型,相比于工厂函数,这是一个很大的好处。缺点:定义的方法在每个实例上都创建一遍。z这个缺点从上文的例子来看,person1 和 person2 的都有名为 sayName() 的方法,但这两个方法不是同一个 Function 实例。我们知道,ECMAScript 中的函数是对象,因此每次定义函数时,都会初始化一个对象。逻辑上讲,这个构造函数实际上是这样的:function Person(name) { this.name = name; this.sayName = new Function("console.log(this.name"); }这样理解这个构造函数可以更清楚知道,每个 Person 实例都会有自己的 Function 实例用于显示 name 属性。当然了,以这种方式创建函数会带来不同的作用域链和标识符解析。但创建新 Function 实例的机制是一样的,因此不同实例上的函数虽然同名却不相等,如下所示:console.log(person1.sayName === person2.sayName); // false因为都是做一样的事,所以没必要定义两个不同的 Function 实例。况且,this 对象可以把函数与对象的绑定推迟到运行时。要解决这个问题,我们可以把函数定义转移到构造函数外部:function Person(name){ this.name = name; this.sayName = sayName; } function sayName() { console.log(this.name); } let person1 = new Person("Lucy"); let person2 = new Person("Joe"); person1.sayName(); // "Lucy" person2.sayName(); // "Joe"sayName() 被定义到了函数的外部,构造函数内部,sayName 属性指向的是全局 sayName() 函数,所以 person1 和 person2 共享了定义在全局作用域上的 sayName 函数。虽然这样解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,代码也不能很好地聚集在一起。这个新问题可以通过原型模式解决。七、原型模式每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。function Person() {}; // 将属性和方法添加到 prototype 属性上 Person.prototype.name = "Lucy"; Person.prototype.sayName = function() { console.log(this.name) } let person1 = new Person(); person1.sayName(); // "Lucy" let person2 = new Person(); person2.sayName(); // "Lucy"八、组合模式组合模式是原型模式和构造函数模式的结合。function Person(name) { this.name = name; } Person.prototype = { constructor: Person, sayName: function() { console.log(this.name) } }; let person1 = new Person("Lucy"); let person2 = new Person("Joe"); person1.sayName(); //"Lucy" person1.constructor === Person; // true person2.sayName(); //"Joe" person2.constructor === Person; // true我们重写 prototype 对象时,最好保证 constructor 属性的正确性。组合模式的优点是该共享的共享了,缺点是封装性不够好。二、如何理解继承和原型链?JavaScript 中对象可以通过原型(prototype) 继承其他对象的特征。每个对象都有自己的 prototype 属性,叫做原型。原型和原型链的出现给代码复用提供了解决方案。MDN 的定义如下:当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 __proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。prototype 对象几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。Object() 是 JavaScript 内置的函数。注意 Object() 是函数,不是对象 object,调用 Object() 后返回一个新的对象。typeof(Object) // "function"Object()函数有一个匿名的对象,可以通过 prototype 属性访问:Object.prototype 对象上有很多有用的属性和方法,如 toString()Object.prototype 有个重要的属性 constructor,它指向 Object() 函数,Object.prototype.constructor === Object。我们定义一个构造函数:function Person(name){ this.name = name; }JavaScript 创建了一个新的函数 Person() 并且有匿名的对象 prototype:和 Object() 函数一样,Person() 函数也有自己的 prototype 指向一个匿名的对象,这个对象也有 constructor 属性指向 Person() 函数。function Person(name){ this.name = name; } console.log(Person); console.log(Person.prototype);打开 Chrome 浏览器的开发者工具,在 Console 中粘贴代码可以看到:此外,JavaScript 将 Person.prototype 对象和 Object.prototype 对象通过 [[Prototype]] 连接。原型链获取原型链__proto__是 Object.prototype 对象的可访问属性,它是内部原型链[[prototype]]对象对外暴露的可访问属性。__proto__在 ES6 中为了确保浏览器的兼容性,建议使用 Object.getPrototypeOf()替代。console.log(p1.__proto__ === Person.prototype); // true console.log(p1.__proto__ === Object.getPrototypeOf(p1)); // true注意 [[Prototype]] 、__proto__ 和 prototype 的关系:遵循 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__。但它不应该与构造函数 func 的 prototype 属性相混淆。被构造函数创建的实例对象的 [[Prototype]] 指向 func 的 prototype 属性。Object.prototype 属性表示 Object 的原型对象。原型链我们可以通过 prototype 对象添加属性和方法:Person.prototype.greet = function() { return "Hi, I'm" + this.name + "!"; }创建一个新的 Person 实例:let p1 = new Person("John");JavaScript 创建了一个新的对象 p1 并将 p1 对象和 Person.prototype 对象通过原型连接起来。原型链,就是下图中黄字部分的链状结构。我们可以用下面的代码验证:function Person(name) { this.name = name; } let p1 = new Person("John"); p1.__proto__ === Person.prototype; // true (1) Person.__proto__ === Function.prototype; // true (2) Function.__proto__ === Function.prototype; // true (3) Function.prototype.__proto__ === Object.prototype; // true (4) Object.prototype.__proto__ === null; // true (5)从上可知:p1 是构造函数 Person 创建的实例,它的内部原型链 [[Prototype]] 对象指向构造函数 Person 的原型对象 prototype。(代码 1 处)构造函数 Person 的内部原型链 [[Prototype]] 对象指向构造函数 Function 的原型对象 prototype。(代码 2 处)函数和对象也会一直向上查找,直到 null 为止。(代码 3、 4、 5处)性能在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。function Person(name) { this.name = name; } let p1 = new Person("John"); console.log(p1.hasOwnProperty("name")); // true console.log(p1.hasOwnProperty("toString")); // falsetoString() 方法可以通过原型链查找到,是继承的属性。总结Object() 函数有一个 prototype 属性,指向 Object.prototype 对象。Object.prototype 对象有对象所有的属性和方法,比如 toString()、valueOf()。Object.prototype 对象有 constructor 属性,指向 Object 函数。每个函数都有一个 prototype 对象,这个原型对象的 __proto__ 指向 Object.prototype, 通过 [[prototype]] 或者 __proto__ 属性查找属性和方法。Object.getPrototype() 方法返回给定对象的原型对象,建议使用 Object.getPrototypeOf() 替代 __proto__。通过 hasOwnProperty 检查对象是否具有自己定义的属性。小练习下面代码的输出结果是什么?Function.__proto__ === Object.prototype; // false Object instanceof Function; // true Function instanceof Object; // true
0
0
0
浏览量1668
懒人学前端

第二章:JavaScript数据类型—下

第二章:JavaScript数据类型—下四、谈谈 undefined 和 null ?undefined 和 null 都是基本数据类型。它们的定义是:undefined 意味着变量已经声明了但是没有赋值。null 是空值,可以作为对象的初始值。undefined 不是 undeclaredundeclared 是指变量从未在代码中出现.使用未声明的变量就会报错:”ReferenceError: cat is not defined“。undefined 则是声明了但是值是 undefined 或者值并不存在。如何获取安全的 undefined 值?因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。可以使用 void 0 获得安全的值。console.log(void 0); // undefined?? 和 ??=空值合并运算符(??)空值合并运算符(??)是一个逻辑运算符,判断左边的值是否是 null 或 undefined。如代码 a ?? b,如果 a 是 null 或 undefined,返回 b, 反之,返回 a。a ?? b 可以理解为: a !== undefined && a !== null ? a : b我们可以给值为 null 或 undefined 的变量一个默认值。如下面的代码所示:let firstName = null, lastName = 'Sun'; let fullName = firstName ?? lastName console.log(fullName); // Sun?? vs ||?? 和 || 的相同点:都可以为值为 null 或 undefined 的变量赋默认值。如下面代码:let firstName = null, lastName = 'Sun'; let fullName = firstName ?? lastName console.log(fullName); // Sun fullName = firstName || lastName; console.log(fullName); // Sun不同点在于:?? 只判断值 null 和 undefined|| 是任何假值(0, '', NaN, null, undefined)都不会被返回。这导致如果你使用 0,'' 或 NaN 作为有效值,就会出现不可预料的后果。如下面代码,判断值是 '' 时两者的不同表现:let firstName = '', lastName = 'Sun'; let fullName = firstName ?? lastName console.log(fullName); // '' fullName = firstName || lastName; console.log(fullName); // Sun逻辑空赋值运算符 ??=逻辑空赋值运算符(x ??= y)仅在 x 是空值(null 或 undefined)时对其赋值。a ??= b 可以理解为: a ?? (a = b) 。如下面的代码:let firstName = null; firstName ??= 'yangyang' console.log(firstName); // 'yangyang'五、typeof null 的结果是什么?JavaScript 中,typeof null 是 object,这是不对的,因为 null 是基本数据类型,不是对象。这是个 bug,但是因为修复这个 bug 会影响现存的代码,所以就一直没改。这个 bug 是 JavaScript 第一版的遗留物,这个版本中,值都是 32 位存储单元,由类型标签(1-3位)和实际的值组成。类型标签存在单元的低位里,有下面五种:000: 对象,数据是对象类型1:整数,存储的数据是一个 31 位的有符号整数。010:浮点数,存的数据是双精度浮点数100: 字符串,存的数据是字符串110:布尔,存的数据是布尔低位如果是 1 位,类型标签就是 1 位长度(如整数类型),如果是 0,类型标签是 3 位长度,提供两个额外的位,如其余的四个类型。六、JavaScript 如何做类型转换?使用 == 会发生隐式类型转换,=== 会严格判断两者的类型是否相等。我们也可以显式调用类型转换方法去转换类型,如使用 toString() 等。通常来说,隐式类型转换的难点主要有两点:使用 + 运算符作为一元运算符,转换成数字作为二元运算符,将两个操作数转成字符串,再进行拼接对象的隐式转换任意对象都有 toString 方法,将返回 [object Object]但是数组例外,toString 方法将返回由逗号分隔的一系列数字组成的字符串小练习给出下列代码输出结果:console.log(1 + "2" + "2"); // 122 console.log(1 + +"2" + "2"); // 32 console.log(1 + -"1" + "2"); // 02 console.log(+"1" + "1" + "2"); // 112 console.log( "A" - "B" + "2"); // NaN2 console.log( "A" - "B" + 2); // NaN console.log("10,11" == [[[[10]],11]]) // 10,11 == 10,11, 答案: true console.log("[object Object]" == {name: "test"}) // true答案:标注在代码注释中。七、==、 === 和 Object.is() 的区别是什么?JavaScript 提供三种不同的值比较操作:严格相等比较,使用 ===抽象相等比较,使用 ==以及 Object.is (ECMAScript 2015/ ES6 新特性)简而言之,在比较两件事情时:== 将执行类型转换=== 将进行相同的比较,不做类型转换Object.is 除下面三个值外,表现与 === 相同-0 和 +0 不相等Object.is(NaN,NaN) 为 true// === console.log(0 === -0); // true console.log(NaN === NaN); // false // Object.is console.log(Object.is(0, -0)); // false console.log(Object.is(NaN, NaN)); // true八、JavaScript 判断数据类型有哪些方法?有四种方法:方法一:typeoftypeof 运算符返回一个字符串,表示操作数的类型。下图是使用 typeof 判断类型的汇总:可以看到,typeof 判断类型有两个缺点:对 null 值的判断是 object,这个是历史遗留问题对 object 的判断区分不出具体的对象类型方法二: instanceofinstanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。// 定义构造函数 function C(){} function D(){} var o = new C(); o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype方法三:Object.prototype.constructorconstructor 属性返回 Object 的构造函数(用于创建实例对象)。注意:此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。所有对象(使用 Object.create(null) 创建的对象除外)都将具有 constructor 属性。在没有显式使用构造函数的情况下,创建的对象(例如对象和数组文本)将具有 constructor 属性,这个属性指向该对象的基本对象构造函数类型。const o = {} o.constructor === Object // true const o = new Object o.constructor === Object // true方法四:Object.prototype.toStringObject.prototype.toString 方法返回一个表示该对象的字符串。const toString = Object.prototype.toString; toString.call(new Date()); // [object Date] toString.call(new String()); // [object String]小练习如何实现 instanceof?答案:function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left); // 获取对象的原型 let prototype = right.prototype; // 获取构造函数的 prototype 对象 // 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; if (proto === prototype) return true; proto = Object.getPrototypeOf(proto); } }
0
0
0
浏览量225
懒人学前端

第五章:从数组 [1,2,3,4,5,6] 中找出值为 2 的元素

方法一:filter()filter() 方法创建一个新数组, 包含通过所提供函数实现的测试的所有元素。function isBigEnough(element) { return element == 2; } var filtered = [1,2,3,4,5,6].filter(isBigEnough);方法二:find()find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。var arr = [1, 2, 3, 4, 5, 6]; function finds(x) { return x === '2'; } console.log(arr.find(finds));
0
0
0
浏览量945
懒人学前端

7.优化

二、Tree Shakingtree-shaking所谓 tree-shaking 就是 “摇树” 的意思,可以将应用程序想象成一棵树。绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的静态结构特性,例如 import 和 export。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。 webpack 2 正式版本内置支持 ES2015 模块(也叫做 harmony modules)和未使用模块检测能力。新的 webpack 4 正式版本扩展了此检测能力,通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此可以安全地删除文件中未使用的部分。例子tree shaking 会在编译过程中将未使用的代码进行标记,在 uglify 时移除被标记的无效代码,在 mode 设置为 production 时,webpack 会开启压缩和 tree-shaking 等优化,下面的例子如果配置为生产模式,打包后未使用的引用会被移除掉。1、将 mode 设置为 none(不使用任何打包优化),optimization 配置中增加 usedExports 。index.js// add 只是导入没有使用 import { add } from './util.js'; const divElement = document.createElement('div'); divElement.innerHTML = addRes; divElement.className = 'demo'; document.body.appendChild(divElement);util.js// 导出 add 函数 export const add = (a, b) => { return a + b }webpack.config.js... module.exports = { ... mode: 'none', // 不使用任何默认优化选项 optimization: { // 告诉 Webpack 去决定每一个模块所用到的导出 usedExports: true, }, };执行打包命令后,查看 dist/index.js 中的代码。可以看到 index.js 中只做了导入但是没有调用的方法 add 被打了 unused harmony export 的注释。2、package.json 中增加 sideEffects: false "sideEffects": false,webpack.config.js 中移除 optimization 配置,index.js 与 util.js 中内容不变,执行打包命令后,可以看到打包后的 index.js 中 add 函数已经被移除。sideEffects 与 usedExports在了解上面两个参数区别之前,我们先来看下函数的副作用。函数副作用是指函数在正常工作任务之外对外部环境所施加的影响。具体地说,函数副作用是指函数被调用,完成了函数既定的计算任务,但同时因为访问了外部数据,尤其是因为对外部数据进行了写操作,从而一定程度地改变了系统环境。在上面例子中我们在 index.js 中导入的 util.js 中导出了一个 add 函数,我们来看下这个函数。export const add = (a, b) => { return a + b }add 函数中接收了 a 和 b 两个参数,函数体中返回了 a 与 b 的和,此函数返回的结果只依赖于传入的值并且没有其他影响,如修改全局变量等操作,则此函数即为无副作用函数。export const add = (a, b) => { window.userName = '***' return a + b }上面的 add 函数除了返回了 a 与 b 的和的同时还修改了 window对象中属性的值,则此函数即为有副作用的函数。简单了解了函数副作用,我们来看下 sideEffects 和 usedExports(更多被认为是 tree shaking)的两种不同的优化方式。usedExports 依赖于 terser 去检测语句中的副作用,对未使用的函数增加标记,之后交由压缩工具去移除死代码。webpack.config.js... module.exports = { ... mode: 'none', // 不使用任何默认优化选项 optimization: { // 告诉 Webpack 去决定每一个模块所用到的导出 usedExports: true, // 开启压缩 minimize: true }, };index.js 与 util.js 内容保持不变,在 webpack.config.js 的 optimization 配置中增加参数 minimize 为 true,打包成功后查看输出文件,可以看到之前被打了 unused harmony export 注释的代码被移除掉了。下面我们来写一个带有副作用的方法,看看 usedExports 会如何处理。index.jsimport './util.js'; const divElement = document.createElement('div'); divElement.innerHTML = 'demo'; divElement.className = 'demo'; document.body.appendChild(divElement);util.jsArray.prototype.custom = function () { console.log('custom'); }; export const add = (a, b) => { return a + b; };util.js 中修在 Array 原型链上增加了方法,所以是 util.js 是有副作用的,在 index.js 中我们不只导入 add 方法而是导入整个 util.js 文件,执行打包命令后可以查看 dist/index.js 文件中 add 方法已被删除,有副作用的部分依然在打包文件中。sideEffects 更为有效 是因为它允许跳过整个模块/文件和整个文件子树在一个纯粹的 ESM 模块世界中,很容易识别出哪些文件有副作用。然而,我们的项目无法达到这种纯度,所以,此时有必要提示 webpack compiler 哪些代码是“纯粹部分”。通过 package.json 的 "sideEffects" 属性,来实现这种方式。如果所有代码都不包含副作用,我们就可以将 sideEffects 标记为 false,来告诉 webpack 它可以跳过安全检测删除未用到的 export。依然使用上面包含副作用的代码。来看下增加 sideEffects: false 的效果。package.json{ // ... "sideEffects": false, // ... }webpack.config.jsmodule.exports = { mode: 'none', entry: { index: './src/index.js', }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', } };执行打包命令后查看 dist/index.js 文件,可以看到包含副作用的代码也被移除,即 util.js 导入部分被全部移除。项目中包含副作用的函数被移除在打包后会导致部分功能不可用,所以 sideEffects 支持传入参数,告知 webpack 传入的文件包含副作用不要移除。上面的例子中修改 package.json 的配置,将包含副作用的文件传入进去。此数组支持简单的 glob 模式匹配相关文件。其内部使用了 glob-to-regexp(支持:*,**,{a,b},[a-z])。如果匹配模式为 *.css,且不包含 /,将被视为 **/*.css。package.json{ // ... "sideEffects": ["./src/util.js" ], // ... }执行打包命令后查看输出文件,可以看到只有导入但是没使用的 add 函数和 Array 方法都被保留了下来。tree shaking 开启条件tree shaking 需要使用 ES2015 模块语法(即 import 和 export)才能生效,有时我们会发现只引用了某个库中的一个方法,却把整个库都加载进来了,同时 bundle 的体积也并没有因为 tree shaking 而减少。这可能是该库不是使用 ESModule 模式导出的,所以在使用某个库时,我们尽量引入 es 版,按需加载,这样 tree shaking 才能将无用代码删除掉。总结为了利用 tree shaking 的优势, 我们必须使用 ES2015 模块语法(即 import 和 export)。确保没有编译器将您的 ES2015 模块语法转换为 CommonJS 的(顺带一提,这是现在常用的 @babel/preset-env 的默认行为,详细信息请参阅文档)。在项目的 package.json 文件中,添加 "sideEffects" 属性。使用 mode 为 "production" 的配置项以启用更多优化项,包括压缩代码与 tree shaking。二、代码分离及懒加载代码分离及懒加载前端性能优化一直是围绕在每一个前端周围的话题,减少网络请求、减少加载 bundle 体积、外部资源放在 CDN 等等。对于性能优化 webpack 也提供了一些手段。下面让我们来了解下代码分离、缓存和懒加载。代码分离代码分离是 webpack 最引人注目的特性之一,试想如果项目中所有代码都打包到一个 bundle 中,那 bundle 的体积将会变大,这对首次访问页面来说,加载资源的请求时间会变长,将影响用户体验。所以前端性能优化的一个方向是将代码按照一定规则拆分到不同的 bundle 中,触发不同的功能加载不同的资源,这样除了减少资源体积外还能增快请求响应速度。不过拆分的粒度大小还是要看实际的项目需求,无限拆分资源包也会造成资源请求过多。所以对于代码分离我们要结合项目情况合理使用,这会极大影响加载时间。常用的代码分离方法有三种:入口分离:使用 entry 配置手动地分离代码。去重分离:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。动态导入:通过模块的内联函数调用来分离代码。入口分离入口分离即从 webpack 入口文件配置处分离 bundle。这种分离方式根据项目的多个入口拆分出多个 bundle,这是所有分离方式中最简单的分离方式,不过这种方式也存在弊端即 输出的 bundle 会同时包含导入的三方依赖代码(重复代码),在后面的分离方式讲解中会解决这个问题。我们先来看看入口分离的例子。src/index.jsindex.js 文件中引入依赖包 lodash 的 join 方法。创建 div 标签并将内容添加到 body 中。import { join } from 'lodash'; const divElement = document.createElement('div'); divElement.innerHTML = join(['hello', 'index'], '-'); document.body.appendChild(divElement);src/main.jsmain.js 文件中同样引入依赖包 lodash 的 join 方法。创建 div 标签并将内容添加到 body 中,只是展示内容与 index.js 不一致。import { join } from 'lodash'; const divElement = document.createElement('div'); divElement.innerHTML = join(['hello', 'main'], '-'); document.body.appendChild(divElement);webpack.config.js在 entry 配置中传入两个入口,chunk 的名字分别是 index 和 main。const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); ... module.exports = { mode: 'development', entry: { index: './src/index.js', main: './src/main.js' }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, plugins: [ ... new HtmlWebpackPlugin({ template: './index.html', }), ], };执行打包命令后,在 dist 文件夹下可以看到,同时输出了 mian.js 和 index.js 文件,通过配置 entry 实现了代码分离。我们分别打开 dist/index.js 和 dist/main.js 后可以看到两个 bundle 中都包含了 lodash 的源码。去重分离在入口分离的基础上,我们继续优化将重复的部分单独分离出一个 bundle,在 entry 中配置 dependOn 选项,这样可以在多个 chunk 之间共享模块。webpack.config.js在 entry 配置中将两个入口文件按照对象的形式定义,除定义入口之外,增加了一个 dependOn 选项,传入的 vendor 为共享模块的 chunk 名称。const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); ... module.exports = { ... entry: { index: { import: './src/index.js', dependOn: 'vendor', }, main: { import: './src/main.js', dependOn: 'vendor', }, vendor: 'lodash' }, };执行打包命令后,在 dist 文件夹下除了两个入口文件外还多了一个 vendor.js 文件,文件的内容即为 lodash 源码。HtmlWebpackPlugin 插件将三个 js 文件都注入到了 index.html 中。项目可以正常运行。通过在入口配置 dependOn 属性,虽然可以实现公共代码抽离,但是还存在一个问题是,在我们的项目中会有很多公共代码,难道我们要手动的都添加到 dependOn 中吗?这将会增加非常多的工作量并且容易出错。这时我们可以考虑使用 webpack 的 SplitChunksPlugin 插件了。SplitChunksPluginSplitChunksPlugin 插件不需要单独安装,是 webpack 提供的开箱即用的插件。我们通过 optimization 配置即可。默认情况下,SplitChunksPlugin 它只会影响到按需加载的 chunks,不过我们可以通过传入 chunks 属性不同变量来决定影响哪些 chunk 。webpack 将根据以下条件自动拆分 chunks:新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹新的 chunk 体积大于 20kb(在进行压缩和 gzip 之前的体积)当按需加载 chunks 时,并行请求的最大数量小于或等于 30当加载初始化页面时,并发请求的最大数量小于或等于 30我们使用 SplitChunksPlugin 来拆分下上个例子中的 lodash,关于 SplitChunksPlugin 的各项配置参数含义,感兴趣的伙伴可以查看官网webpack.config.js... module.exports = { entry: { index: './src/index.js', main: './src/main.js', }, optimization: { splitChunks: { // 设置为 all 意味着 chunk 可以在异步和非异步 chunk 之间共享 chunks: 'all', }, }, ... };执行打包命令后,在 dist 文件夹下可以看到除了 index.js 和 main.js 外还输出了一个文件,打开文件可以看到内容正是 lodash。动态导入动态导入也叫资源异步加载,当模块数量过多时,可以把一些暂时用不到的模块延迟加载,以此来减少用户初次加载页面的体积,后续模块等到恰当的时机再去加载,因此也把这种加载方式叫懒加载。在 webpack 中提供了两种类似的技术来实现动态加载。import 函数及 require.ensure。require.ensure 是 Webpack 1 支持的异步加载方式,从 Webpack 2 开始引入了 import 函数,并且官方更推荐使用 import 函数的方式实现异步加载,因此下面我们只介绍 import 函数的使用。通过 import 函数加载的模块会被异步的进行加载,并且返回一个 Primise 对象。main.jsexport const add = (a, b) => { console.log(a + b); }index.jsindex 文件中通过 import 函数导入 main 文件内容并赋值给 addFunc,在 index 中通过 setTimeout 定时任务 2 秒钟后执行 addFunc 回调函数,在回调函数返回值中通过 ES6 的对象解构语法获取到 add 函数。const addFunc = import('./main'); setTimeout(() => { addFunc.then(({add}) => { add(3, 8); }); }, 2000);webpack.config.js... module.exports = { mode: 'development', entry: { index: './src/index.js' }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, ... };通过 webpack.config.js 配置我们可以看到入口文件只有 index.js,执行打包命令后在生成的 dist 文件夹中可以看到除了入口文件 index.js 外,还生成了一个 src_main_js.js 的文件,内容即为 src/main.js 中内容对应的编译后代码。通过 import 函数异步加载文件也可以实现拆分代码的功能,这种方式在实际的项目开发中非常实用,非主页面加载的内容都可以通过 import 函数动态加载,在点击到对应页面时在加载相应资源。上面的例子中还可以进一步优化下异步加载文件的 chunk 名称。其他配置不变,我们修改下 import 导入函数部分。index.jsconst addFunc = import(/* webpackChunkName: "main" */ './main'); setTimeout(() => { addFunc.then(({add}) => { add(3, 8); }); }, 2000);通过在 import 函数中增加注释 /* webpackChunkName: "main" */ 其中的 main 即为生成的 chunk 名称。这样在项目中通过定义语义化的名称,可以增加代码的可读性。总结上面的示例中,我们总结了代码分离的几种方式,分别是入口分离、去重分离、动态导入,其中的动态导入就是我们常说的懒加载。代码分离对于减少主包体积,优化项目加载速度,减少白屏时间来说将非常有用。
0
0
0
浏览量2014
懒人学前端

第四章:对象—下

五、Map 和 WeakMap 有什么区别WeakMap 提供的接口与 Map 相同,但是它们有以下的区别:MapMap 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。创建和设置对象属性下面展示创建 Map 和它的方法的使用:// 创建 const users = new Map(); // String 作为 key users.set("John", { address: "John's Address" }); // Object 作为 key const obj = { name: "Michael" }; users.set(obj, { address: "Michael's Address"}); // Function 作为 key const func = () => "Andrew"; users.set(func, { address: "Andrew's Address"}); // NaN 作为 key users.set(NaN, { address: "NaN's Address"});一些方法的使用如下:const contacts = new Map(); contacts.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" }); contacts.has("Jessie"); // true contacts.get("Hilary"); // undefined contacts.delete("Raymond"); // false console.log(contacts.size); // 1Map vs ObjectObject 和 Map 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Map 使用。不过 Map 和 Object 有一些重要的区别,在下列情况中使用 Map 会是更好的选择:WeapMapWeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。创建和设置对象属性设置对象const test = new WeakMap() const o1 = {}; test.set(o1, {name: 'test'});获取对象const test = new WeakMap(); const o1 = function () {}; test.set(o1, { name: "test" }); test.get(o1);删除和查询对象const test = new WeakMap(); const o1 = function () {}; test.set(o1, { name: "test" }); test.has(o1); // true test.delete(o1); test.has(o1); // false应用WeakMap 对象的一个用例是存储一个对象的私有数据或隐藏实施细节。const privates = new WeakMap(); function Public() { const me = { // 私有数据 }; privates.set(this, me); } Public.prototype.method = function () { const me = privates.get(this); // ... }; module.exports = Public;总结Map 和 WeakMap 的功能以及方法(都有 get()、 set()、has()、delete())是一样的,它们区别有以下四个方面:定义键值(key)垃圾回收有无 size 属性小练习Map 和 Object 有什么区别?答案:见正文。六、如何实现深拷贝和浅拷贝?深拷贝和浅拷贝都是针对数据类型是 Object 而言,因为对象是引用类型,如果要拷贝一个副本,副本中的对象更改后,不影响到原对象(或修改原对象不影响副本),即为深拷贝,否则,为浅拷贝。这是因为,计算机存储数据有两种内存:stack 和 heap:stack 是一个临时存储空间,存放的是原始值变量和对象的引用。heap 存储全局变量,对象的值都是存在 heap 中,stack 中保存的是对象的引用。下图可以帮助更好理解:这里将介绍 5 种浅拷贝的方法和 4 种深拷贝的方法。浅拷贝(shallow copy)浅拷贝对原对象或副本的更改可能也会导致其他对象的更改。它实际上只拷贝了一层,并且只当数组和对象包的值是原始值时才进行拷贝。赋值运算符 =如果将一个对象赋值给另一个对象,也是浅拷贝。const array = [1, 2, 3]; const copyWithEquals = array; console.log(copyWithEquals === array); // true 扩展运算符 ...扩展运算符 ... 可以很方便地浅拷贝对象和数组:const array = [1, 2, 3]; const copyWithEquals = array; console.log(copyWithEquals === array); // true const copyWithSpread = [...array]; console.log(copyWithEquals === array); // true.slice()数组可以使用内置的方法 .slice(),它的作用和扩展运算符一样,都可以实现浅拷贝:const array = [1, 2, 3]; const copyWithSlice = array.slice(); console.log(copyWithSlice === array); // false // 改变原数组 array array[0] = 4; console.log(array); // [4, 2, 3] 原数组改变了 console.log(copyWithSlice); // [1, 2, 3] 拷贝的数组没有受到影响.assign()使用 Object.assign() 也可以实现对象和数组的浅拷贝:const array = [1, 2, 3]; const copyWithEquals = array; // 没有拷贝数组 const copyWithAssign = []; Object.assign(copyWithAssign, array); array[0] = 4; console.log(array); // [4, 2, 3] 原数组改变了 console.log(copyWithAssign); // [1, 2, 3] 拷贝的数组没有受到影响Array.from()Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。const array = [1, 2, 3]; const copyWithEquals = array; // 没有拷贝数组 const copyWithArrayFrom = Array.from(array); array[0] = 4; console.log(array); // [4, 2, 3] 原数组改变了 console.log(copyWithArrayFrom); // [1, 2, 3] 拷贝的数组没有受到影响深拷贝(deep copy)JavaScript 对象和数组如果深度嵌套,浅拷贝只能实现第一层的拷贝,但是深度的值依然和原对象共享引用。const nestedArray = [[1], [2], [3]]; const nestedCopyWithSpread = [...nestedArray]; nestedArray[0][0] = '4'; // 注意这里 console.log(nestedArray); // [[4], [2], [3]] 原数组被修改了 console.log(nestedCopyWithSpread); // [[4], [2], [3]] 拷贝的数组值也被修改了我们希望的深拷贝是无论是嵌套多少层数组和对象,拷贝的数组、对象和原来的数组、对象互不受影响。如果数组和对象中还包含其他数组和对象,拷贝这些对象、数组就需要使用深拷贝,否则,改变嵌套的引用将会影响到嵌套的原数组和对象。深拷贝和浅拷贝的区别在于,浅拷贝对于数组和对象仅仅包含原始值时表现良好,但是数组和对象中嵌套其他数组和对象便不能正确拷贝。=== 可以帮助我们理解深浅拷贝:const nestedArray = [[1], [2], [3]]; const nestedCopyWithSpread = [...nestedArray]; console.log(nestedArray[0] === nestedCopyWithSpread[0]); // true const nestedCopyWithHack = JSON.parse(JSON.stringify(nestedArray)); // 使用 JSON.stringify() 进行深拷贝 console.log(nestedArray[0] === nestedCopyWithHack[0]); // false 深拷贝之后两个对象用 === 比较不相等第三方库:lodash、rfdc深拷贝可以使用 lodash 库,使用方法如下:import _ from "lodash" const nestedArray = [[1], [2], [3]]; const shallowCopyWithLodashClone = _.clone(nestedArray); const deepCopyWithLodashClone = _.cloneDeep(nestedArray);rfdc 库也可以实现深拷贝,它的优点是速度快:const clone = require('rfdc')() // clone({a: 37, b: {c: 3700}}) // {a: 37, b: {c: 3700}}JSON.parse/stringifyJSON.parse(JSON.stringify(object)) 也可以实现深拷贝,但是不同类型的值表现各有区别。因此,不建议使用。// 仅仅下面的数据类型支持使用 JSON.parse()、JSON.stringify() const sampleObject = { string: 'string', number: 123, boolean: false, null: null, notANumber: NaN, // NaN 会被忽略 date: new Date('1999-12-31T23:59:59'), // Date 将被转成字符串 undefined: undefined, // Undefined 会被忽略 infinity: Infinity, // Infinity 会被忽略 regExp: /.*/, // RegExp 会被忽略 } console.log(sampleObject) // Object { string: "string", number: 123, boolean: false, null: null, notANumber: NaN, date: Date Fri Dec 31 1999 23:59:59 GMT-0500 (Eastern Standard Time), undefined: undefined, infinity: Infinity, regExp: /.*/ } console.log(typeof sampleObject.date) // object const faultyClone = JSON.parse(JSON.stringify(sampleObject)) console.log(faultyClone) // Object { string: "string", number: 123, boolean: false, null: null, notANumber: null, date: "2000-01-01T04:59:59.000Z", infinity: null, regExp: {} } console.log(typeof faultyClone.date) // stringstructuredCloneMDN 定义如下:全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝structuredClone 将返回一个原对象的深拷贝,它的语法如下:structuredClone(value, options)value 为需要进行深拷贝的对象options 可选const nestedArray = [[1], [2], [3]]; const nestedCopyWithStructuredClone = structuredClone(nestedArray); // 深拷贝 console.log(nestedArray === nestedCopyWithStructuredClone); // false nestedArray[0][0] = 4; console.log(nestedArray); // [[4], [2], [3]] 修改了原对象 console.log(nestedCopyWithStructuredClone); // [[1], [2], [3]] 复制对象不受影响注意:structuredClone() 是 ECMAScript 的一部分,虽然被大部分浏览器支持,但是还有部分浏览器不可用(在 360 浏览器中报 undefined)。自定义函数实现深拷贝我们可以手写一版深拷贝,顺便巩固一下对深拷贝的理解。简单版:处理对象和数组实现深拷贝的关键就是使用递归复制原对象(或数组)的键和值,判断嵌套的值是否是引用类型(如下代码中的对象或数组),如果是,则继续递归,直到值是原始值为止。const deepCopy = (obj) => { // 如果不是对象或者为 null,直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 创建一个新对象或数组 const newObj = Array.isArray(obj) ? [] : {}; Object.keys(obj).forEach((key) => { newObj[key] = deepCopy(obj[key]); }); return newObj; }; // 测试对象和数组的深拷贝: const nestedArray = [[1], [2], [3]]; const nestedCopyWithStructuredClone = deepCopy(nestedArray); // 深拷贝 console.log(nestedArray === nestedCopyWithStructuredClone); // false nestedArray[0][0] = 4; console.log(nestedArray); // [[4], [2], [3]] 修改了原对象 console.log(nestedCopyWithStructuredClone); // [[1], [2], [3]] 复制对象不受影响注意:我们需要判断单独判断 null 的类型,因为 typeof null 的结果是 "object"。进阶版:处理循环引用简单版只处理了普通的数组和对象,然而,如果遇到循环引用,上面的代码就不能得到正确的结果了。我们先看一个循环引用的例子:const nestedObject = { name: 'Lily' } nestedObject.nestedObject = nestedObject; // nestedObject 有个 nestedObject 属性指向 nestedObject,即:自己引用自己如果使用简单版的深拷贝,会得到下面的报错:解决循环引用导致的栈内存溢出,我们只需要使用 WeakMap 记录已经复制过的对象,如下面代码中的 cache,如果已经复制过该对象,则直接返回缓存中的对象,避免了无限递归的问题。const deepCopy = (obj, cache = new WeakMap()) => { // 如果不是对象或者为 null,直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 如果已经复制过该对象,则直接返回 if (cache.has(obj)) { return cache.get(obj); } // 创建一个新对象或数组 const newObj = Array.isArray(obj) ? [] : {}; // 将新对象放入 cache 中 cache.set(obj, newObj); // 处理循环引用的情况 Object.keys(obj).forEach((key) => { newObj[key] = deepCopy(obj[key], cache); }); return newObj; }; // 测试有循环引用的深拷贝: const nestedObject = { name: 'Lily' } nestedObject.nestedObject = nestedObject; const nestedCopyWithStructuredClone = deepCopy(nestedObject); // 深拷贝 console.log(nestedObject === nestedCopyWithStructuredClone); // false最终版:处理特殊对象:Date 和 RegExp简单版只处理了对象和数组的情况,这里,我们再增加处理 Date 和 RegExp,让代码更加完善。const deepCopy = (obj, cache = new WeakMap()) => { // 如果不是对象或者为 null,直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 如果已经复制过该对象,则直接返回 if (cache.has(obj)) { return cache.get(obj); } // 创建一个新对象或数组 const newObj = Array.isArray(obj) ? [] : {}; // 将新对象放入 cache 中 cache.set(obj, newObj); // 处理特殊对象的情况 if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof RegExp) { return new RegExp(obj); } // 处理循环引用的情况 Object.keys(obj).forEach((key) => { newObj[key] = deepCopy(obj[key], cache); }); return newObj; }; // 测试 Date 和 regExp 的深拷贝: const date = new Date(); const regExp = /test/g; const cloneDate = deepCopy(date); // 深拷贝 const cloneRegExp = deepCopy(regExp); // 深拷贝 console.log(cloneDate === date); // false console.log(cloneRegExp === regExp); // false当然,除了上述提到的类型外,还有函数、Symbol 等类型,这里不做阐述,感兴趣的可以去看相关库的源码。小练习实现深拷贝有哪些方法?答案:见正文。
0
0
0
浏览量1372
懒人学前端

16.设计—下

六、多方法隐藏元素七、实现文字描边text-shadow<style> div { font-size: 100px; color: #fff; text-shadow: 0 0 3px black; } </style> <div>文字描边</div>-webkit-text-stroke<style> div { font-size: 100px; color: #fff; -webkit-text-stroke: 3px black; } </style> <div>文字描边</div>position: relative/position: absolute子绝父相<style> div { position: relative; font-size: 100px; color: #fff; } div p { position: absolute; margin: 0; } div p:first-child { font-weight: bolder; color: black; } </style> <div> <p>文字描边</p> <p>文字描边</p> </div>八、实现渐变背景背景图片一张宽 1px 像素,高度沿渐变方向固定,重复铺满即可<style> div { height: 250px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAD6CAIAAADvIroEAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA01pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDYuMC1jMDAyIDc5LjE2NDQ4OCwgMjAyMC8wNy8xMC0yMjowNjo1MyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKDIwMjAwODE4Lm0uMTAxMiAyMDIwLzA4LzE4OiBmODJmNDMzNzYxKSAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkQxQ0JDMDQyNzlEOTExRUJBQkYyQzQzRUVCM0ZDNjg0IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkQxQ0JDMDQzNzlEOTExRUJBQkYyQzQzRUVCM0ZDNjg0Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RDFDQkMwNDA3OUQ5MTFFQkFCRjJDNDNFRUIzRkM2ODQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RDFDQkMwNDE3OUQ5MTFFQkFCRjJDNDNFRUIzRkM2ODQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz470lWNAAAAp0lEQVR42pRTQQ6DMAxLInHc/x+0M/8BaWAKbQQobtQdkGmtuLHTCr6zYdtNRAzlq6jvtRZEQ8Y3vP81cBeWekArMp5oUy70k/dFee+B+OprEF9ei1NTu3UiSPo7M2k89ZVkeu0jng33GM9OdbV5efIqSS4dTz4jlvlA/SsXKM98JFfnH76qxh93hd7jbN7ZvDwbJX5G5h3fQcy31i4b7DOZrL/9EGAAxQGYLZj/m8EAAAAASUVORK5CYII=); background-repeat: repeat-x; background-size: 1px 100%; } </style> <div></div>linear-gradient<style> div { height: 250px; background-image: linear-gradient(to bottom, pink, skyblue); } </style> <div></div>九、对比常见图片格式和 base64 图片?可以将图片转为 Base64 编码,直接将编码放入 CSS 中,即可引入图片编码后的图片通常比原图大 30% 或更多,但可以与 CSS 一起被 gzip 或 br 压缩适用小图片和没有权限上传图片的场景,来减少请求,但也应设置代码编辑器不换行或折叠图片编码区域,避免影响 CSS可读性十、为什么要重置浏览器默认样式,对比 Reset.css 和 Normalize.css?什么是浏览器默认样式对于部分HTML标签,如段落、列表,部分表单元素,浏览器会提供默认样式,包含外观及交互,开发者只需引入标签,不需要重复定义这些样式,便于开发为什么要重置默认样式不同浏览器,默认样式可能不同,特别是尺寸,位置的不同,让开发者无法统一用户体验,甚至有错位的风险只使用标签的语义,而不想引入样式UI稿与浏览器默认样式不同基于以上,都需要开发者重置或部分重置浏览器的默认样式,以满足开发需求对比常用的 Reset.css 和 Normalize.css共同点两者都能抹平浏览器间的默认样式差异都部分重置了浏览器默认样式,尤其是内外边距属性不同点Reset.css让元素在不同浏览器样式完全一样Normalize.css适当保留部分浏览器默认样式,只重置影响开发的样式,此外Normalize.css修复了表单、SVG溢出等BUGNormalize.css适当提高了可用性Normalize.css避免大量使用群组选择器,通过注释提高调试体验最佳实践对于绝大多数小型项目:只重置在页面中使用到的标签只重置有默认属性的属性名适当保留浏览器的默认样式,如表单的outline
CSS
0
0
0
浏览量1997
懒人学前端

3.继承

一、什么是继承?CSS 属性分为非继承属性和 继承属性,继承属性的默认值为父元素的该属性的 计算值,非继承属性和根元素的继承属性的默认值为初始值。对于非继承属性,可以显示的声明属性值为 inherit,让子元素的属性继承父元素。常见的继承属性:字体 font 系列文本 text-align text-ident line-height letter-spacing颜色 color列表 list-style可见性 visibility光标 cursor容易被误认为继承属性的 非继承属性:透明度 opacity背景 background系列二、如何重置元素的属性值到初始值?属性值initial可以将属性设为W3C规范初始值属性all可以将元素的所有属性重置在规范之外,浏览器还会为部分元素,如表单元素设置默认样式属性的值来源于开发者定义,用户配置和浏览器默认all:initial相当于清空了用户配置和浏览器默认样式工作中,我们更希望重置到默认样式,而不是清空它们all:revert属性还原。可以将子元素的属性重置按如下规则重置:继承属性:重置到父元素的属性值非继承属性或父元素继承属性都未设置:重置到用户配置和浏览器默认属性示例:<style> button { color: yellow; border: 1px solid red; background-color: red; } button:nth-of-type(2) { all: initial; /* 清空按钮的样式 */ color: blue; } button:last-of-type { all: revert; /* 保留按钮的默认样式 */ color: blue; } </style> <button>按钮1</button> <button>按钮2</button> <button>按钮3</button>
CSS
0
0
0
浏览量2009
懒人学前端

1.概念—上

一、什么是 HTML?HTML,全称是 HyperText Markup Language,即超文本标记语言,它不是编程语言,而是一种用来告知浏览器如何组织页面的标记语言,用来描述网页的表现,展示效果或功能及行为“超文本”(hybertext) 是指连接单个网站或多个网站网页的链接HTML 使用“标记”(markup) 来注明文本、图片和其它内容HTML 通过“标签”(tag)标记元素,标签由在<和>中包裹的元素名组成HTML 标签里的元素名不区分大小写。可以用大写、小写或混合形式书写二、常用的浏览器引擎是什么 ?浏览器是一种从 Web 获取和显示页面的程序,让用户通超链接访问更多页面排版引擎(Layout Engine),也称为浏览器引擎(Browser Engine)、页面渲染引擎(Rendering Engine)或样板引擎,它是软件组件,负责获取标记式内容(如 HTML、XML 及图像文件等)和整理信息(如 CSS 及 XSL 等),并将排版后内容输出至显示器或打印机常见的浏览器排版引擎分别是:Mozilla Firefox 使用 Gecko 引擎Apple Safari 和 早期 Google Chrome 使用 KDE 引擎,后发展成为 WebKit 引擎Internet Explorer 使用 Trident 引擎Microsoft Edge 早期使用 EdgeHTML 引擎Opera 早期使用 Presto 引擎目前,Google Chrome 及基于 Chromium 浏览器,如 Microsoft Edge,Opera 使用基于 WebKit 分支自行构建的 Blink 引擎三、请列举常用的 HTML 实体字符 ?字符 < >  "  ' 和 & 等本身是 HTML 语法自身的特殊字符表示其本身需要使用字符引用,即表示字符的特殊编码,每个字符引用以 & 开始,分号 ; 结束四、HTML 注释如何写 ?HTML 注释使用特殊标记<!--和-->包裹HTML 注释不会被渲染会被传输解析时,早期 IE 浏览器使用 HTML 注释区分版本通常使用 UglifyJS 和 Terser 或正则匹配的方式,在生产环境删除注释HTML 注释用来描述代码是如何工作的不同部分代码做了什么五、什么是 HTML 语义化,有什么好处,一定要 HTML 语义化吗 ?语义是语言的含义,语义化是前端开发的专用术语,语义类标签是对内容的补充,表达标题摘要,文章结构、强调重点、丰富含义,避免歧义HTML 语义化的好处包括增强可读性,便于开发和维护增强可访问性,便于屏幕阅读器定位和朗读增强结构清晰度,利于 SEOHTML 语义化不是一定要执行的标准利用无语义标签,如<div>和<span>可以满足几乎所有开发需求可读性,可访问性和 SEO,使用语义化标签不是必须的部分语义化标签存在兼容性问题,如 <button> 的默认 type不总为 submit 等滥用列表标签,会增加不必要的嵌套,增加额外的 CSS Reset 的样式HTML 语义化以外,良好的命名,简明扁平的结构,良好的无障碍设计,清晰的导航和分区,一定程度上,也能弥补语义的欠缺,提升代码的机器阅读体验,降低抓取难度,提高索引权重在明确知晓语义化标签的含义和组合搭配后,探索其使用的最佳实践和场景,而不是盲目地滥用、错用语义化标签,才能让 HTML 语义化标签体现更好的价值六、连续空格如何渲染,意义是什么 ?为了代码的可读性,开发者通常会在 HTML 元素嵌套中使用空白空白可以使用空格或 TAB 缩进实现HTML 解释器会将连续出现的空白字符减少为一个单独的空格符如果一定要使用连续空格,可以使用全角空格或者实体字符  七、如何声明文档类型 ?<!DOCTYPE html> 是最简单有效的文档类型声明,目的是防止浏览器在渲染文档时,切换到“怪异模式(兼容模式)”。确保浏览器按照最佳相关规范进行渲染,而不是使用一个不符合规范的渲染模式。八、哪些字符集编码支持简体中文,如何解决 HTML 乱码问题 ?(1)支持简体中文的字符集编码GB 2312共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个,同时收录拉丁字母、希腊字母、日文平假名和片假名字母、俄语西里尔字母在内 682 个字符使用区位码“分区”,每区含有 94 个汉字 / 符号 01 - 09 区为特殊符号 16 - 55 区为一级汉字,按拼音排序 56 - 87 区为二级汉字,按部署 / 笔画排序无法处理人名、古汉语中的罕用字和繁体字GBK汉字内码扩展规范拓展 GB 2312 - 80,拥有 23940 个码位,包括 21003 个汉字,883 个图形符号兼容 BG 2312 - 80,支持 希腊字母、俄语字母,不支持韩国字GB 18030国家标准 GB 18030 - 2005多字节编码,编码空间可定义 161 万个字元,包括 70244 个汉字完全兼容 GB 2312,基本兼容 GBK,支持少数民族文字、繁体汉字和日韩汉字BIG5大五码、五大码支持 13060 个中文文字Unicode万国码,国际码,统一码或单一码采用 ISO 10646 通用字符集,应用 UCS-2 使用 16 位编码空间,支持 65536 个字符Unicode 转换格式即 UTF,UTF-8、UTF-16、UTF-32 是将数字转换到程序数据的编码方案UTF-8多字节编码,针对 Unicode 的可变长度字符编码使用 1 到 6 字节为每字符编码,实际最多 4 字节 1 字节编码:ASCII 字符 2 字节编码:带附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母 3 字节编码:其他基本多文种平面(BMP)中字符(包含大部分常用字,汉字) 4 字节编码:其他极少使用 Unicode 辅助平面的字符,如 Emoji 字符UTF-16介于 UTF-8 和 UTF-32 间,使用 2 字节或 4 字节存储,长度既固定又可变UTF-32固定长度的编码方案,不管字符编号大小,始终使用 4 字节存储(2)如何解决 HTML 汉字乱码问题HTML 汉字乱码的原因:客户端不支持 HTML 编码的字符集实际存储的字符集与使用 meta 标签声明的字符集不一致部分现代浏览器会自动纠正,根据实际使用的字符集编码渲染 HTML解决方法:建议使用 utf-8 存储并在页面添加 <meta charset="utf-8"> 声明编码类型9.如何验证 HTML 是否正确 ?难度:★ ☆ ☆ ☆ ☆验证 HTML 的最好方法使用 W3C 创立并维护的标记验证服务,网址如下:https://validator.w3.org/提交一个线上 URL,HTML 文件或者代码,网页会返回相应的错误报告10.什么是 HTML5,HTML5 有哪些新特性 ?难度:★ ★ ★ ☆ ☆(1)什么是 HTML5?HTML5 是定义 HTML 标准的组新版本,具有两个不同的概念:HTML5 是一个新版本的 HTML 语言,具有新的元素,属性和行为HTML5 有更大的技术集,允许构建多样化和更强大的网站和应用程序(2)HTML5 有哪些新特性 ?根据功能,HTML5 新特性可以分为:语义:能够更恰当地描述内容是什么新的区块和段落元素举例: <section> 表示一个包含在 HTML 文档的独立部分 <article> 表示文档、页面、应用或网站中的独立结构 <nav> 表示页面的一部分,其目的是在当前文档或其他文档提供导航链接 <header> 用于展示介绍性内容辅助导航。包含标题,Logo,搜索框和作者名称 <footer> 表示最近一个章节或根节点元素的页脚,包含作者,版权,相关链接 <aside> 表示一个和其余页面内容几乎无关的部分,通常是侧边栏或标注框 <hgroup>代表文档章节所属的多级别目录嵌入和允许操作新的多媒体内容举例: <audio> 用于在文档中嵌入音频内容 <video> 用于再文档嵌入媒体播放器,支持视频及音频播放表单的改进强制性校验 API举例: required 必填属性 pattern 声明正则校验规则属性 minlength 和 maxlength 限制输入的长度 constraint validation API 检测和自定义表单元素的状态新 <input> 元素的 type 属性值举例: color 取色器 date 日期控件 detetime-local 不包括时区的日期控件 month 输入年和月的控件,没有时区 range 输入不需要精确地数字空间 search 搜索字符串的单行文字区域 tel 输入电话号码的控件 time 输入时间的控件 url 输入并校验 URL 的控件其它新的语义元素举例: <mark> 为表示引用或符号目的而标记或突出显示的文本 <figure> 常与 <figcaption> 配合使用,表示独立的说明内容 <data> 将一个指定内容和机器可读的翻译联系在一起 <time> 表示机器可读的 24 小时制的时间或者公历日期 <progress> 显示一项任务的完成进度 <meter> 用来显示已知范围的标量值或者分数值 <main> 呈现了文档的 <body> 或应用的主体部分 output 表示计算或用户操作的结果<iframe> 的改进精确控制 <iframe> 元素的安全级别和期望的渲染举例: sandbox 对呈现在 iframe 框架中的内容启用一些额外的限制条件 srcdoc 支持的浏览器优先使用 srcdoc 代替 src MathML用于描述数学公式、符号的一种标记语言,允许直接嵌入数学公式连通性:能够通过创新的新技术方法进行通信Web Sockets允许在页面和服务器之间建立持久连接,并通过这种方法来交换非 HTML 数据Server-sent events允许服务器向客户端推送事件WebRTC支持在浏览器客户端之间语音 / 视频交流和数据分享的技术浏览器原生支持点对点的分享应用数据和进行电话会议离线 & 存储:能够让网页再客户端本地存储数据并且更高效地离线运行离线资源:应用程序缓存缓存 .manifest 上的资源,离线或资源没有更新时,浏览器会加载缓存的离线资源在线和离线事件 navigator.onLine 返回在线 true 或离线 false online 和 offline 事件 window document document.body 使用 addEventListenerdocument document.body 的 .ononline 或 .onoffline 属性设为一个 JavaScript Function 对象<body> 标签上指定 ononline="..." 或 onoffline="..." 属性WHATWG 客户端会话和持久化存储(又名 DOM 存储)StorageDOM 存储被设计为用户提供一个更大存储量,更安全,更便捷的存储方法代替掉将一些不需要让服务器知道的信息存储到 cookies 里的这种传统方法构造函数 Storage 及其实例seesionStorage 全局对象,维护着页面会话期间有效的存储空间,重新载入或从崩溃中恢复不会丢失localStorage 全局对象,本次持久化存储,隐身模式下关闭浏览器会丢弃IndexedDB用于在客户端存储大量的结构化数据,包括文件 / 二进制大型对象(blobs)使用索引实现对数据的高性能搜索在 Web 应用程序中使用文件File API:可以访问 FileList,包含表示用户所选择的 File 对象name 文件名称,只读字符串,只包含文件名,不包含任何路径信息size 以字节数为单位的文件大小,只读的 64 位整数type 文件的 MIME 类型,只读字符串,当类型不能确定为 ""通过 change 事件访问被选择的文件this.files通过 drogenter dragover drag 的 dataTransfer 的 files 中获取文件列表对象 URL window.URL.createObjectURL() 和 window.URL.revokeObjectURL()多媒体:加快普及 video 和 audio 应用,丰富 web 表现力HTML5 音视频<video> 和 <audio> 标签以及 JavaScript 和 APIs 用于对其进行控制WebRTC支持在浏览器客户端之间语音 / 视频交流和数据分享的技术浏览器原生支持点对点的分享应用数据和进行电话会议Camera API使用手机的摄像头拍照,然后把拍到的照片发送给当前网页Track 和 WebVTT<track> 元素怒被当作媒体元素 <audio> 和 <video> 的子元素WebVTT(Web 视频文本跟踪格式)使用 <track> 元素现实定时文本轨道(如字幕或标题)的格式化,支持 VTTCue 和 VTTRegion 接口2D/3D 绘图 & 效果:提供定制图形、动画界面的新选择Canvas<canvas> 元素被用来通过 JavaScript (Canvas API 或 WebGL API)绘制图形及图形动画HTML5 文本 API 由 <canvas> 支持fillText(text, x, y, [, maxWidth]) 在指定的 (x, y) 位置填充指定的文本strokeText(text, x, y, [, maxWidth] 在指定的 (x, y) 位置绘制文本边框WebGLWebGL (Web 图形库) 是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,无需使用插件WebGL 引入 OpenGL ES 2.0,通过 canvas.getContext('webgl') 使用WebGL 2 引入 OpenGL ES 3.0,通过 canvas.getContext('webgl2') 使用SVGSVG (可缩放矢量图形)是一种描述二维的矢量图形,基于 XML 的标记语言优雅而简洁地渲染不同大小的图形,并和 CSS,DOM,JavaScript 和 SMIL 等其他网络标准无缝衔接可以搜索、索引、编写脚本和压缩,也可以使用任何文本编辑器和绘图软件来创建和编辑 SVG性能 & 集成:提供作用显著的性能优化方案,更有效地使用设备硬件Web Workers为 Web 内容在后台线程中运行脚本提供一种简单方法线程可以执行任务而不干扰用户界面专用 workernew Worker() 构建通过 postMessage() 和 onmessage 事件函数发送和接收消息共享 workernew SharedWorker() 构建通过 port.postMessage() 和 port.onmessage 事件函数发送和接收消息worker 中需先使用 onconnect 获取 portXMLHttpRequest Level 2可以设置 HTTP 请求的时限可以使用 FormData 对象管理表单数据可以上传文件可以请求不同域名下的数据(跨域请求)可以获取服务器端的二进制数据可以获得数据传输的进度信息即时编译的 JavaScript 引擎新一代的 JavaScript 引擎更强大,性能更杰出History APIHistory 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录属性 History.length 返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页 History.scrollRestoration 允许 Web 应用程序在历史导航上显式地设置默认滚动恢复行为。此属性可以是自动的(auto)或者手动的(manual) History.state 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态的方式方法 History.back() 在浏览器历史记录里前往上一页,用户可以点击浏览器左上角的返回按钮模拟此方法,等价于 history.go(-1) History.forward() 在浏览器历史记录中前往下一页,用户可以点击浏览器左上角的前进按钮模拟此方法,等价于 history.go(1) History.go() 通过当前页面的相对位置从浏览器历史记录(会话记录)加载页面 History.pushState() 按指定的名称和 URL(如果提供该参数)将数据 push 进会话历史栈,数据被 DOM 进行不透明处理,你可以指定任何可以被序列化的 JavaScript 对象 History.replaceState() 按指定的数据,名称和 URL(如果提供该参数)更新历史栈上最新的入口。这个数据被 DOM 进行了不透明处理。您可以指定任何可以被序列化的 JavaScript 对象Content EditableHTML 中任何元素都可以被编辑,设置 contenteditable 属性为 true 即可HTML5 将此属性标准化HTML 拖放 APIHTML 拖放(Drag and Drop)接口使应用程序能够在浏览器中私用拖放功能引入拖放功能的基本步骤确定可拖拽元素给元素添加 draggable 属性,添加全局事件处理函数 ondragstart定义拖拽数据通过 drag event 的 dataTransfer 属性访问事件数据通过 dataTransfer 的 setData() 方法为拖拽数据添加一个项通过 dataTransfer 的 setDrageImage 方法定义拖拽图像通过 dataTransfer 的 dropEffect 属性定义拖拽效果 copy 表明拖拽的数据将从它原本的位置拷贝到目标的位置 move 表明被拖拽的数据将被移动 link 表明拖拽源位置和目标之间将会创建一些关系表格或是连接确定放置区域给元素添加 ondragover 和 ondrop 事件处理程序属性定义放置效果通过 dataTransfer 的 dropEffect 属性定义拖拽效果拖拽结束拖拽操作结束时,在源元素(开始拖拽时的目标元素)上触发 dragend 事件不管拖拽是完成还是取消,这个事件都会被触发HTML 焦点管理DOM 属性 activeElement 与方法 hasFocus() 为程序按提供了更好的控制页面交互的能力,特别是丢与用户行为引发的交互activeElement 只读属性,用来返回当前在 DOM 或者 shadow DOM 树中处于聚焦状态的 ElementDocument.hasFocus() 方法返回一个 Boolean,表明当前文档或者文档内的节点是否获得了焦点。该方法可以用来判断当前文档中的活动元素是否获得了焦点两者关系获得焦点的元素一定是当前文档的活动元素一个文档中的活动元素不一定获得了焦点基于 Web 的协议处理程序使用 navigator.registerProtoolHandler(scheme, url, title) 方法把 web 应用程序注册成一个协议处理程序requestAnimationFrame传入一个回调函数,该回调函数会在浏览器下一次重绘之前执行全屏 API全屏 API 为使用用户的整个屏幕展现网络内容提供了一种简单的方式,不需要时退出全屏模式方法Document.exitFullscreen() 用于请求从全屏模式切换到窗口模式,会返回一个 Promise,会在全屏模式完全关闭的时候,被重置为 resolved 状态Element.requestFullscreen() 请求浏览器将特定元素置为全屏模式,隐去屏幕上的浏览器所有 UI 元素,以及其它应用属性DocumentOrShadowRoot.fullscreenElement fullscreenElement 属性提供了当前在 DOM(或者 shadow DOM)里被展示为全屏模式的 Element,如果这个值为 null,文档不处于全屏模式Document.fullscreenEnabled fullscreenEnabled 属性提供了启用全屏模式的可能性。当它的值是 false 的时候,表示全屏模式不可用事件处理程序Document 事件处理程序 onfullscreenchange 和 onfullscreenerrorElement 事件处理程序 onfullscreenchange 和 onfullscreenerror指针锁定 API 光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件 指针锁定是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或者用户使用一个专门的释放手势 指针锁定不局限于浏览器或者屏幕边界 指针锁定持续发送事件,而不管鼠标按钮状态如何 指针锁定隐藏光标 指针锁定目前需要先进入全屏模式 requestFullscreen() 然后执行 requestPointerLock() 方法在线和离线事件navigator.onLine 返回在线 true 或离线 falseonline 和 offline 事件window document document.body 使用 addEventListenerdocument document.body 的 .ononline 或 .onoffline 属性设为一个 JavaScript Function 对象<body> 标签上指定 ononline="..." 或 onoffline="..." 属性设备访问 :能够处理各种输入和输出设备Camera API 使用手机的摄像头拍照,然后把拍到的照片发送给当前网页触摸事件 触摸事件提供了在触摸屏或触控板商解释手指(或触控笔)活动的能力 触摸事件接口可为程序提供多点触控交互的支持,分为开始、移动、结束三个阶段 接口TouchEvent 接口将当前所有活动的触摸点封装起来Touch 接口表示单独一个触摸点,其中包括浏览器视角的相对坐标TouchList 表示一组 Touch,用于多点触控的情况使用地理位置定位地理位置 API 允许用户向 Web 应用程序提供他们的位置出于隐私考虑,报告地理位置和前会先请求用户许可方法,通过 navigator.geolocation 提供getCurrentPosition(success[, error[, options]]) 用来获取设备当前位置watchPosition(success[, error, options]]) 用于注册监听器,在设备的地理位置发生改变的时候自动被调用,返回一个 idclearWatch(id) 清除注册的位置及错误监听器检测设备方向DeviceOrientationEvent 它会在加速度传感器检测到设备在方向上产生变化时触发DeviceMotionEvent 它会在加速度发生改变时触发指针锁定 API 光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件 指针锁定是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或者用户使用一个专门的释放手势 指针锁定不局限于浏览器或者屏幕边界 指针锁定持续发送事件,而不管鼠标按钮状态如何 指针锁定隐藏光标 指针锁定目前需要先进入全屏模式 requestFullscreen() 然后执行 requestPointerLock() 方法 样式设计:支持创作更复杂的主题什么是 CSS3?自 CSS2.1 后,CSS 标准被拆解成多个模块,每个模块有自己的版本并独立更新CSS3 泛指这些模块的总和,作为 CSS 的第 3 版本的 CSS3 事实上已不存在* 什么是 CSS3 新特性?CSS 标准的各个模块都在快速更新,其中已经进入到候选推荐、建议推荐和推荐 的模块被称为稳定模块稳定模块中新增的特性大多已获得浏览器广泛支持,使用不需要加私有前缀这里的新特性通常指这些模块中新增的标准* CSS3 新特性有哪些,举例说明? * 颜色模块 * 新增 `opacity` 属性 * 新增 `hsl()` `hsla()` `rgb()` `rgba()` 方法 * 新增 颜色关键字 currentColor * 定义 `transparent` 为 `rgb(0, 0, 0, 0.0)` * 选择器模块 * 新增 属性选择器:`[attribute^="value"]` `[attribute$="value"]` `[attribute*="value"]` * 新增 伪类:`:target` `:enabled` `:disabled` `:checked` `:indeterminate` `:root` `:nth-child` `:nth-last-child` `:nth-of-type` `:nth-last-of-type` `:last-child` `:first-of-type` `:last-of-type` `:only-child` `:only-of-type` `:empty` `:not` * 新增 普通兄弟选择器:`~` * 规范 伪元素表示为两个冒号:`::after` `::before` `::first-letter` `::first-line` * 命名空间模块 新增 @规则:`@namespac` * 媒体查询模块 * 支持更多媒体查询条件:`tv` `color` `resolution`等 * 支持 `<link>` 标签的媒体查询 * 背景和边框模块 * 支持渐变 `linear-gradient`背景 * 支持多背景图片 * 新增 `background-origin` `background-size` `background-clip` * 新增 圆角边框:`border-radius` * 新增 边框图片:`border-image` * 新增 边框阴影:`box-shadow` * 多列布局模块 * 支持多列布局:`columns` `column-count` `column-width` 等 * 值和单位模块 * 新增 想对长度单位:`rem` `ch` `vw` `vh` `vmax` `vmin` * 新增方法:`calc()` * 弹性盒布局模块 * 支持弹性布局:`dispaly:flex` `flex-direction` `flex-wrap` 等 * 文本装饰模块 * 新增 着重符号:`text-emphasis` * 新增 文本阴影:`text-shadow` * 过渡和动画模块(草案) * 新增 过渡效果:`transition` `transition-delay` `transition-duration` `transition-propery` 和 `transition-timing-function` 属性来支持定义两个属性值间的 transitions effects(过渡效果) * 新增 动画效果:`animation` `animation-delay` `animation-direction` `animation-duration` `animation-fill-mode` `animation-iteration-count` `animation-name` `animation-play-state` 和 `animation-timing-function` 属性,以及 `@keyframes` @规则
0
0
0
浏览量2019
懒人学前端

6.多媒体及嵌入

一、如何插入音频?IE 浏览器,早期用非标准属性 <bgsound> 设置网页背景音乐,只支持 .wav .au 和 .midFlash 支持.mp3 .flv .f4v rtmp 和 m3u8,用于 Web 播放音视频HTML5 使用<audio> 元素用于在文档中嵌入音频内容可以使用内嵌 <source> 提供不同播放源设置 type属性,避免消耗大量时间和资源让浏览器尝试加载浏览器会使用第一个支持的格式二、如何插入视频 ?Flash 支持.mp3 .flv .f4v rtmp 和 m3u8,用于 Web 播放音视频HTML5 使用 <video> 元素用于在文档中嵌入视频内容可以使用内嵌 <source> 提供不同播放源设置 type属性,避免消耗大量时间和资源让浏览器尝试加载浏览器会使用第一个支持的格式<video> 标签支持 width / height 属性<video> 标签支持 poster 属性设置缩略图三、有哪些标签可以嵌入外部内容 ?可以嵌入外部内容的标签包括:<link> 外部 CSS,Favicon.ico<script> 外部 JavaScript<img> 外部图像<audio> <bgsound> 外部音频<video> 外部视频<iframe> 嵌入外部网页<embed> 嵌入插件<object> 嵌入插件可以嵌入外部内容的标签,通常被用来解决跨域问题四、兼容性较好的视频、音频格式分别是 ?视频MPEG-4 即 MP4容器格式 MP4 支持流媒体 MP4支持 MPEG-2、MPEG-4、HEVC、H.265、H.264 和 H.263 视频编码 MP4 支持 AAC、MPEG-1、Layers Ⅰ、Ⅱ、Ⅲ 和 AC-3 等音频编码 所有现代浏览器、移动端浏览器和 Internet Explorer 都支持WebM WebM 支持流媒体 WebM 支持 VP8 和 VP9 视频编码 WebM 支持开源的 Vorbis 和 Opus 音频 所有现代浏览器都支持音频MPEG Audio Layer 3 即 MP3 MP3 利用 MPEG Audio Layer 3 技术,将音乐以 1:10 至 1:12 压缩率压缩成小文件 绝大多数浏览器,包括 IE9 +,除老版本的 Firefox、Opera 外都支持五、如何使媒体文件支持不同平台,不同设备的浏览器 ?使用 <source> 标签用于为图片 <picture> 音频 <audio> 和视频 <video> 指定多个媒体资源设置 type 属性声明资源的 MIME 类型,增加资源的备选类型图像,优先现代图片格式,如 webp 或 avif 使用 jpg 或 png托底视频,优先 webm 使用 mp4 托底音频,优先 mp3 使用 ogg 兼容老版本 Firefox,使用 wav 兼容老版本 Opera使用 替换内容,如图片的 <img> 标签, <audio> 和 <video> 嵌入 Flash 兼容老浏览器六、如何为视频插入字幕 ?使用 <track> 标签设置 src 属性,声明视频字幕的地址引用 WebVTT 格式 或者 TTML 时序文本标记语言格式的字幕地址设置 kind 属性为 subtitles。subtitles 是默认值,这步可省略设置 srclang 属性,从合法 BCP 47 语言标签中选择一种声明字幕语言设置 label 属性,用户可读可选择语种七、如何为视频设置缩略图 ?设置 <video> 的 poster 属性提供视频的缩略图地址缩略图将在视频播放或跳帧前显示未指定缩略图,在第一帧可用前,什么都不显示八、为什么设置 `autoplay` 的视频无法自动播放 ?移动端普遍按流量付费,所以移动端浏览器在用户首次打开视频时忽略 autoplay的自动播放属性弹出确认框,询问用户是否使用流量播放视频自动播放策略限制始终允许静音视频自动播放以下情况允许自动播放声音 用户与域进行了交互(单击、点击等) 桌面上,用户的媒体参与指数阈值已被超过,即用户之前曾播放过有声视频 用户已将站点添加到其移动设备主屏幕或在桌面设备上安装了 PWA上层框架可以将自动播放权限委托给它们的 iframe,允许自动播放声音策略影响版本音视频元素从 Chrome 66 起受自动播放策略限制网络音频从 Chrome 71 起受自动播放策略限制 如果 AudioContext 在文档接收到用户手势之前创建,AudioContext.state 为 suspended,需在用户手势之后调用 resume() 开始播放 用户主动点击节点,调用 start() 开始播放
0
0
0
浏览量2017
懒人学前端

第十四章:手写new

第一参数作为构造函数,其余参数作为构造函数参数继承构造函数原型创建新对象执行构造函数结果为对象,返回结果,反之,返回新对象function myNew(...args) { const Constructor = args[0] const o = Object.create(Constructor.prototype) const res = Constructor.apply(o, args.slice(1)) return res instanceof Object ? res : o } // 使用 function P(v) { this.v = v } const p = myNew(P, 1) // P {v: 1}
0
0
0
浏览量665
懒人学前端

2.元素—上

一、什么是 HTML 标签 ?HTML 超文本标记语言标记标签通常被称为 HTML 标签HTML 标签是 HTML 语言中最基本单位和重要组成部分HTML 标签不区分大小写,从一致性、可读性等方面来说,最好仅使用小写字母HTML 标签以尖括号( <> )开始和结束通常成对出现,分别是开始标签和结束标签,也可以称为开放标签和闭合标签自闭合标签只有其本身,在开始标签中自动闭合二、HTML 标签区分大小写吗 ?HTML 标签不区分大小写输入标签时,既可以使用大写字母,也可以使用小写字母从一致性、可读性等方面来说,最好仅使用小写字母三、什么是 HTML 元素 ?HTML 元素是指从开始标签到结束标签的所有代码开始标签:包含元素的名称,被左、右角括号所包围。表示元素从这里开始或者开始起作用结束标签:与开始标签相似,只是其在元素名之前包含了一个斜杠,表示着元素的结尾内容:元素的内容元素:开始标签、结束标签与元素相结合,便是一个完整的元素四、HTML 元素有哪些分类方法 ?按闭合特征分类双标签元素:开始标签和结束标签成对,中间包括内容单标签元素:空元素,开始标签自动闭合,没有内容按显示方式分类行内元素(内联元素)只占据它对应标签的边框所包含的空间块级元素占据其父元素(容器)的整个空间通常浏览器会在块级元素前后另起一行块级元素可以包含行内元素和其他块级元素按 HTML5 规范文档(HTML-conformant document)分类主内容类:描述了很多元素共享的内容规范元数据内容(Metadata content):此类元素修改文档的其余部分的陈述或者行为,建立与其他文档的链接,或者传达其他外带信息举例: <base> <link> <meta> <noscript> <script> <style> <title>流式元素(Flow content):此类元素同行包含文本或植入的内容举例:   <a> <abbr> <address> <article> <aside> <audio> <b> <bdo> <bdi> <blockquote> <br> <button> <canvas> <cite> <code> <data> <datalist> <del> <details> <dfn> <div> <dl> <em> <embed> <fieldset> <figure> <footer> <form> <h1> <h2> <h3> <h4> <h5> <h6> <header> <hgroup> <hr> <i> <iframe> <img> <input> <ins> <kbd> <label> <main> <map> <mark> <math> <menu> <meter> <nav> <noscript> <object> <ol> <output> <p> <pre> <progress> <q> <ruby> <s> <samp> <script> <section> <select> <small> <span> <strong> <sub> <sup> <svg> <table> <template> <textarea> <time> <ul> <var> <video> <wbr>  等和 Text以下元素仅限于某种特殊情况,属于此类<area> 仅限于它作为 <map> 的子元素时<link> 仅限于 itemprop 属性存在的情形<meta> 仅限于 itemprop 属性存在的情形<style> 仅限于 scoped 属性存在的情形章节元素(Heading content):隶属于分节内容模型的元素,再当前的大纲中创建一个分节。此分节将定义 <header> 元素、 <footer> 元素和标题元素的范围举例: <article> <aside> <nav> 和 <section>标题元素(Heading content):定义了分节的标题,而这个分节可能由一个明确的分节内容元素直接标记,也可能由标题本身隐式地定义举例: <h1> - <h6> 和 <hgroup>短语元素(Phrasing content):规定文本和它包含的标记,一些短语元素构成段落举例:   <abbr> <audio> <b> <bdo> <br> <button> <canvas> <cite> <code> <datalist> <dfn> <em> <embed> <i> <iframe> <img> <input> <kbd> <label> <mark> <math> <meter> <noscript> <object> <output> <progress> <q> <ruby> <samp> <script> <select> <small> <span> <strong> <sub> <sup> <svg> <textarea> <time> <var> <video> <wbr> 和 plain text以下元素仅限于某种特殊情况,属于此类<a> 仅限于它包含 phrasing content 时<area> 仅限于它作为 <map> 的子元素时<del> 仅限于它包含 phrasing content 时<ins> 仅限于它包含 phrasing content 时<link> 仅限于 itemprop 属性存在的情形<map> 仅限于它包含 phrasing content 时<meta> 仅限于 itemprop 属性存在的情形嵌入元素(Embedded content):输入另一个资源或者将来自另一种标记语言或命名空间的内容插入到文档中举例: <audio> <canvas> <embed> <iframe> <img> <math> <object> <svg> <video>交互元素(Interactive content):交互式内容包含为用户交互而特别设计的元素举例: <a> <button> <details> <embed> <iframe> <label> <select> 和 <textarea>以下元素仅限于某种特殊情况,属于此类<audio> 仅限于 controls 属性存在<img> 仅限于 usemap 属性存在<input> 仅限于 type 属性不处于隐藏(hidden)状态<menu> 仅限于 type 属性处于工具栏(toolbar)状态<object> 仅限于 usemap 属性存在<video> 仅限于 controls 属性存在表单相关内容类:描述了表单相关元素共有的内容规范可列举的元素(listed)举例: <button> <fieldset> <input> <object> <output> <select> 和 <textarea>可标签的元素(labelable)可以和 <label> 相关联的元素举例: <button> <input> <meter> <output> <progress> <select> 和 <textarea>可提交的元素(submittable)包括当表单提交时,可以用来组成表单数据的元素举例: <button> <input> <object> <select> 和 <textarea>可重置的元素(resettable)当表单重置时会被重置的元素举例: <input> <output> <select> 和 <textarea>特殊内容类:描述了仅在某些特殊元素商才需要遵守的规范,通常这些元素都有特殊的上下文关系支持脚本元素:不会直接渲染输出在页面文档中。被用来存放脚本代码及脚本代码所要用到的数据举例: <script> <template>透明内容模型元素(Transparent content model)如果一个元素拥有透明内容模型,将透明标签删除,依然是合法的 HTML5 元素举例: <del> <ins>五、什么是 HTML 头部元素 ?HTML 头部元素,即 <head> 元素HTML 头部元素的内容不会在浏览器中显示HTML 头部元素的作用是保存页面的标题、元数据六、什么是元数据 ?元数据(Metadata),简单的来说就是描述数据的数据HTML 文件在头部元素,即 <head> 标签中包含描述该文档的元数据HTML 元数据通常使用 <meta> 标签表示,共有 4 种类型 如果设置了 name 属性meta 元素提供的是文档级别(document-level)的元数据,应用于整个页面meta 指定了元素的类型,说明该元素包含了什么类型的信息与 content 一起使用,后者指定实际的元数据内容,用来添加 author description 用于提交作者、摘要和 SEO 如果设置了 http-equiv 属性meta 元素则是编译指令,提供的信息与类似命名的 HTTP 头部相同content-security-policy允许页面作者定义当前页的内容策略指定允许的服务器源和脚本,有助于防止跨站点脚本攻击(XSS)content-type用于声明文档类型,如 text/html; charset=utf-8default-style设置默认 CSS 样式表组的名称content 属性的值必须匹配同一文档中一个 link 元素上的 title 属性的值x-ua-compatiblecontent 属性必须为 IE=edgerefreshcontent 只包含一个正整数,则为重新载入页面的时间间隔(秒)content 包含一个正整数,并且后面跟着字符串 ;ulr= 和一个合法的 URL,则是重定向到指定链接的时间间隔(秒) 如果设置了 charset 属性,meta 元素是一个字符集声明,告诉文档使用哪种字符编码值与 ASCII 大小写(ASCII case-insensitive)无关,如 utf-8如果设置了 itemprop 属性,meta 元素提供用户定义的元数据content 属性对应用户定义的值,可用于数据标记和结构化数据提交property 属性通常与 itemprop作用一致,如 Facebook 编写的元数据协议(Open Graph protocol)使用 property 声明属性名七、什么是 Open Graph protocol?元数据协议(Open Graph Data)由 Facebook 编写制定的 Metatags 规格,用来标注页面帮助社交媒体、搜索引擎高效、准确地获取网页的标题、主图及元数据使得网页在社交分享及搜索结果中有更好的展现除网页外,还可以用于声明将音乐、视频、文章、书籍、用户信息等转换为图形对象应用元数据协议,需要在页面添加 <meta> 标签放在网页的 <head> 中,其中包括:基本元数据og:title - 标题og:type - 对象类型og:image - 图像 URLog:url - 对象 URL可选元数据og:audio - 音频文件 URLog:description - 描述og:determiner - 出现在对象标题前的单词,可选 a / an / the / auto,默认为空og:locale - 语言环境,格式为 language_TERRITORY,默认为 en_USog:locale:alternate - 页面可用的其他语言环境数组og:site_name - 网站名称,如 Facebookog:video - 视频 URL结构化属性某些属性可以附加额外的元数据og:imageog:image:url - 图像 URLog:image:secure_url - HTTPS 下的图像 URLog:image:type - 图像 MIME 类型og:image:width - 图像宽度og:image:height - 图像高度og:image:alt - 图像内容描述(不是标题)og:videoog:video:url - 视频 URLog:video:secure_url - HTTPS 下的视频 URLog:video:type - 视频 MIME 类型og:video:width - 视频宽度og:video:height - 视频高度og:audioog:audio:url - 音频 URLog:audio:secure_url - HTTPS 下的音频 URLog:audio:type - 音频 MINE 类型
0
0
0
浏览量426
懒人学前端

5.核心概念-Plugin—中

三、DllPluginDllPluginDllPlugin 和 DllReferencePlugin 用某种方法实现了拆分 bundles,同时还大幅度提升了构建的速度。"DLL" 一词代表微软最初引入的动态链接库。通过使用 DllPlugin 可以拆分项目中版本不经常变更的或者包大小比较大的三方依赖。将依赖拆分成单独的 bundles,这样可以提升项目打包速度、减少包体积来加快加载速度。使用本章中主要介绍 DllPlugin 的使用及配置,由于需要打包第三方依赖库,所以下面以 lodash 为例。安装 lodashnpm install loadsh --save-devwebpack.config.dll.js新建一个 webpack.config.dll.js 文件,用来添加 dll 配置,在执行打包命令之前先生成 dll 文件。const webpack = require('webpack'); const path = require('path'); module.exports = { entry: { lodash: ['lodash'] }, output: { filename: '[name]_dll.js', library: "[name]", path: path.resolve(__dirname, 'dist/dll') }, plugins: [ new webpack.DllPlugin({ context: __dirname, path: path.join(__dirname, 'dist/dll', '[name]-mainfest.json'), name: '[name]' }), ] }在上面的配置中,entry 为项目入库文件,output 为项目输出文件名称及输出目录。其中要注意的是 output 中的 library 参数,增加 library 配置后 mainfest.json 会暴露 dll 库的全局名称。此参数定义的名称要与 DllPlugin 插件中的 name 属性定义名称一致。在命令行执行 npx webpack --config webpack.config.dll.js 命令,打包成功后在 dist 文件下可以看到生成了一个 dll.js 一个 mainfest.json 文件。配置context默认值:webpack 的 context值类型:String作用:生成的 manifest.json 文件中 content 对象中的文件映射地址。webpack.config.dll.jsmodule.exports = { plugins: [ new webpack.DllPlugin({ // {"name":"vendors","content":{"./node_modules/lodash/lodash.js":{"id":486,"buildMeta":{}}}} context: __dirname // 根目录地址 // 下面的 mainfest.json 文件将找不到 lodash.js,映射地址错误 // {"name":"vendors","content":{"../node_modules/lodash/lodash.js":{"id":486,"buildMeta":{}}}} // context: 'src' }), ] };format默认值:false值类型:Boolean作用:如果值为 true,生成的 mainfest.json 文件将被格式化。webpack.config.dll.jsmodule.exports = { plugins: [ new webpack.DllPlugin({ // {"name":"vendors","content":{"./node_modules/lodash/lodash.js":{"id":486,"buildMeta":{}}}} format: false, // { // "name": "vendors", // "content": { // "./node_modules/lodash/lodash.js": { // "id": 486, // "buildMeta": {} // } // } // } format: true }), ] };name默认值:''值类型:String作用:生成的 mainfest.json 文件中的 name 属性值。webpack.config.dll.jsmodule.exports = { ... plugins: [ new webpack.DllPlugin({ // {"name":"lodash-name","content":{"./node_modules/lodash/lodash.js":{"id":486,"buildMeta":{}}}} name: "lodash-name" }) ] }; path默认值:无(必须)值类型:String作用:生成 mainfest.json 文件的路径。webpack.config.dll.jsmodule.exports = { ... plugins: [ new webpack.DllPlugin({ // 在根目录的 dist/dll 文件夹下生成 [name]-mainfest.json path: path.join(__dirname, 'dist/dll', '[name]-mainfest.json'), }) ] };entryOnly默认值:true值类型:Boolean作用:如果为 true,则仅暴露入口。webpack.config.dll.jsmodule.exports = { ... plugins: [ new DllPlugin({ entryOnly: false }) ] };建议 DllPlugin 只在 entryOnly: true 时使用,否则 DLL 中的 tree shaking 将无法工作,因为所有 exports 均可使用。type类型:"var" | "module" | "assign" |"assign-properties" | "this" | "window" | "self" | "global" | "commonjs" | "commonjs2" | "commonjs-module" |"commonjs-static" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system"作用:配置 dll 库暴露的方式。webpack.config.dll.jsmodule.exports = { ... plugins: [ new DllPlugin({ // {"name":"lodash-name","type":"window","content":{"./node_modules/lodash/lodash.js":{"id":486,"buildMeta":{}}}} type: 'window' }) ] };总结此章节演示了 DllPlugin 的用法和包含的配置参数。生成的 dll.js 和 mainfest.json 文件,在下个 DllReferencePlugin 章节来展示如何使用。四、DllReferencePluginDllReferencePlugin在上一章节的 DllPlugin 插件中,DllPlugin 插件会将第三方依赖生成一个 [name]-dll.js 文件和一个 [name]-manifest.json 文件,其中 [name]-dll.js 文件包含第三方依赖所有的源码,[name]-manifest.json 文件包含所有第三方依赖的名称位置和与 [name]-dll.js 映射的索引 ID,执行相应命令生成了 [name]-dll.js 和 [name]-manifest.json 后,就可以执行工程的打包命令。在 webpack 打包过程中,通过 DllReferencePlugin 插件,可以将 [name]-manifest.json 文件中的第三方依赖的映射关系进行解析,当检索到有第三方依赖的映射关系时,会将三方依赖索引 ID 值注入到使用依赖的文件中。如果没有检索到三方依赖映射关系,将去 node_modules 或者项目中查找并将检索内容添加到需要使用依赖的文件中。使用index.jsimport _ from 'lodash'; import { name } from './main.js'; console.log(_.join(['hello', 'world'], '-')) console.log(name);main.jsexport const name = 'dll';webpack.config.dll.jsconst webpack = require('webpack'); const path = require('path'); module.exports = { entry: { vendors: ['lodash', './src/main.js'], }, output: { filename: '[name]-dll.js', library: "[name]", path: path.resolve(__dirname, 'dist/dll') }, plugins: [ new webpack.DllPlugin({ context: __dirname, path: path.join(__dirname, 'dist/dll', '[name]-manifest.json'), name: '[name]', format: true }), ] }在上面的配置中,将 lodash 和本地 main.js 生成 dll 文件。webpack.config.jsconst path = require('path'); const webpack = require('webpack'); module.exports = { mode: 'development', entry: { index: './src/index.js', }, output: { path: path.resolve(__dirname, "dist"), filename: '[name].js' }, plugins: [ new webpack.DllReferencePlugin({ // 对动态链接库的文件内容的描述或者映射关系,非 dll 本身 manifest: require('./dist/dll/vendors-manifest.json'), }) ] }在 webpack.config.js 的 DllReferencePlugin 插件中引入 DllPlugin 插件生成的 manifest.json 文件。执行 npm run build 打包命令。我们看下生成的 index.js 文件内容。从上面的截图我们可以看到 DllReferencePlugin 插件解析了 manifest.json 中的第三方依赖对应的映射关系,将 ID 值传入到 index.js 的方法中,我们接下来需要手动将 dll/vendors-dll.js 通过 <script> 标签添加到 index.html 中。vendors-dll.js 文件中包含第三方依赖的代码和 manifest.json 中的 ID 值,index.js 中通过加载 ID 值来使用第三方依赖方法。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Webpack App</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script defer src="./dll/vendors-dll.js"></script> <script defer src="index.js"></script></head> <body> </body> </html>在浏览器中打开 index.html 文件,在控制台可以看到输出的内容,main.js 和 lodash 方法均可正常使用。DllReferencePlugin 在解析 manifest.json 文件时,虽然 manifest.json 包含多个第三方依赖的映射关系,但是插件会根据 index.js 中的引用来注入对应的代码。即当 index.js 中只引用了 main.js 文件时,打包后生成的 index.js 文件则只包含 main.js 的映射关系。在实际的项目中,就可以将不常改动的第三方依赖打包成 dll,将 dll 的链接通过 CDN 等方式引入到项目中,这样可以减少主包的大小,加快打包速度。注意:[name]-dll.js 可以通过插件动态插入到 index.html,小伙伴儿可自行查阅。配置context类型:String默认值:webpack 的 context作用:(绝对路径) manifest 中请求的上下文。webpack.config.jsmodule.exports = { plugins: [ new webpack.DllReferencePlugin({ ... context: __dirname // 根目录地址 // 下面的配置会在 dist 文件夹下查找 manifest 的地址,当找不到 manifest.json 文件时,会将依赖代码打包到 bundle.js 中 // path.resolve(__dirname, "dist") }), ] }; manifest类型:String作用:用于加载 manifest.json 路径。webpack.config.jsmodule.exports = { plugins: [ new webpack.DllReferencePlugin({ // 动态链接库的文件内容 manifest: require('./dist/dll/vendors-manifest.json'), }), ] }; name类型:String默认值:manifest.json 的 name 属性。作用:[name]-dll.js 暴露的全局变量方法名称,将 manifest.json 中的 索引 ID 传入全局方法中,可返回对应模块方法。index.js其中的 vendors(225),vendors 为 dll 暴露的全局变量,225 为 manifest.json 中 main.js 依赖的 ID 索引值。import _ from 'lodash'; import { name } from './main.js'; console.log(_.join(['hello', 'world'], '-')) console.log(vendors(225)); console.log(vendors(225).plugin);main.jsexport const plugin = 'dll';webpack.config.jsmodule.exports = { ... plugins: [ new webpack.DllReferencePlugin({ ... name: 'vendors' }) ] };打包成功后,控制台打开 dist/index.html,可以看到 输出了 hello-world、 main.js 模块代码和 main.js 代码中的 plugin 变量。content类型:Object默认值:manifest.json 的 content 属性。作用:传入请求依赖到模块 ID 的映射关系。webpack.config.jsmodule.exports = { ... plugins: [ new webpack.DllReferencePlugin({ content: { "./src/main.js": { "id": 225, "exports": [ "plugin" ] }, } }) ] };sourceType类型:"var" | "assign" | "this" | "window" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system"作用:dll 中的模块代码以哪种模式暴露给外部调用 output.library.typeindex.jsimport { name } from './main.js'; console.log(window.vendors(225));webpack.config.jsmodule.exports = { ... plugins: [ new HtmlWebpackPlugin({ ... sourceType: 'window' }) ] };总结此章节演示了 DllReferencePlugin 的用法和包含的配置参数。DllReferencePlugin 通过解析 manifest.json 将依赖的映射索引注入到需要的依赖中。通过将不常变动的依赖打包成 dll 的方式,可以减少 bundle 的大小,同时能加快打包速度。
0
0
0
浏览量647
懒人学前端

项目题全解:井字棋—上

Step 1:用类组件实现井字棋游戏Step 1.1:搭建本地开发环境基础知识:1、安装 Node.js。2、使用下面的指令创建一个新的项目npx create-react-app my-app3、删除掉新项目中 src/ 文件夹下的所有文件。注意:不要删除整个 src 文件夹,删除里面的源文件。我们会在接下来的步骤中使用示例代码替换默认源文件。 基础步骤:第一步:同函数式组件的搭建方式,如下是搭建完成的页面路径。第二步:在 src/文件夹中创建一个名为 index.css 的文件,并拷贝这些 CSS 代码。body { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .board-row:after { clear: both; content: ""; display: table; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; }第三步:在 src/ 文件夹下创建一个名为 index.js 的文件,并拷贝这些 JS 代码。import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { render() { return ( <button className="square">{ }</button> ); //return后面的内容加括号,防止VScode等编辑器自动在return后面加分号 } } class Board extends React.Component { renderSquare(i) { return <Square />; } render() { const status = 'Next player:X'; return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )效果展示:Step 1.2:通过 Props 传递数据基础知识:1、通过阅读代码,我们可以看到有三个 React 组件:Square渲染了单独的<button>Board渲染了9个方块Game渲染了含有默认值的一个棋盘2、render 方法React 根据描述把结果展示出来render 方法的返回了一个 React 元素上面的代码使用了 JSX 语法糖3、props 数据传递数据通过 props 的传递,从父组件流向子组件基础步骤:第一步:修改代码,将数据从 Board 组件传递到 Square 组件在Board组件的renderSquare方法中,改写代码,将名为value的 prop 传递到Square中class Board extends React.Component { renderSquare(i) { return <Square value={i} />; } }第二步:修改Square组件的render方法,在 button 中加入内容class Square extends React.Component { render() { return ( <button className="square"> {this.props.value}</button> ); //return后面的内容加括号,防止VScode等编辑器自动在return后面加分号 } }效果展示:基础代码:index.cssbody { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .board-row:after { clear: both; content: ""; display: table; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; }index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { render() { return ( <button className="square"> {this.props.value}</button> ); //return后面的内容加括号,防止VScode等编辑器自动在return后面加分号 } } class Board extends React.Component { renderSquare(i) { return <Square value={i} />; } render() { const status = 'Next player:X'; return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )Step 1.3:组件交互基础步骤:这一步的目标是让棋盘的在点击之后每个格子能落下”X"作为棋子第一步:Square 组件中 render() 方法的返回值中的 button 标签第二步:增加 onClick 属性class Square extends React.Component { render() { return ( <button className="square" onClick={() => alert('click')}>{this.props.value}</button> ); } }Step 1.4:Square 组件实现“记忆”功能基础知识:React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。通过 state 来实现“记忆”功能,在 React 组件的构造函数中设置 this.state 初始化 statethis.state 应被视为一个组件的私有属性。在 this.state 中存储当前每个方格(Square)的值,并且在每次方格被点击的时候改变这个值。基础步骤:第一步:在 Square 中用构造函数来初始化 stateclass Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; } render() { return ( <button className="square" onClick={() => alert('click')}>{this.props.value}</button> ); } }第二步:修改 Square 组件的 render 方法,实现每当方格被点击时显示当前 state 值。在onClick事件监听函数中调用this.setStateclass Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; } render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})} > {this.state.value} </button> ); } }这样子就能实现:在每次<button>被点击的时候通知React去重新渲染Square组件组件更新后,Square组件的this.state.value的值会变为'X'每次在组件中调用setState,React都会自动更新子组件效果展示:点击变成 x基础代码:index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; } render() { return ( <button className="square" onClick={() => this.setState({ value: 'X' })} > {this.state.value} </button> ); } } class Board extends React.Component { renderSquare(i) { return <Square value={i} />; } render() { const status = 'Next player:X'; return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )Step 1.5:状态提升实现轮流落子基础步骤:第一步:为 Board 组件添加构造函数,将 Board 组件的初始状态设置为长度为 9 的空值数组constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; }第二步:考虑到填充棋盘后,每个格子上可能的形式是null,O,X三种这些我们打算存储到 squares 数组里面,那么就要修改Board的renderSquare方法来读取这些值。renderSquare(i) { return <Square value={this.state.squares[i]} />; }这样,每个Square就都能接收到一个 value prop 了,这个 prop 的值可以是 'X'、 'O'、 或 null(null 代表空方格)。第三步:修改 Square 的事件监听函数。renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); }第四步:从 Board 组件向 Square 组件中传递value和onClick两个props参数,修改 Square 组件。class Square extends React.Component { renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); } }第五步: 在 Board 下添加handleClick方法handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }第六步: 实现轮流落子将"X"默认设置为先手棋,设置一个布尔值来表示下一步轮到哪个玩家棋子每移动一步,xIsNext都会反转,该值确定下一步轮到哪个玩家,并且游戏的状态会被保存下来在构造函数中添加xIsNext修改handleClick函数constructor(props) { super(props); this.state = { squares: Array(9).fill(null), xIsNext: true, }; } handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext? 'X':'O'; this.setState({ squares: squares, xIsNext:!this.state.xIsNext, }); }效果展示:基础代码:index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); } } class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; } handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); } renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { const status = 'Next player:X'; return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )Step 1.6:显示轮到哪个玩家基础步骤:第一步:显示轮到哪个玩家,在 Board 组件的 render 方法中修改 status 的值const status = 'Next player:'+(this.state.xIsNext?'X':'O');效果展示:点击会表面下一个操作的玩家是谁。基础代码index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); } } class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; } handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); } renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { const status = 'Next player:' + (this.state.xIsNext ? 'X' : 'O'); return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )Step 1.7:判断胜出者基础步骤:第一步:定义一个函数:function calculateWinner(squares) { const lines = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6], ]; for(let i = 0 ; i < lines.length ; i++) { const[a,b,c] = lines[i]; if(squares[a]&&squares[a]===squares[b]&&squares[a]===squares[c]){ return squares[a]; } } return null; }第二步:在 Board 组件的 render 方法中调用刚刚那个函数检查是否有玩家胜出,有人胜出就把玩家信息显示出来修改Board组件的render方法const winner = calculateWinner(this.state.squares); let status; if (winner) { status = 'Winner:' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); }效果展示:O 获得了胜利基础代码index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; // 获得胜利的组件 function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; } class Square extends React.Component { renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); } } class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; } handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); } renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { const winner = calculateWinner(this.state.squares); let status; if (winner) { status = 'Winner:' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )Step 1.8:bug 修复已经有人胜出了,但是棋盘还能落子 —— 当某个 Square 落子之后,还能覆盖继续落子。基础步骤:第一步修改handleClick,使得当有玩家胜出时,或者某个 Square 被填充时,该函数不做任何处理直接返回handleClick(i) { const squares = this.state.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext? 'X':'O'; this.setState({ squares: squares, xIsNext:!this.state.xIsNext, }); }效果展示:基础代码index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; // 获得胜利的组件 function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; } class Square extends React.Component { renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); } } class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; } handleClick(i) { const squares = this.state.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); } renderSquare(i) { return (<Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />); } render() { const winner = calculateWinner(this.state.squares); let status; if (winner) { status = 'Winner:' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } return ( <div> <div className="status"> {status} </div> <div className="board-new"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{ }</div> <ol>{ }</ol> </div> </div> ) } } ReactDOM.render( <Game />, document.getElementById('root') )
0
0
0
浏览量2009
懒人学前端

8.表单—下

十六、HTML 下拉框有哪两种实现方式 ?使用 <select> 标签包裹 <option> 标签,每一行是一项代码<select> <option value="选项 1">选项 1</option>   <option value="选项 2">选项 2</option>   <option value="选项 3">选项 3</option> </select>效果使用 <input> 标签使用 <datalist> 标签包裹 <option> 标签,每一行是一项设置 list 属性,关联 <datalist>代码<input type="text" list="select" /> <datalist id="select"> <option value="选项 1">选项 1</option>   <option value="选项 2">选项 2</option>   <option value="选项 3">选项 3</option> </datalist>效果十七、如何构建一个兼顾老版本浏览器的自动补全输入框 ?可以在 <datalist> 标签中嵌入 <select> 和 <option> 标签,当 <datalist> 不被支持时,展示下拉选择框,提供用户手动输入内容的第二选择<label for="colorInput">What is your favorite color ?</label> <input type="text" id="colorInput" list="colorList"> <dataList id="colorList"> <label for="colorSuggestion">or pcik a color</label> <select> <option>Blue</option> <option>Red</option> <option>Orange</option> <option>Green</option> <option>Yellow</option> <option>Pink</option> <option>Purple</option> <option>White</option> </select> </dataList>支持浏览器预览:不支持浏览器预览:十八、如何构建单选框,如何构建复选框 ?单选框设置 type=radio相同 name 的单选框同时只有一个能被选中选中项有 checked 属性,没有一个选中项,不会发送 name 的值默认 value 值为 on复选框5.  设置 type=checkbox6.  相同 name 的复选框表单被提交后,可以获得提交的键名和键值对字符串Python 可以使用 self.request.get('name', allow_multiple = True) 或 self.request.getAll('name') 获取PHP 可以设置 name 值的格式为 name[] ,使用名称 name 获取数组7.  选中项有 checked 属性,没有一个选中项,不会发送 name 的值8.  默认 value 值为 on十九、如何在表单中发送图片被点击时的坐标 ?1.  使用图像按钮元素设置 type=image支持与 <img> 元素的相同属性2.  支持其它表单按钮的支持属性使用图像按钮提交表单不会提交自身值会提交单击处相对于图像左上角的 X 和 Y 坐标以查询字符串的格式跟在 URL 后https://www.leetcode-cn . com/?pos.x=100&pos.y=200二十、支持 `min` 和 `max` 的表单组件有哪些 ?1.  数字选择器 min 和 max 分别设置最小值和最大值type=number 的 <input>2.  滑块选择器 min 和 max 分别设置最小值和最大值type=range 的 <input>3.  日期时间选择器 min 和 max 设置开始时间和结束时间type=datetime-local 的 <input>type=month 的 <input>type=time 的 <input>type=week 的 <input>4.  进度条选择器 <progress>max 指定随时间变化而变化到最大的值5.  仪表选择器 <meter>min 值域的最小边界值,默认为 0max 值域的上限边界值,默认为 1二十一、哪种输入表单适合显示密码强度 ?1.  low 和 high 将范围划分为三个部分较低部分:min 和 low中间部分:low 和 high较高部分:high 和 max2.  optimum 定义 <meter> 元素的最优值optimum 值在较低范围内,较低范围最优,中等范围一般,较高范围最坏optimum 值在中等范围内,较低范围一般,中等范围最优,较高范围最坏optimum 值在较高范围内,较低范围最坏,中等范围一般,较高范围最优3.  <meter> 颜色最优显示为绿色平均显示为黄色最坏显示为红色实现一个密码强度显示器,分别对应 弱、中等、强的密码<p>密码强度指示器</p> <p>弱:<meter min="0" low="33" high="66" max="100" value="30" optimum="100"></meter></p> <p>中等:<meter min="0" low="33" high="66" max="100" value="55" optimum="100"></meter></p> <p>强:<meter min="0" low="33" high="66" max="100" value="88" optimum="100"></meter></p>效果如图所示:二十二、使用 GET 和 POST 发送表单数据,有什么不同 ?根据 rfc-2616 规范,HTTP 协议中的 GET 和 POST 主要是语义上的区别在浏览器的实现及应用中,存在 GET 和 POST 的最佳实践GET 发送表单数据设置 <form> 的 method 属性为 get数据以查询字符串的形式被追加到 URL,参数上限受早期浏览器和 Web 服务器的限制问号 ? 后跟查询字符串符号 & 分隔开的键名键值对默认缓存,受缓存策略控制可回退可收藏参数随 URL 保存在浏览器历史中适用于无副作用,幂等的请求POST 发送表单数据设置 <form> 的 method 属性为 post数据以查询字符串的形式附加到请求体中,参数上限可能受后端脚本限制,如 PHP 通过 max_input_vars 限制最大输入参数上限包含请求行Content-Type: application/x-ww-form-urlencodedContent-Length: {请求体的数据长度}默认不缓存,受缓存策略控制,可声明缓存通常回退会触发重新提交警告,避免回放攻击通常不可收藏参数不随 URL 保存适用于有副作用,非幂等的请求二十三、Form 的 enctype 属性都有哪些值,各自适合什么场景 ?enctype 属性通常用来提交表单的内容类型application/x-www-form-urlencoded 默认值,数据转换为键值对,用于不含文件的表单提交multipart/form-data 使用 <input> 标签上传文件时,必须设置此类型text/plain 表示纯文本形式,HTML5 新增,通常用于调试二十四、什么是表单校验,为什么要使用表单校验 ?(1)什么是表单校验向 Web 应用输入或提交数据时,验证数据的过程就是表单校验正确则允许数据继续提交后端或后台服务失败则提示错误类型、原因、位置或更改建议(2)为什么要进行表单校验 ?确保数据格式正确:引导用户:引导用户输入符合预期的数据保护系统:符合设定类型、位数或具体规则,避免不正确格式数据影响程序运行信息安全保护用户:确保用户设定密码、二次密码、安全问答够复杂,不易被暴力破解,无泄漏减少攻击:经常与后端或后台服务的校验一起应用,减少恶意或伪造的数据提交二十五、表单校验都有哪些类型 ?表单校验可以分为客户端校验和服务器端校验客户端校验校验时机:发生在应用端或浏览器端,表单数据被提交到服务器之前实时性:即时反馈用户输入的校验结果作用:确保数据格式正确,保护信息安全方式: JavaScript:监听输入框输入、失去焦点和提交事件,验证非空(必填),使用 length 校验长度,正则校验格式等,校验通过或失败分别设置不同 class 或 style,失败时,通过禁用提交按钮,阻止 onsubmit 事件,阻止表单继续提交 HTML5 内置校验:通过设置不同的 type 类型,声明 required 必填属性,minlength 和 maxlength 限制长度,min 和 max 限制数字或日期范围,设置 pattern 正则表达式校验输入内容,更改伪类类名的属性自定义校验或失败的提示,性能更好,代码更少,老版本 IE 不兼容 HTML5 内置校验 + JavaScript(constraint validaton API):检测、自定义表单元素状态和错误信息服务器端校验校验时机:应用端或浏览器提交数据并被后端或后台服务接收之后滞后性:需要等待后端或后台服务响应作用:提供验重等需要查询数据库的校验,校验、过滤和清洗数据方式: 通常采用 Ajax 方式,将用户输入数据提交给后端或后台服务,再次校验规则或查询数据库,将校验结果作为响应返回。应用端或浏览器端根据响应结果,更改 class 或设置 style 展示不同的提示二十六、如何使用 HTML5 的内置表单数据校验 ?HTML5 内置表单数据校验通常包括校验属性、约束校验 API 及对应的 CSS 伪类1.校验属性类型校验:设置 type 类型email url 类型限制输入文字必须为邮箱或网址其它非 text 类型,通过控件,触屏端唤起数字键盘等,来限制用户输入类型非空必填校验:声明 required 属性即可长度校验:设置 minlength 和 maxlength 属性限制长度数字 / 日期 / 范围:设置 min 和 max 属性设置数值范围、起止日期、range 范围步长:step 与 min 或 max 其中至少一个一起设置时,限制值的有效性2.  Constraint Validation API 约束验证 API属性  validationMessage 本地化消息描述元素不满足校验条件时的文本信息无需校验或满足校验条件时,为空  validity ValidityState 对象,每一个子项都返回布尔值customError 是否设置了自定义错误patternMismatch 是否匹配正则表达式rangeOverflow 元素的值是否高于所设置最大值rangeUnderflow 元素的值是否低于所设置最小值stepMismatch 元素的值是否符合 step 属性规则tooLong 元素的值是否超过最大长度typeMismatch 元素的值是否出现语法错误valid 元素的值是否有效valueMissing 元素的值是否 required 且为空willValidate 元素的值是否是在表单提交时被校验方法  checkValidity() 校验元素的值是否有效  reportValidity() 元素报告其校验状态,重新展示校验失败提示给用户  setCustomValidity(message) 为元素添加自定义的错误消息3.伪类:valid 有效:invalid 无效:in-range 在范围内:out-of-range 超出范围二十七、使用 JavaScript 发送表单数据都有哪些方法 ?使用 JavaScript 发送表单数据,需要两步骤首先,构造请求参数构建查询字符串使用 ES3const buildParam = data => { const dataPairs = [] for (const key in data) dataPairs.push(encodeURIComponent(key) + encodeURIComponent(data[key])) return dataPairs.join('&').replace(/%20/g, '+') }* 使用 jQueryconst buidParam = form => $(form).serialize()使用 FormData 对象将对象转为 FormData 对象const buildParam = data => { const formData = new FormData() for (const key in data) formData.append(key, data[key]) return formData }* 将表单元素构建 `FormData` 对象const buildParam = form => { const formData = new FormData(form) return formData }然后,发送数据使用 XMLHttpRequest 对象const send = (url, param, cb) => {   const request = new XMLHttpRequest()   request.addEventListener('load', e => cb(null, e => cb(e.target.responseText)))   request.addEventListener('error', e => cb(e.message))   request.open('POST', url) request.send(param) }使用 jQueryconst send = (url, param, cb) => { $.ajax({ url, type: 'post', data: param }).done(data => cb(null, data)).fail((_. textStatus) => cb(textStatus)) }使用 fetch 方法const send = (url, param, cb) => { fetch(url, { method: 'POST', body: param }).then(response => cb(null, response)).catch(error => cb(error)) f }二十八、如何自定义表单元素的样式 ?首先,由于表单元素先于 CSS 被添加到 HTML,早期的渲染依靠底层系统实现,至今,表单元素在不同浏览器,同一浏览器的不同系统版本中,存在不同的默认外观。自定义表单样式的方法:使用 JavaScript + HTML + CSS 重建表单元素,模拟其交互行为。最终通过 UI 组件库的方式供设计团队参考和业务开发调用。保持跨浏览器,跨操作系统的一致性可以自定义所有样式交互、校验、提交等都需要自己完成,代码多,复杂度高JavaScript 出错或禁用,CSS 加载失败,都可能导致表单失效。在稳定性要求高的场景,需要能降级到原生表单元素自定义原生表单元素的样式  原生表单元素按应用 CSS 的难度 ,分为三类容易应用,跨平台不易出问题:<form> <fieldset> <label> <output>难应用,不同平台写不同属性:<legend> checkbox radio placeholder不推荐应用:<select> <option> <optgroup> <datelist> <progress> <meter>  保持跨平台一致性字体和文本大小:继承父级元素的 CSS,而不使用系统默认样式button, input, select, textarea { font-family: inherit; font-size: 100%; }盒子模型:保持相同的宽度和高度button, input, select, textarea { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }定位<legend> 元素定位是 <fieledset> 父元素的上边框的最顶端<textarea> 元素的垂直对齐由基线改为顶端对齐 vertical-align: top清除默认样式-moz-appearance 清除 Firefox 表单元素的默认样式-webkit-appearance 清除 Safari 和 Chrome 表单元素的默认样式使用 CSS 伪类定义表单组件的细节CSS 2.1 支持 3 伪类::active :focus :hoverCSS Selector Level 3 新增 4 伪类::enabled :disabled :checked :indeterminateCSS Basic UI Level 3 新增 9 伪类::default :valid :invalid :in-range :out-of-range :required :optional :read-only :read-writeCSS Selector Level 4 新增: :user-error
0
0
0
浏览量2013
懒人学前端

3.核心概念—出口

一、配置参数详解output 输出配置详解output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。我们可以通过在配置中指定一个 output 对象,来配置这些处理过程:const path = require('path'); module.exports = { mode: 'development', entry: { index: './src/index.js' }, output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), publicPath: '/assets/', library: 'DemoLibrary', // 导出名称 libraryTarget: 'window' // 挂载目标 } };output 对象中可以包含数十个配置项,其中大部分开发中使用频率不高,我们在本章内容中只介绍几个常用配置,对其他配置感兴趣的同学可以查看官网 output配置filenamefilename 决定了每个输出 bundle 的名称。这些 bundle 将写入到 output.path 选项指定的目录下。对于单个入口起点,filename 会是一个静态名称。 filename 支持以字符串和函数的形式定义参数。// 字符串形式 module.exports = { ... output: { filename: 'bundle.js', } }; // 函数形式 module.exports = { ... output: { filename: (pathData) => { console.log(pathData) return '[name].js'; } } };字符串形式的 filename,会在输出文件中生成 bundle.js,函数形式的 filename 会在输出文件中生成 index.js (以 chunk name 命名),在控制台输出下 pathData,我们可以看到返回了一个包含 chunk 内容等信息的对象。filename 可以不仅仅是 bundle 的名字,还可以使用像 'js/[name]/bundle.js' 这样的文件路径,即便路径中的目录不存在也没关系,webpack 会在输出资源时创建该目录。例子如下:module.exports = { ... output: { filename: 'js/[name]/bundle.js' } }; 当通过多个入口起点(entry point)、代码拆分(code splitting)或各种插件(plugin)创建多个 bundle,应该使用以下一种替换方式,来赋予每个 bundle 一个唯一的名称上面的配置除了可以对不同的 bundle 进行名称区分,还能起到一个控制客户端缓存的作用,表中的[chunkhash] 和 [contenthash] 都与文件内容直接相关,在 filename 中使用了这些变量后,当对文件内容做了修改,可以引起 bundle 文件名的修改,从而用户在下一次请求文件资源时会重新加载文件,而不会直接命中缓存资源。在实际的工程中,我们一般使用较多的是[name],一般与定义的 chunk 一一对应,可读性较高,为了控制客户端缓存,我们一般还加上 [contenthash],如:module.exports = { ... output: { filename: '[name]-[contenthash].js' } };打包结果如下pathpath 可以指定资源输出位置,要求必须使用绝对路径,如const path = require('path'); module.exports = { mode: 'development', entry: { index: './src/index.js', }, output: { path: path.resolve(__dirname, 'dist') } };上述配置将工程的dist目录设置为资源的输出目录,在 webpack 4 之后,output.path 已经默认为 dist 目录,除非我们需要修改他,否则可以不用单独配置。publicPathpublicPath 从功能上来说,用于指定资源的请求位置。页面中的资源分为两种,一种是由 HTML 页面直接请求的,比如通过 script 标签加载 js,通过 link 标签加载 css。另一种是由 js 或 css 请求的,如加载图片字体文件等。 publicPath 就用来指定第二种间接资源的请求位置。如果指定了一个错误的值,则在加载这些资源时会收到 404 错误。publicPath 有以下三种形式相对于 HTML相对于 HOST相对于 CDN相对于 HTML在请求资源时,会以当前 html 页面所在路径加上 publicPath 的相对路径来构成实际请求的 URL,如// 假设当前 html 页面地址为 http://demo.com/webpack/index.html // 需要请求文件名为 demo.png module.exports = { ... output: { publicPath: '' // 实际请求路径 http://demo.com/webpack/demo.png publicPath: './css' // 实际请求路径 http://demo.com/webpack/css/demo.png publicPath: '../assets/' // 实际请求路径 http://demo.com/assets/demo.png } };相对于 HOST若 publicPath 的值以 “/” 开始,则代表此时 publicPath 是以当前页面的域名加上 publicPath 的相对路径来构成实际请求的 URL,如// 假设当前 html 页面地址为 http://demo.com/webpack/index.html // 需要请求文件名为 demo.png module.exports = { ... output: { publicPath: '/' // 实际请求路径 http://demo.com/demo.png publicPath: '/css' // 实际请求路径 http://demo.com/css/demo.png publicPath: '../assets/' // 实际请求路径 http://demo.com/assets/demo.png } }; 相对于 CDN上面两种配置都是相对路径,我们也可以使用绝对路径的形式配置 publicPath,这种情况一般发生在将静态资源放在 CDN 上面,如// 假设当前 html 页面地址为 http://demo.com/webpack/index.html // 需要请求文件名为 demo.png module.exports = { ... output: { publicPath: 'http://cdn.example.com/assets/' // 实际请求路径 http://cdn.example.com/assets/demo.png publicPath: 'https://cdn.example.com/assets/' // 实际请求路径 https://cdn.example.com/assets/demo.png publicPath: '//cdn.example.com/assets/' // 实际请求路径 //cdn.example.com/assets/demo.png } };webpack-dev-server 也会默认从 publicPath 为基准,使用它来决定在哪个目录下启用服务,来访问 webpack 输出的文件。librarylibrary 的作用是将打包的内容生成一个库,可以供其他工程加载使用。这一点在目前流行的微前端架构实战上面很有用,如子应用通过输出类库的形式将内容输出到一个对象上,这样主应用就可以通过加载 js 的方式去引入子应用,并且可以通过子应用输出的对象名称来加载子应用的内容。library 具体的使用方法,我们来看下面的例子:webpack.config.jsmodule.exports = { ... entry: './src/index.js', output: { library: 'DemoLibrary' } };src/index.js 的入口中导出了如下函数export function hello(webpack) { console.log(`hello ${webpack}`); }此时,变量 DemoLibrary 将与入口文件所导出的文件进行绑定,下面是如何使用打包生成的index.js文件:index.html<!DOCTYPE html> <html lang="en"> <head> <title>测试DemoLibrary库</title> </head> <body> <script src='./dist/index.js'></script> <script> DemoLibrary.hello('webpack'); </script> </body> </html>在浏览器中可以看到成功输出 hello webpack。library 的类型可以为字符串、数组、和对象,字符串的参数类型则直接指代库的名称,与对象中设置 name 属性作用相同。如果 entry 入口设置为 object,所有入口都可以通过 library 的 array 语法暴露:module.exports = { // … entry: { a: './src/a.js', b: './src/b.js', }, output: { filename: '[name].js', library: ['DemoLibrary', '[name]'], // [name] 为 chunk name }, };假设 a.js 与 b.js 导出名为 hello 的函数,下面就是如何使用这些库的方法:<!DOCTYPE html> <html lang="en"> <head> <title>测试DemoLibrary库</title> </head> <body> <script src='./dist/a.js'></script> <script src='./dist/b.js'></script> <script> DemoLibrary.a.hello('webpack'); DemoLibrary.b.hello('webpack'); </script> </body> </html>请注意,如果你打算在每个入口点配置 library 配置项的话,以上配置将不能按照预期执行。这里是如何 在每个入口点下 做的方法:module.exports = { // … entry: { main: { import: './src/index.js', library: { // `output.library` 下的所有配置项可以在这里使用 name: 'MyLibrary', type: 'umd', umdNamedDefine: true, }, }, another: { import: './src/another.js', library: { name: 'AnotherLibrary', type: 'commonjs2', }, }, }, };library 包含以下可配置参数这里我们说下 type 类型,在实际的使用中,我们可能根据工程运行环境的需要,而需要将类库暴露为不同的类型,如 支持 esModule、amd、cmd、umd 等,type 配置就可以帮我们完成不同输出方式。type 类型默认包括 'var'、'module'、'assign'、'assign-properties'、'this'、'window'、'self'、'global'、'commonjs'、'commonjs2'、'commonjs-module'、'commonjs-static'、'amd'、'amd-require'、'umd'、'umd2'、'jsonp' 以及 'system',除此之外也可以通过插件添加。官方文档对每种类型给了详细说明和事例,具体我们可查看官方文档,output.target.type 配置总结以上为我们在实际开发中使用的 output 配置,包含 path、filename、publicPath、library,日常使用中可能还会用到 libraryTarget ,不过 webpack 未来会放弃对 output.libraryTarget 的支持,所以可以使用 output.library.type 替代 output.libraryTarget。二、输出配置实例output 输出配置实例到目前为止,我们都是在 index.html 文件中手动引入打包生成的资源,然而随着应用程序增长,并且一旦开始在文件名中使用 hash 并输出 多个 bundle,如果继续手动管理 index.html 文件,就会变得困难起来。然而,通过一些插件可以使这个过程更容易管控。HtmlWebpackPlugin 可以帮我们解决这个问题。设置 HtmlWebpackPlugin继续使用之前的工程文件,目录结构为:首先安装插件,并且调整 webpack.config.js 文件:安装 html-webpack-plugin 插件npm install --save-dev html-webpack-pluginwebpack.config.jsconst path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', entry: { index: './src/index.js', hello: './src/hello.js' }, output: { filename: '[name].js' }, plugins: [ new HtmlWebpackPlugin({ title: '管理输出', }), ], };执行构建命令 npm run build,我们看下打包后的结果,我们可以看到,打包文件包含两个入口文件对应的 js 文件,还包含一个 index.html 文件在 dist 目录下我们看下打包的 index.html 文件,我们可以看到 HtmlWebpackPlugin 创建了一个全新的 index.html 文件,所有的 bundle 会自动添加到 html 中。清理 /dist 文件夹你可能已经注意到,由于遗留了之前指南中的代码示例,我们的 /dist 文件夹显得相当杂乱。webpack 将生成文件并放置在 /dist 文件夹中,但是它不会追踪哪些文件是实际在项目中用到的。通常比较推荐的做法是,在每次构建前清理 /dist 文件夹,这样 /dist 文件夹中只有最近一次生成的文件。让我们使用 output.clean( webpack 5.20.0 及以上版本支持)配置项实现这个需求。webpack.config.jsmodule.exports = { ... output: { clean: true } };现在,执行 npm run build,检查 /dist 文件夹。如果一切顺利,现在只会看到构建后生成的文件,而没有旧文件!总结本章我们介绍了一个优化开发效率的插件和一个配置项,使用 HtmlWebpackPlugin 插件可以动态的生成 index.html 文件,以及动态的向 index.html 文件插入 bundle。了解了如何在编译时清空 dist 文件内容。
0
0
0
浏览量2012
懒人学前端

1.注释

一、如何写 CSS 注释,作用是什么?以/*开头,*/结束。可阻止其中 CSS 解析,给代码增加说明,提升可读性二、如何去除 CSS 注释?用正则/\/*[\s\S]*?*\//全局匹配注释,替换为空字符串用工程化工具,如cssnano来去除注释
CSS
0
0
0
浏览量2011
懒人学前端

20.综合

一、HTTP2.0 时代,CSS Sprites 还有用吗,为什么?HTTP1.1 下的 CSS SpritesHTTP1.1 自身的管道化并不完善。浏览器实际通过为同一域名下的资源同时建立多个 TCP 连接,来实现并行传输,并且限制了最大连接数通过手动或者工程化的方式,将将小图片合并成大图,即 Sprites图(又称精灵图,雪碧图)。可以将多个对小图的请求,合并为对单张大图的请求,通过background-position定位在文档显示优点:避免大量小图请求阻塞其他资源下载减少多连接的重复的从发起请求到首字节响应的时间避免由于网络不稳定、客户端、服务端限制,导致部分小图丢失的情况图标、界面一次显示全,提升用户体验缺点:修改、增加和删除图片、修改位置,颜色麻烦请求图片的连接失败,整个界面图片效果丢失宽度和高度通常固定,自适应需要额外设置实现响应式图片时可能需要维护多张合成图,而且灵活度依然不够HTTP1.1 下的 SVG Sprites合并 SVG 到一张 SVG<svg> <symbol id="svg1"><!-- SVG① --></symbol> <symbol id="svg2"><!-- SVG② --></symbol> <symbol id="svg3"><!-- SVG③ --></symbol> </svg>在文档中通过use引用其中的一张 SVG<svg> <use xlink:href="#svg1"></use> </svg>SVG 具有无锯齿放大,容易修改尺寸好颜色等优点SVG Sprites 可以作为不考虑 IE8- 时,CSS Sprites替代但开发者仍然需要 手工 或 工程化方式,合成 SVG SpritesHTTP2.0 支持多路复用HTTP2.0 下,一个 TCP 会话上可以进行任意数量的HTTP请求理论上,所有图片资源几乎可以并行下载CSS Sprites 最初要解决的问题,已经不复存在基于 TCP 本身的局限性(丢包时,整个会话都要重传),考虑网络的不稳定实际体验中,CSS Sprites 对比 HTTP2.0,通常仍会有轻微的速度提升是否使用 CSS Sprites,或者升级到 SVG Sprites 可以结合维护复杂度代入具体项目决定二、你在开发中都遇到过哪些 CSS 兼容性问题,你是如何解决的?IE 兼容性问题PC 时代,Trident 内核的 IE 在 5.5 - 8 时占据主导地位问题:IE6 PNG 图片不透明答案:使用 progid:DXImageTransform.Microsoft.AlphaImageLoader 滤镜重复加载 PNG问题:IE6 浮动元素设置 margin,边距变 2 倍答案:设置浮动元素display: inline问题:IE6 不支持绝对定位 position: fixed答案:position: absolute; top: expression(((t = document.documentElement.scrollTop) ? t : document.body.scrollTop) + 'px'); 问题:IE8 有链接的图片四周由黑边框答案:img { border: none }测试IEtester 比 IE9+ 自带 Simulater 更接近真实效果使用装有 低版本IE 的虚拟机镜像,模拟完全真实的低版本 IE 环境微软甚至专门提供了一套90天激活的 win7 镜像非标与标准浏览器兼容性问题标准属性不被支持,或者实现不同问题:IE兼容模式 与 标准浏览器 宽度和高度表现不同答案:IE 兼容模式默认 box-sizing: border-box 而不是 box-sizing: content-box,可以统一声明为 前者,或者用 CSS hacks 设置值对 IE 兼容模式生效的宽高或边距问题:IE9- 不支持 opacity答案:opacity: .8;filter: alpha(opacity = 80);filter: progid:DXImageTransform.Microsoft.Alpha(style = 0, opacity = 80);问题:IE9- 不支持 background-size 等 CSS3 新特性答案:IE6+ 支持behavior,可以设置或检索对象的 DHTML 行为。引入 css3pie 其用这个属性让 IE 支持部分 CSS3 新特性问题:cursor: hand在标准浏览器无效答案:用 IE 也支持的标准属性值cursor:pointer替代CSS3 新特性兼容性问题CSS3 起,标准被模块化,每个模块相对独立。浏览器可能会将处于 非推荐 的阶段属性实验性实现,或者某个处于 候选推荐 阶段的标准又被否决。导致不同浏览器,以及同一浏览器的不同版本,对于新特性的实现差异。解决:人工或使用混入器添加私有前缀工作量大,开发阶段冗余代码多同一属性,有无私有前缀,后面接收的属性值类型、顺序等可能不同部分私有前缀,需要加到属性值,而不是属性名上PostCSS 的 autoprefixer 可以配置 Browserslist,按需对浏览器及其不同版本添加私有前缀移动端兼容问题主要是 iOS 和 Android 下移动浏览器兼容以及使用 Webview 开发的 APP 兼容问题注意:以下这些问题只会在特定版本的系统下出现问题:溢出含滚动条元素怒在 iOS 滑动体验卡顿答案:给溢出滚动元素设置 -webkit-overflow-scrolling:touch问题:iOS 禁用按钮后,按钮样式异常答案:给按钮设置 opacity: 1问题:iOS 监听<span> <label>点击无响应答案:给元素怒设置 cursor:pointer问题:Android 输入框显示语音输入按钮答案:隐藏语音输入按钮 input::-webkit-input-speech-button { display: none }想要让 APP 用户获得体验更一致,减少兼容性问题:将渲染引擎与源码一起打包,比如使用腾讯基于 X5 内核的浏览服务,安装包会变大采用 React Native Flutter UniApp + Weex 等使用 HTML + CSS + JS 开发,原生渲染的方案三、当你忘记一个 CSS 属性时,你一般如何做?EmmetEmmet 能够提高 HTML 和 CSS 的书写效率,我们只需键入缩写,代码提示就会帮助我们找到相应属性名或属性值IDE 如 VsCode 集成了 Emmemt,文本编辑器 Sublime Notepad++ VIM 等都有相应插件可以安装查看说明IDE 如 VsCode 支持鼠标悬停属性名 或 属性值,查看说明点击其中的链接,可以直达 MDN 对应文档搜索MDN,里面有属性的用法,在浏览器实现的说明及对应规范的链接Can I use,里面有属性在各个运行环境的兼容性情况,移动端版本数据相当丰富记忆重新手打一遍遗忘的属性和属性名,注释搜索中发现的信息或链接,在后续重构中思考最佳实践,这样能收获更多四、结合项目谈谈你是如何学习 CSS 的?发现问题思考你在项目中遇到过哪些问题,你是否如何解决的搜索,查文档、查规范是比请教他人更快速的问题解决途径Bad:同一问题,同一属性,每次都要搜索Good:高频的属性用法,尽量记住Bad:反复复制粘贴代码Good:找出代码中真正解决问题的部分,自己敲一遍Great:明白问题的原因和解决问题的思路提升效率、规范思考项目有哪些可以改进的地方,你是如何改进的,成果怎样效率:是否可以使用工具、脚本,将原有 CSS及相关的图片编译、压缩、上传、部署 等流程化、自动化规范:是否可以改进 CSS 文件、规则的组织形式,是否可以参考或者根据业务,拟定一套利于团队协作的开发规范,并通过工程化的方式推广规范(比如,在Commit前,对CSS代码进行规范性校验和自动更正)技术分享回顾一次你做的技术分享,你通过哪些渠道获取了权威的信息,是否手写代码验证了这些信息,是否有通过 脑图、动画、PPT 等提高信息分享的效率 - 回顾一次你与同事或同学的争论,你是如何证明自己观点是正确的,是拿出了权威的资料,请权威的人帮你说服,还是通过实际测试后,用数据说话学无止境CSS 最近有哪些新鲜事,可以新的草案,新的工具,新的方法论等,简单分享你的观点项目中所用的技术栈,最近更新的内容是什么,比过去改进了什么,解决了什么问题,怎样解决的?你可以根据上面的提纲自由发挥,每个问题举 1 个例子即可,避免长篇大论,不要谈太多感受,重点体现方法,结果,涉及的知识点到为止
CSS
0
0
0
浏览量1855
懒人学前端

第七章:模块

谈谈模块化的发展历程随着我们的应用越来越大,我们想要将其拆分成多个文件,即所谓的“模块”(module)。一个模块可以包含用于特定目的的类或函数库。模块化的定义一个模块(module)就是一个文件。一个脚本就是一个模块。模块可以相互加载,并可以使用特殊的指令 export 和 import 来交换功能,从另一个模块调用一个模块的函数:export 关键字标记了可以从当前模块外部访问的变量和函数。import 关键字允许从其他模块导入功能。模块化的历程大致可以分为三个阶段:早期模块化方案规范标准时代ES6 模块早期模块化方案命名空间对象可以有属性,对象的属性通过对象名字来访问,相当于设定了一个命名空间。var myModule = { name: "Joe", getName: function () { console.log(this.name); } }; myModule.getName(); // "Joe"然而缺点是,对象内部属性全部会暴露出来,内部状态可以被外部更改,如下:var myModule = { name: "Joe", getName: function () { console.log(this.name); } }; myModule.name = "Lucy"; myModule.getName(); // "Lucy"立即执行函数 IIFE立即执行函数(简称 IIFE)是利用函数闭包的特性来实现私有数据和共享方法,如下:var myModule = (function () { var name = "Joe"; function getName() { console.log(name); } return { getName }; })(); myModule.name = "哈哈哈"; myModule.getName(); // "Joe"我们可以看到,这种方法很好实现了数据的封装和私有化。如果依赖另外一个模块呢?我们只需要将其他模块作为参数传入:// otherModule.js 模块文件 var otherModule = (function () { return { a: 1, b: 2 }; })(); // myModule.js模块文件 - 依赖 otherModule 模块 var myModule = (function (other) { var name = "Joe"; function getName() { console.log(name); console.log(other.a, other.b); } return { getName }; })(otherModule); myModule.name = "哈哈哈"; myModule.getName(); // 依次输出:"Joe" 1 2规范标准时代主要有四种规范:CommonJS —— 为 Node.js 创建的模块系统。AMD —— 最古老的模块系统之一,最初由 require.js 库实现。CMD —— 它汲取了 CommonJS 和 AMD 规范的优点,也是专门用于浏览器的异步模块加载。UMD —— 另外一个模块系统,建议作为通用的模块系统,它与 AMD 和 CommonJS 都兼容。CommonJS在 Node.js 中,每一个文件就是一个模块,具有单独的作用域,对其他文件是不可见的。关于 CommonJS 的规范有几个需要注意的特点:文件即模块,文件内所有代码都运行在独立的作用域,因此不会污染全局空间。模块可以被多次引用、加载。在第一次被加载时,会被缓存,之后都从缓存中直接读取结果。加载某个模块,就是引入该模块的 module.exports 属性。module.exports 属性输出的是值的拷贝,一旦这个值被输出,模块内再发生变化不会影响到输出的值。模块加载顺序按照代码引入的顺序。注意 module.exports 和 exports 的区别CommonJS 规范用代码如何在浏览器端实现呢?其实就是实现 module.exports 和 require 方法。实现思路:根据 require 的文件路径,加载文件内容并执行,同时将对外接口进行缓存。因此我们需要定义module.exports 后,借助立即执行函数,将 module 和 module.exports 对象进行赋值:let module = {} module.exports = {} (function(module, exports) { // ... }(module, module.exports))AMDAsynchronous module definition (AMD) 是专门为浏览器环境设计的,它定义了一套异步加载标准来解决同步的问题。它规定了如何定义模块,如何对外输出,如何引入依赖。这一切都需要代码去实现,因此一个著名的库 —— require.js 应运而生,require.js 实现很简单:通过 define 方法,将代码定义为模块;通过 require 方法,实现代码的模块加载。CMDCMD 规范整合了 CommonJS 和 AMD 规范的特点。它的全称为:Common Module Definition,类似 require.js,CMD 规范的实现为 sea.js。AMD 和 CMD 的两个主要区别如下。AMD 需要异步加载模块,而 CMD 在 require 依赖的时候,可以通过同步的形式(require),也可以通过异步的形式(require.async)。CMD 遵循依赖就近原则,AMD 遵循依赖前置原则。也就是说,在 AMD 中,我们需要把模块所需要的依赖都提前在依赖数组中声明。而在 CMD 中,我们只需要在具体代码逻辑内,使用依赖前,把依赖的模块 require 进来。具体到代码实现,sea.js 与 require.js 并没有本质差别,这里不再另做分析。UMDUMD(Universal Module Definition),即通用模块定义。它随着大前端的趋势所诞生,可以通过运行时或者编译时让同一个代码模块在使用 CommonJS、CMD 甚至是 AMD 的项目中运行,也就是说同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了,那它是怎样实现的呢?我们来看看这样一段代码:((root, factory) => { if (typeof define === "function" && define.amd) { // AMD define(factory); } else if (typeof exports === "object") { // CommonJS module.exports = factory(); } else if (typeof define === "function" && define.cmd) { // CMD define(function (require, exports, module) { module.exports = factory(); }); } else { // 都不是 root.umdModule = factory(); } })(this, () => { console.log("我是UMD"); // "我是UMD" // todo... });可以看到,define 是 AMD/CMD 语法,而 exports 只在 CommonJS 中存在,你会发现它在定义模块的时候会检测当前使用环境和模块的定义方式,如果匹配就使用其规范语法,全部不匹配则挂载在全局对象上,我们看到传入的是一个 this,它在浏览器中指的就是 window,在服务端环境中指的就是 global,使用这样的方式将各种模块化定义都兼容。ES6 模块ES6 模块(或称为 ESM),ES6 模块不是对象,import 命令被 JavaScript 引擎静态分析,在编译的时候就引入模块代码。而不是在代码运行时加载,所以无法实现条件加载。也就使得静态分析成为可能。exportexport 可以导出对象中多个属性、方法,export default 只能导出一个可以不具名的函数。我们可以使用 import 引入。importimport { fn } from './xxx' // export 导出的方式 import fn from 'xx' // export default 方式ES6 模块运行机制与 CommonJS 运行机制不一样。JavaScript 引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用。等到脚本真正执行的时候。才会通过引用模块获取值,在引用到执行的过程中,模块中的值发生变化,导入的这里也会跟着发生变化。ES6 模块是动态引入的。并不会缓存值。模块里总是绑定其所在的模块。小练习ES6 模块有什么特点?答案:见上文。
0
0
0
浏览量1966
懒人学前端

5.核心概念-Pligin—下

五、MiniCssExtractPluginMiniCssExtractPluginMiniCssExtractPlugin 插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件。这个插件需要在 webpack 5 中才能正常工作,webpack 4 中使用 extract-text-webpack-plugin 插件做 CSS 分离。之前的文章中我们介绍了 style-loader 的使用,style-loader 是将 JS 中包含的 CSS 内容以 <style> 标签的形式插入到 DOM 中,而 MiniCssExtractPlugin 插件则是将 JS 中包含的 CSS 创建一个新的文件,并且以 <link> 标签的形式插入链接到加载依赖的文件中。MiniCssExtractPlugin 作用与 style-loader 类似,但是实现方式却完全不同。MiniCssExtractPlugin 一般与 css-loader 同时使用。注意:下面【使用】模块的案例,index.css 分离是 MiniCssExtractPlugin 做的,而通过 `<link>` 标签插入index.html 是 HtmlWebpackPlugin 插件做的,MiniCssExtractPlugin 插件可以将非入口文件通过 `<link>` 标签插入到加载依赖的文件中,即 MiniCssExtractPlugin 插入 `<link>` 仅适用于非初始(异步)块。使用index.jsimport styles from "./index.css"; import main from "./main.css"; const divElement = document.createElement("div"); divElement.className = "demo"; divElement.innerHTML = 'mini-css-extract-plugin'; document.body.appendChild(divElement);index.css.demo { color: red; }main.css.demo { font-size: 20px; }安装 mini-css-extract-pluginnpm install mini-css-extract-plugin -D安装成功后,将 mini-css-extract-plugin 配置到 webpack.config.js 的 plugins 和 module 中。webpack.config.js... const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { ... module: { rules: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, plugins: [ ... new MiniCssExtractPlugin() ] }我们在入口文件 index.js 中导入了 main.css 和 index.css 两个样式文件。一个改变字体,一个改变字体颜色。执行打包命令,可以看到在 dist 文件夹下除了 index.js 和 index.html 文件之外还生成了一个 index.css 文件。index.css 文件中包含 main.css 和 index.css 的内容。在 index.html 中通过 <link> 标签的形式引入了 index.css 文件。在浏览器中打开 dist/index.html 可以看到样式被正常展示。上面的例子中在 index.js 中导入了两个样式文件,接下来我们试一下在入口文件 index.js 中导入 main.js 和 index.css,其中 main.js 中导入 main.css。其他配置保持不变。index.jsimport styles from "./index.css"; import { default as mainElement } from './main'; const divElement = document.createElement("div"); divElement.className = "demo"; divElement.innerHTML = 'mini-css-extract-plugin'; divElement.appendChild(mainElement); document.body.appendChild(divElement);main.jsimport main from "./main.css"; const divElement = document.createElement("div"); divElement.className = "demo-main"; divElement.innerHTML = 'demo-main'; export default divElement;main.css.demo-main { font-size: 20px; }执行打包命令后,dist 文件夹下依然只有 index.html,index.js,index.css 文件。查看后发现虽然在 main.js 中导入的 main.css,但由于 main.js 被 index.js 依赖。所以 css 文件内容依然都打包到了 index.css 中。配置MiniCssExtractPlugin 包含两部分配置,plugin 配置和 loader 配置。Plugin Optionsfilename类型:string | ((pathData: PathData, assetInfo?: AssetInfo) => string);默认值:[name].css作用:输出的 css 文件的名称,默认为 chunkName 值。webpack.config.jsmodule.exports = { plugins: [ new MiniCssExtractPlugin({ ... filename: '[name].css' // index.css // filename: (pathData) => { // return pathData.hash + '-index.css'; // }, // 8de455759e47e7f4d53b-index.css }), ] }; chunkFilename类型:string | ((pathData: PathData, assetInfo?: AssetInfo) => string);默认值:基于 filename作用:修改非入口文件的打包后的名称。index.jsimport styles from "./index.css"; // 将 main.js 打包到单独文件 const mainElement = import(/* webpackChunkName: "main" */ './main'); ...webpack.config.jsmodule.exports = { plugins: [ new MiniCssExtractPlugin({ chunkFilename: 'chunk-[id].css', // chunk-main.css }), ] };打开 dist 我们可以看到 main.js 文件已经单独打包,并且生成了一个 chunk-name.css 文件。ignoreOrder类型:boolean默认值:false作用:模块中加载 css 文件顺序不一致是否发出警告,例如:1.js 中加载 css 的顺序是 a.css、b.css,2.js 中加载顺序是 b.css、a.css,就会发出警告,详细可看下官网 issuewebpack.config.jsmodule.exports = { ... plugins: [ new MiniCssExtractPlugin({ ignoreOrder: false, }) ] };insert类型:string | ((linkTag: HTMLLinkElement) => void)默认值:document.head.appendChild(linkTag),即默认插入到 DOM 文件的 head 中作用:打包的 CSS 文件,通过 <link> 标签插入的位置。index.jsimport styles from "./index.css"; const divElement = document.createElement("div"); // import 方法导入的模块为异步加载 import(/* webpackChunkName: "main" */ './main').then(res => { divElement.appendChild(res.default) }); divElement.className = "demo"; divElement.innerHTML = 'mini-css-extract-plugin'; document.body.appendChild(divElement);index.html... <body> <div id="div-element"></div> </body>webpack.config.jsmodule.exports = { ... plugins: [ new MiniCssExtractPlugin({ insert: '#div-element' }) ] };执行打包命令后,在 dist/index.html 文件中,只包含了 index.css 的 <link> 标签,在浏览器中运行 index.html,在控制台可以看到 chunk-main.css 的 <link> 标签被插入到选择器为 #div-element 元素的后面。在生成的 index.js 文件中可以看到创建 link 标签的源码。attributes类型:object默认值:{}作用:在 <link> 标签中添加属性webpack.config.jsmodule.exports = { ... plugins: [ ... new MiniCssExtractPlugin({ attributes: { "data-target": "data" // <link data-target="data" rel="stylesheet" type="text/css" href=".../dist/main.css"> }, }) ] };linkType类型:string | boolean默认值:text/css作用:修改 <link> 标签中 type 属性,type 的默认值为 text/css。webpack.config.jsmodule.exports = { ... plugins: [ ... new MiniCssExtractPlugin({ linkType: "text/css" // <link type="text/css" ...> // linkType: false // <link rel="stylesheet" href=".../dist/main.css"> }) ] };runtime类型:boolean默认值:true作用:允许启用/禁用运行时生成。webpack.config.jsmodule.exports = { ... plugins: [ ... new MiniCssExtractPlugin({ runtime: false // 运行 index.html 时,main.css 不会被插入到 DOM 中。 }) ] };Loader OptionspublicPath类型:string | ((resourcePath: string, rootContext: string) => string)默认值:webpackOptions.output 选项的值作用:输出的 CSS 文件中,为图像、文件等外部资源指定自定义公共路径。main.css.demo-main { font-size: 20px; background: url(./img/1.png); }webpack.config.jsmodule.exports = { ... module: { rules: [ { test: /\.css$/i, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: "/public/path/to/", // main.css background: url(/public/path/to/e30cb9c395cbb5ae00e9.png); }, }, "css-loader"], }, ], }, };打包成功后,查看生成的 main.css 文件,background 地址已经被替换成传入的 publicPath,通过传入 publicPath 可以引用外部资源。emit类型:boolean默认值:true作用:提取的 CSS 是否生成对应文件。当 emit 值为 true 会生成 CSS 文件,值为 false 不生成 CSS 文件。webpack.config.jsmodule.exports = { ... module: { rules: [ { test: /\.css$/i, use: [ { loader: MiniCssExtractPlugin.loader, options: { emit: false, // 打包后的 dist 中没有生成 index.css 和 main.css,运行 index.html 样式不生效 }, }, "css-loader"], }, ], }, };esModule类型:boolean默认值:true作用:生成的文件是否使用 ES 模块语法,开启 ES 语法对 tree-shaking 将非常有用。webpack.config.jsmodule.exports = { ... module: { rules: [ { test: /\.css$/i, use: [ { loader: MiniCssExtractPlugin.loader, options: { esModule: false, // 启用CommonJS 语法 }, }, "css-loader"], }, ], }, };总结此章节演示了 MiniCssExtractPlugin 的用法和包含的配置参数。MiniCssExtractPlugin 还有一些推荐的例子,感兴趣的同学可查看官方。六、WebpackManifestPluginWebpackManifestPluginWebpackManifestPlugin 是一个 webpack 插件,此插件的作用是生成资产清单即 manifest.json 文件,在使用此插件前,我们先看下什么是 manifest 以及 manifest 的作用,以下引自 webpack 官网。一旦你的应用在浏览器中以 index.html 文件的形式被打开,一些 bundle 和应用需要的各种资源都需要用某种方式被加载与链接起来。在经过打包、压缩、>为延迟加载而拆分为细小的 chunk 这些 webpack 优化 之后,你精心安排的 /src 目录的文件结构都已经不再存在。所以 webpack 如何管理所有所需模 >块之间的交互呢?这就是 manifest 数据用途的由来……总结来说,manifest.json 就是记录项目中生成的 bundle 之间的映射关系。有了这份清单,我们可以通过特定的方法加载资源,如服务端渲染或通过遍历 manifest.json 将记录输出到页面上等。在当前流行的微前端框架中,通过引入不同子项目的 manifest.json 文件,并遍历文件内容动态输出到 DOM 中,从而实现加载不同子项目工程,这会比手动获取子项目资源清单减少出错概率和省事的多。使用index.jsimport styles from "./index.css"; const divElement = document.createElement("div"); divElement.className = "demo"; divElement.innerHTML = 'webpack-manifest-plugin'; document.body.appendChild(divElement);index.css.demo { color: red; background: url(./img/1.png); }安装 webpack-manifest-pluginnpm install webpack-manifest-plugin -D安装成功后,将 webpack-manifest-plugin 配置到 webpack.config.js 的 plugins 中。webpack.config.js... const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); module.exports = { ... plugins: [ ... new WebpackManifestPlugin({ publicPath: './' }) ] }我们在 index.js 文件中创建了一个 div 标签,在 index.css 中设置了 div 标签的字体颜色和背景图片,此时执行打包命令,打包成功后在 dist 中可以看到除了项目资源文件外还有一个 manifest.json 文件。打开 manifest.json 文件看下内容。manifest.json 包含了资源的映射关系,我们需要遍历 manifest.json 对象的内容,将需要的如 js 资源通过 createElement 等方式挂载到 index.html 中项目即可正常使用。配置以下为包含部分常用配置,全部配置可查看官网basePath类型:String默认值:''作用:生成的 manifest.json 文件中,basePath 参数传入的值将添加到对象的 key 值前面。webpack.config.jsmodule.exports = { plugins: [ new WebpackManifestPlugin({ ... basePath: 'src' // "src/index.js": ".../index.js", basePath: '' // "index.js": ".../index.js", }), ] }; fileName类型:String默认值:manifest.json作用:fileName 传入的值作为输出的文件清单的名称。webpack.config.jsmodule.exports = { plugins: [ new WebpackManifestPlugin({ fileName: 'manifest-filename.json' // manifest.json -> manifest-filename.json }) ] };filter类型:(file: FileDescriptor) => Boolean默认值:undefined作用:执行 filter 回调函数,返回 true 则 manifest.json 中包含 bundle 的映射关系,返回 false 则不包含此 bundle 映射关系。webpack.config.jsmodule.exports = { ... plugins: [ new WebpackManifestPlugin({ filter: (file) => { return file?.name?.endsWith('.js') // manifest.json 中只包含 js 拓展名的文件 } }) ] };generate类型:(seed: Object, files: FileDescriptor[], entries: string[]) => Object默认值:undefined作用:自定义修改生成的 manifest.json 中的键值对内容,generate 传入的函数返回值需要为对象。webpack.config.jsmodule.exports = { ... plugins: [ new WebpackManifestPlugin({ generate: (seed, files, entries) => { console.log(seed, 'seed') console.log(files, 'files') console.log(entries, 'entries') return { 'main-index': 'index.js' } } }) // manifest.json 中内容 { "main-index": "index.js" } ] };map类型:(file: FileDescriptor) => FileDescriptor默认值:undefined作用:自定义修改生成的 manifest.json 中的键值对内容。webpack.config.jsmodule.exports = { ... plugins: [ ... new WebpackManifestPlugin({ map: (file) => { const fileName = file?.name; if (file?.name?.endsWith('.js')) { file.name = 'assets.' + fileName } return file } }) // { "assets.main.js": "auto/main.js" ... } ] };publicPath类型:String默认值:webpack.config.js 中的 output.publicPath 值作用:publicPath 传入的内容将添加到 manifest.json 对象的值前面。webpack.config.jsmodule.exports = { ... plugins: [ ... new WebpackManifestPlugin({ // { // "index.js": "./dist-public/index.js", // "1.png": "./dist-public/2b2001bb98465dd14a87.png", // "index.html": "./dist-public/index.html" // } publicPath: './dist-public' }) ] };serialize类型:(Object) => string默认值:undefined作用:格式化 manifest.json 中的内容。webpack.config.jsmodule.exports = { ... plugins: [ ... new WebpackManifestPlugin({ serialize: (obj) => { console.log(obj) return JSON.stringify(obj) // {"index.js":"auto/index.js","1.png":"auto/2b2001bb98465dd14a87.png","index.html":"auto/index.html"} } }) ] };总结此章节演示了 WebpackManifestPlugin 的用法和包含的部分常用配置参数,通过 WebpackManifestPlugin 生成的资源清单可以让我们在项目中快速找到引用的依赖文件路径,这对于服务端渲染或微前端等将非常有用。
0
0
0
浏览量1134
懒人学前端

3.属性

一、什么是 HTML 属性 ?HTML 元素拥有属性(Attribute)HTML 属性不会出现在实际的内容中HTML 属性包含元素的额外信息,被用来配置元素调整元素的行为二、HTML 属性必须包括哪些内容 ?一个属性必须包含如下内容一个空格,在属性和元素名称之间属性名称,后面跟着一个等于号一个属性值,由一对引号“”引起来三、什么是布尔属性 ?布尔属性是值可以省略的属性只声明属性名,等同于声明设置一个属性值与属性名相同的属性不声明属性名,则表示该元素没有该属性,不具备该属性值所描述的配置或行为四、属性值的引号的可以省略吗 ?属性值省略引号这是被允许的当属性值包含空格、引号时,可能让浏览器误解标记始终建议添加引号避免浏览器误解标记的问题使代码更易读单引号或者双引号都可以成对使用,这只是风格问题,但不能混用五、如何链接到 HTML 文档的特定部分(文档片段)?链接到 HTML 文档的特定部分,即文档片段,需要给要链接的元素分配一个 id 属性链接到特定 id 设置 href 属性不同文档,在 URL 结尾使用 # 指向 id同一文档,直接使用# 指向 id六、览器如何对待引号没有闭合的属性,如何理解 HTML 的宽松解析 ?(1)浏览器只解释非编译 HTML浏览器不会将 HTML 编译成其它形式,而是直接解释并显示结果浏览器解析 HTML 的过程比编程语言编译运行的过程要宽松得多(2)什么是 HTML 的宽松解析通常写错代码会带来以下两种主要类型的错误语法错误:由通常是拼写错误,熟悉语法并理解错误信息后很容易修复逻辑错误:不存在语法错误,但代码运行结果不符合预期。通常逻辑错误比语法错误更难修复,因为无法得到指向错误源头的信息因为浏览器是以宽松模式解析 HTMLHTML 本身不容易出现语法错误,出现语法错误时,浏览器仍会继续解析 HTML浏览器使用内建规则来解析语法错误,例如未关闭标签,未关闭属性和嵌套混乱元素标签,属性和层级会被自动修复修复结果不一定符合开发者预期存在 XSS,版式混乱,内容错误等隐患(3)为什么 HTML 要宽松解析因为 Web 创建的初心就是:人人可发布内容,不去纠结代码语法如果 Web 以严格风格起步,也许就不会像今天这样流行七、什么是微数据 ?HTML5 微数据允许通过特定的机器可读的标签来标记内容,只需向已有内容添加一组键值对微数据由键值对组成,每一组称为项,每个键值对可以用属性表示itemscope用来声明一组微数据itemprop用来声明键名属性不同,值相同:一个标签,声明多个键名,空格分隔属性相同,值不同:多个标签,声明相同键名键值通常是字符串,即元素内容URL<a> 标签的 href 属性<img> 标签的 src 属性value对于不适合给人类展示的内容,使用 value 声明内容<meter> 标签的 value 属性<time> 标签的 datetime 属性itemscope键值可以嵌套另一组微数据itemref用来关联不在 imtescope 所在标签的后代的属性itemtype指定结构化数据词汇的 URL,其中定义的标准且唯一的词汇用于 itemprop与 itemscope 一起使用用于设置词汇的生效范围使用词汇表,可以增加词汇的复用度,保持属性名唯一,避免冲突Google 和其它主流搜索引擎支持 schema.org 结构化数据词汇,便于在搜索结果中丰富展示内容和形式itemid与 itemtype 一起使用,用于声明全局唯一标识符八、如何创建一个下载链接 ?设置 a 标签的 download 属性指示浏览器下载 URL 而不是导航到 URL,提示用户将其保存为本地文件属性值可以在保存提示中用作预先填写的文件名斜杠 / 和反斜杠 \ 会被转换为下划线大多数文件系统限制一些标点符号,浏览器会相应地调整建议名称download 属性仅适用于同源 URLs使用 blob: URLs 和 data: URLs方便用户下载 JavaScript 方式生成内容例如 Canvas,Base64 编码图片等如果 HTTP 头存在 Content-Disposition 属性属性值不同于 download 设置值,HTTP 头优先使用此属性属性值设置为 inline火狐优先 Content-Disposition 的属性值Chrome 优先 download 的属性值(如果与 Content-Disposition 不同)
0
0
0
浏览量127
懒人学前端

第八章: Proxy & Reflection

一、谈谈 Object.defineProperty 与 Proxy 的区别?要谈两者区别,我们先来了解一下 Objet.defineProperty() 和 Proxy。Objet.defineProperty()Objet.defineProperty() 可以定义一个对象的属性或修改对象上已存在的属性,并返回这个对象。语法如下Object.defineProperty(obj, prop, descriptor);我们来了解一下第三个参数 descriptor。它是属性描述符,MDN 中描述如下:对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符和存取描述符共享两个可选键值:configurable:当值为 true 时,该属性的描述符才能被改变,默认为 falseenumerable:当值为 true 时,该属性才会出现在对象的枚举属性中,默认为 false除上面两个属性外,数据描述符具有下面两个可选键值:value:该属性对应的值。默认为 undefinedwritable: 当值为 true 时,上面的 value 才能被修改,默认为 false存取描述符具有下面两个可选键值:get:属性的 getter 函数,当访问该属性时,会调用此函数。如无 getter,则为 undefinedset:属性的 setter 函数,但属性值被修改时,会调用该函数。如无 setter,则为 undefined总的来说,数据描述符和存取描述符可拥有的键值概括如下:拦截对象属性此处,descriptor 我们使用 get 和 set 方法拦截对象的属性:let o = {}; let value = "value"; Object.defineProperty(o, "b", { get() { // 获取 o.b 的值 console.log(`get ${value}`); return value; }, set(newValue) { // 设置 o.b 的值 console.log(`set ${newValue}`); value = newValue; }, enumberable: true, configurable: true }); // 获取对象 o 的属性 b 的值 console.log(o.b); // 依次输出: // "get value" // "value"; // 设置对象 o 的属性 b 的值为 value1 o.b = "value1"; // "set value1" // 重新获取对象 o 的属性 b 的值 console.log(o.b); // 依次输出: // "get value1" // "value1" delete o.b; // 删除属性, 未触发 get、set 操作 console.log(o.b); // undefined从上面的代码可以看出,我们通过 get 和 set 方法,可以检测到对象属性的变化,从而实现对象属性的监听。然而删除属性操作却并未触发 get 和 set 方法。监听对象上的多个属性如果我们监听对象上的多个属性的变化,就需要遍历对象:let list = [1, 2, 3]; list.map((elem, index) => { Object.defineProperty(list, index, { get: function () { console.log("get index:" + index); return elem; }, set: function (val) { console.log("set index:" + index); elem = val; } }); }); list[2] = 6; // 输出 "set index:2" console.log(list[1]); // 依次输出: "get index:1" 2 list.push(4); // A 无输出 list[3] = 5; // B 无输出 console.log(list[3]); // 输出 5 list.length = 10; // C 无输出虽然我们监听到了数组的变化,但是代码 A、B、C 处理想情况下,我们希望它触发 set 操作,但此处并没有输出。因此,操作数组的 push 方法、修改数组长度 length 都无法监听到正确结果。数组的方法我们可以通过劫持 Array.prototype 上的方法做到:const arrayMethods = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ]; const arrayProto = Object.create(Array.prototype); arrayMethods.forEach((method) => { const origin = Array.prototype[method]; arrayProto[method] = function () { console.log("run method", method); return origin.apply(this, arguments); }; }); const list = []; list.__proto__ = arrayProto; list.push(2); // "run method" "push" list.shift(3); // "run method" "shift"Object.defineProperty 的缺陷由上可知,Object.defineProperty 在劫持对象和数组时的缺陷:无法检测到对象属性的添加或删除监听对象的多个属性,需要遍历该对象无法检测数组元素的变化,需要进行数组方法的重写无法检测数组的长度的修改ProxyMDN 对 Proxy 的定义如下:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。它的语法如下:cosnt p = new Proxy(target, handler)它的两个参数:target:需要代理的目标对象,可以是任何类型的对象,包括原生数组,函数,甚至另一个代理handler:一个以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。这里列举几个 handler:handler.get(target, property, receiver):设置属性的捕捉器target:是目标对象property:是目标属性名receiver: 如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。handler.set(target, property, value, receiver):读取属性的捕捉器,如果写入成功,返回 true,否则返回 falsetarget:目标对象property:目标对象的属性value:目标对象属性的值receiver:同 get 捕捉器handler.defineProperty() :方法的捕捉器handler.has():in 操作符的捕捉器hander.apply():函数调用的捕捉器上文我们使用 Object.defineProperty 拦截对象的属性和方法,这里也可以使用 Proxy 来实现:let o = {}; let value = 'value'; // 使用 Proxy 创建 对象 o 的代理对象 p let p = new Proxy(o, { get(target, property) { // 获取对象 o 上的属性,有则返回该属性的值,如果没有则返回变量 value return property in target ? target[property] : value; }, set(target, property, value) { // 设置对象 p 的 value target[property] = value; return true; }, deleteProperty: function (target, propKey) { // 删除对象 p 上的属性 console.log(`delete ${propKey}!`); delete target[propKey]; return true; } }); // 获取对象 p 的属性 value 的 value console.log(p.value); // "value"; // 设置对象 p 的属性 b 的值为 value1 p.b = 'value1'; // "value1" console.log(p.b); // "value1" // 对象 o 的方法也被改变了 console.log(o.value); // undefined console.log(o.b); // "value1" delete p.b; // "delete b!" console.log(o.b); // "undefined"可以看到,Proxy 直接代理了 target 整个对象,并且返回了一个新的对象;能监听到属性的增加、删除操作。Proxy 还能代理数组:let numbers = []; let p = new Proxy(numbers, { set(target, prop, val) { // 拦截写入属性操作 if (typeof val == 'number') { target[prop] = val; return true; } else { return false; } } }); numbers.push(1); // 添加成功 numbers.push(2); // 添加成功 console.log("Length is: " + p.length, numbers.length); // Length is: 2 2 numbers.push("test"); // TypeError(proxy 的 'set' 返回 false) console.log("p is: " + p); // p is: 1,2,test console.log("numbers is: " + numbers); // numbers is: 1,2,test我们可以在控制台看到代理对象 p:数组的长度变化以及方法 push 都能够被监听到,而且除了除了常用的 get、set 操作外,Proxy 更是支持 13 种拦截操作,上面只列出了部分,剩余的可以去 MDN 上查阅。Proxy 中的 thisProxy 中虽然完成了对目标对象的代理,但是即使 handler 为空对象,它代理的对象中的 this 指向的是代理对象,而不是目标对象。let target = {     m() {         // 检查 this 的指向是不是 proxyObj         console.log(this === proxyObj)     } } let handler = {} let proxyObj = new Proxy(target, handler) proxyObj.m() // 输出: true target.m() //输出: false如果想要获取目标对象的 this,可以使用 Reflect,下文详细讲解。总结二、Reflect 有什么用?Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法,这些方法与 Proxy 的 handler 的方法相同。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。Reflect 有什么用?Reflect 对象上挂载了很多静态方法,它有一些对应的函数或功能符,如下表:我们可以使用 Reflect 操作数组:const arr1 = []; Reflect.set(arr1, 0, 'first'); Reflect.set(arr1, 1, 'second'); Reflect.set(arr1, 2, 'third'); console.log(arr1); // ["first","second","third"]我们还可以搭配 Proxy 使用:let obj = {}; const proxy = new Proxy(obj, { get(target) { console.log("get name"); return Reflect.get(target, name); }, deleteProperty(target, name) { console.log("delete" + name); return Reflect.deleteProperty(target, name); } }); proxy.name = "Joe"; console.log(proxy.name); delete proxy.name;执行结果如下:返回值如果我们把 obj 的 name 改为可读,configurable 默认是 false,因此只设置 get 方法:let obj = {} Object.defineProperty(obj, name, { get () { return "Joe" }, // configurable: false }); console.log(obj.name = "Lily1"); // "Lily1" console.log(Reflect.set(obj, name, "Lily")) // false可以看到,Relfect 可以知道属性是否设置成功,而 Object.defineProperty 则不可以。Reflect 的第三个参数 receiverreceiver 是接收者的意思,表示调用对应属性或方法的主体对象,通常情况下,receiver 无需传递,但是如果发生了继承,则需要明确调用主体,需要使用 receiver。let cat = { _name: '中华田园猫', get name () { return this._name } } let baiMao = new Proxy(cat, { get (target, prop) { return target[prop]; } }) let xiaoBai = { __proto__: baiMao, _name: "小白" } console.log(xiaoBai.name); // "中华田园猫"此处,我们希望输出结果是 "小白",应该传递 receiverlet cat = { _name: '中华田园猫', get name () { return this._name } } let baiMao = new Proxy(cat, { get (target, prop, receiver) { return Reflect.get(target, prop, receiver); // A } }) let xiaoBai = { __proto__: baiMao, _name: "小白" } console.log(xiaoBai.name); // "小白"注意代码 A 处,我们使用 Reflect 并且传递了 receiver。Reflect 与传统方法的对比那么,Reflect 与传统方法的优势在哪里呢?应用我们可以使用 Reflect 实现一个最简单的观察者模式。观察者模式指得是函数自动观察数据对象,一旦对象变化,函数就会自动执行。const person = observerable({ name: 'Zhange San', age: 47, }); function print() { console.log(`${person.name}, ${person.age}`); } observe(print); person.name = 'Li Si'; // 放回目标结果: // Li Si, 47 const observe = (fn) => { // 待实现 // .... }; const observable = (obj) => { // 待实现 // ... };上面代码中,person 是观察对象,函数 print 是观察者,一旦数据发生变化,print 就会自动执行。我们需要实现观察者模式,即实现 observe 和 observable函数。下面,我们用 queuedObservers 存放观察者们,observable 用于代理目标对象,监听并拦截 set 方法,一旦目标对象发生变化,则依次调用观察者们 queuedObservers 中的函数。// 创建观察者队列 const queuedObservers = new Set(); // 添加观察对象时需要执行的函数 const observe = (fn) => queuedObservers.add(fn); // 监听对象 const observable = (obj) => new Proxy(obj, { set }); // 监听对象的 set 操作 function set(target, key, value, receiver) { // 设置对象属性的值 const result = Reflect.set(target, key, value, receiver); // 观察对象改变时,执行添加的函数 queuedObservers.forEach((observer) => observer()); return result; }完整代码如下:// 创建观察者队列 const queuedObservers = new Set(); // 添加观察对象时需要执行的函数 const observe = (fn) => queuedObservers.add(fn); // 监听对象 const observable = (obj) => new Proxy(obj, { set }); // 监听对象的 set 操作 function set(target, key, value, receiver) { // 设置对象属性的值 const result = Reflect.set(target, key, value, receiver); // 观察对象改变时,执行添加的函数 queuedObservers.forEach((observer) => observer()); return result; } const person = observable({ name: "Zhange San", age: 47 }); function print() { console.log(`${person.name}, ${person.age}`); } observe(print); person.name = "Li Si"; // Li Si, 47小练习如何实现一个简单的观察者模式?答案:见上文。
0
0
0
浏览量2012
懒人学前端

2.概念入口—入—上

一、资源处理流程资源处理流程借用 webpack 官网对 webpack 的描述: webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图会映射项目所需的每个模块,并生成一个或多个 bundle什么是bundle代码分离是 webpack 的特性之一,使用 entry 配置入口起点,会将代码分成源代码和分发代码其中源代码是开发编辑的代码,分发代码是经过 webpack 构建,可能经过转义、压缩或优化后的代码,这些代码存放于 bundle 中,可以被浏览器等环境直接运行什么是dependency graph看了上面的内容,其实我们还是不清楚 webpack 到底做了哪些事情使浏览器不支持的语法变得可以执行,而去查看源码,会发现源码中代码对我们不是特别友好,经常左跳右跳无法持续跟踪,所以针对 webpack 打包流程,总结了下面的一个简版打包代码,让我们能大体看清 webpack 做了哪些操作,以下只是简版 demo,没有强行靠近 webpack 打包结果,让我们更能清晰的梳理流程。打包步骤总结我们工程的依赖路径 index.js -> hello.js -> message.js根据 webpack.config.js 中定义的 entry 解析入口(index.js)文件,找到他的依赖递归的构建依赖关系图将所有内容打包到 webpack.config.js 定义的 output 文件在整理打包内容之前,我们先来看下我们现在项目的结构,项目名称为 webpack_demo,其中包含 webpack.config.js,package.json,dist/main.js,src/index.js,src/hello.js,src/message.js,src/bundler.js,src/complier.js。webpack.config.js 定义对象,导出项目入口出口文件const path = require('path') module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist'), filename: 'main.js' } }src/index.js 代码中导入 hello.js方法并执行import hello from './hello.js' console.log(hello())src/hello.js 代码中导入 message.js中的message参数import { message } from './message.js' export default function() { return 'hello ' + message; }src/message.js 中定义了一个message变量export const message = 'world!!!'上面的代码层层引用下来,可以在控制台输出 ‘hello world!!!’,就代表打包成功了。上面的环境已经定义完成,接下来让我们按照步骤完成打包操作:1.在src/complier.js文件中创建complier构造函数,在构造函数中获取webpack.config.js中定义的入口出口参数module.exports = class Complier { constructor(options) { const { entry, output } = options this.entry = entry console.log(options) this.output = output } }2.在 src/bundler.js 文件中引入 webpack.config.js 文件,创建 Complier 的实例化对象并传入 optionsconst complier = require('./complier') const options = require('../webpack.config') new complier(options)在命令行执行 node src/bundler.js 后,在控制台会打印出 options 内容3.拿到配置参数后,开始根据入口文件解析文件内容,解析单流程整体为:根据入口文件名称通过 nodejs 提供的方法 fs.readFileSync 读取到文件内容使用 @babel/parser 将文件内容转换成ast语法树ast 语法树中 node 节点中包含了文件依赖的文件名称,使用 @babel/traverse 方法提取出依赖的文件名称并存储到一个数组中通过 @babel/core 中的 babel.transformFromAst 方法将 ast 转换成目标浏览器可执行的代码将上述获取的参数返回个对象,对象包含文件名,依赖数组,文件可执行代码,这个对象即为一个依赖图谱中的一个节点遍历入口文件的依赖数组,由于数组中是文件名,则递归执行上述方法,直到找到所有依赖返回所有依赖对象根据上面总结内容我们在 src/complier.js 中创建一个 createAsset 方法const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const babel = require('@babel/core') ... // 开始编译,构建ast语法树 filename: ./src/index.js createAsset(filename) { // 1 // content 内容即为index.js中书写的内容 const content = fs.readFileSync(filename, 'utf-8') // 2 // ast 内容为对象,具体内容可以console.log(ast)查看 // https://astexplorer.net/ 在官网输入index.js内容即可看到对应的树 const ast = parser.parse(content, { sourceType: 'module' }) // 创建依赖对象 const dependencies = {} // 3 // 获取抽象语法树中的依赖文件名 traverse(ast, { ImportDeclaration: ({node}) => { // 获取文件的路径名如 './src/index.js' dirname='./src' const dirname = path.dirname(filename) const absPath = path.join(dirname, node.source.value) // node.source.value: .hello.js // absPath: ./src/index.js dependencies[node.source.value] = absPath } }) // 4 // 将ast转换成可执行代码 // https://www.babeljs.cn/docs/babel-core 将index.js内容直接放在官网即可看到转译后代码 const { code } = babel.transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) // 5 return { filename, dependencies, code } }入口文件的依赖关系已经定义好,接下来根据入口文件的 dependencies ,递归遍历出所有子依赖,在 src/complier.js 文件中定义 run 方法 // 拿到参数、执行、分析入口文件 run() { // 拿到入口文件的依赖 const mainAsset = this.createAsset(this.entry) const queue = [mainAsset] // 6 // 遍历对象 for (const asset of queue) { // 遍历文件的依赖文件,递归创建依赖图 Object.values(asset.dependencies).forEach(filename => { const child = this.createAsset(filename) queue.push(child) }) } // 7 return queue }命令行执行 node src/bundler.js 看下 queue 的内容如下4.依赖树已经拿到,接下来在 src/bundler.js 中获取 complier 中返回的 queue// 获取dependence graph const graph = new complier(options).run()5.在 src/bundler.js 中创建函数 bundle,解析 graph 树,定义 require 函数,定义 modules,通过 eval 函数执行依赖树中的 code,在此我们可以知道 webpack 重写了 require 函数,所以 babel 中转换的函数可以正常执行function bundle(graph){ // 得到依赖文件名的对象 let modules = {}; graph.forEach(item => { // 将文件名作为key, value为依赖文件,code为文件名对应的函数 modules[item.filename] = { dependencies: item.dependencies, code: item.code } }) modules = JSON.stringify(modules) const result = `(function(graph){ function require(filepath) { function localRequire(relativePath) { // 将代码中的require中的路径转换成dependencies存储的带文件夹名的路径 return require(graph[filepath].dependencies[relativePath]) } var exports = {} function fn(require, exports, code) { eval(code) } fn(localRequire, exports, graph[filepath].code) return exports } require('${entry}') })(${modules})` return result } const graph = new complier(options).run() // 执行bundle函数 const result = bundle(graph)命令行输出 result 内容,粘贴内容到浏览器控制台并回车执行,发现我们预期的 'hello world!!!' 已经可以正常打印6.以上我们已经拿到了编译后的代码,最后将它输出到 dist/main.js 中,在 src/bundler.js 中创建方法 createFile(),使用 fs 对象的 writeFileSync 将内容输出,在命令行执行命令后可以看到 src/main.js 中输出了对应内容function createFile(code) { fs.writeFileSync(path.join(output.path, output.filename), code) }7.下面是 src/complier.js和 src/bundler.js文件全部内容complier.js// 文件操作模块,读取文件内容 const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const babel = require('@babel/core') module.exports = class Complier { constructor(options) { const { entry, output } = options this.entry = entry this.output = output } // 拿到参数、执行、分析入口文件 run() { const mainAsset = this.createAsset(this.entry) const queue = [mainAsset] for (const asset of queue) { Object.values(asset.dependencies).forEach(filename => { const child = this.createAsset(filename) queue.push(child) }) } console.log(queue) return queue } // 开始编译,构建ast语法树 filename: ./src/index.js createAsset(filename) { const content = fs.readFileSync(filename, 'utf-8') const ast = parser.parse(content, { sourceType: 'module' }) // 创建依赖 const dependencies = {} traverse(ast, { ImportDeclaration: ({node}) => { // 获取文件的路径名如 './src/index.js' dirname='./src' const dirname = path.dirname(filename) const absPath = path.join(dirname, node.source.value) dependencies[node.source.value] = absPath } }) // 将ast转换成代码 // https://www.babeljs.cn/docs/babel-core const { code } = babel.transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) return { filename, dependencies, code } } }bundler.js// 引入配置 const fs = require('fs'); const path = require('path') const options = require('../webpack.config') const complier = require('./complier') const { entry, output } = options function bundle(graph){ // 得到以依赖文件名的对象 let modules = {}; graph.forEach(item => { modules[item.filename] = { dependencies: item.dependencies, code: item.code } }) modules = JSON.stringify(modules) const result = `(function(graph){ function require(filepath) { function localRequire(relativePath) { // 将代码中的require中的路径转换成dependencies存储的带文件夹名的路径 return require(graph[filepath].dependencies[relativePath]) } var exports = {} function fn(require, exports, code) { eval(code) } fn(localRequire, exports, graph[filepath].code) return exports } require('${entry}') })(${modules})` return result } function createFile(code) { fs.writeFileSync(path.join(output.path, output.filename), code) } const graph = new complier(options).run() const result = bundle(graph) createFile(result) 总结:通过上面的 demo,我们已经可以大概了解 webpack 的编译流程,当然 webpack 的源码功能强大且复杂,感兴趣的小伙伴儿可以自行研究。
0
0
0
浏览量2015

履历