物勒工名,以考其诚

关键词搜索结果:

Web前端开发:如何在 JavaScript 中将数字转换为字符串

JavaScript非常灵活,并提供了许多不同的方法来在数据类型之间进行转换。在这个简短的教程中,我们将了解如何在JavaScript中将数字转换为字符串。您可能希望这样做是为了使数字数据对用户更具可读性——例如,将数字显示为句子的一部分。本教程探讨了四种在JavaScript中将数字转换为字符串的方法。使用插值将数字转换为字符串插值可能是在字符串中使用数字的最易读的方式。您可以使用此方法将其插入到字符串中,而不是手动将数字转换为字符串。要使用插值,请用反引号( `)包装一个字符串。然后,在字符串中,您可以使用`${}`.例如:const number = 99;console.log(`${number} percent of people love JavaScript`); // "99% of people love JavaScript"由于正在登录到控制台的字符串是用反引号包裹的,因此您可以使用${}.您可以在下面的演示中看到示例。<!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></head><body>    <div>        <label for="number">Enter a number</label>        <input type="number" name="number" id="number" />    </div>    <p id="result"></p>       <script>        const input = document.querySelector("#number");        const resultElm = document.querySelector("#result");        input.addEventListener('keyup', function (e) {        resultElm.textContent = `${input.value} percent of people love JavaScript`        });    </script></body></html>使用字符串连接将数字转换为字符串第二种方法是字符串连接。您可以使用运算符将数字转换为字符串+。例如:console.log(10 + "USD"); //"10USD"console.log(10 + ""); //"10"<!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></head><body>      <div>        Variable Value: <span id="variableValue"></span>      </div>      <div>        Original Type: <span id="originalType"></span>      </div>      <div>        After Converting Value: <span id="afterConvertValue"></span>      </div>      <div>        Type After Converting with String Concatenation: <span id="afterConvert"></span>      </div>       <script>        const number = 10;        document.getElementById("variableValue").textContent = number;        document.getElementById("originalType").textContent = typeof number;        const numberStr = number + '';        document.getElementById("afterConvertValue").textContent = numberStr;        document.getElementById("afterConvert").textContent = typeof numberStr;    </script></body></html>尽管这种方法很有效(因为它需要最少的代码),但它会使代码的可读性降低。字符串连接警告当对多个数字使用此方法时,可能会发生意外结果。例如:const a = 2000;const b = 468;console.log(a + b + " motorway"); // "2468 motorway"由于a+b在到达字符串之前首先进行评估,因此该操作是数字加法而不是字符串连接。一旦达到字符串变量或文字,操作就变成了字符串连接。所以,结果是2468motorway。但是,请尝试将代码更改为以下内容:const a = 2000;const b = 468;console.log("it is " + a + b + " motorway"); // "it is 2000468 motorway"因为"itis"+a首先被评估,所以该+运算符用于表达式其余部分的字符串连接。a因此,它不再像前面的示例那样在and之间进行加法运算b,而是变成了两者之间的字符串连接操作。这可以使用括号来解决:const a = 2000;const b = 468;console.log("it is " + (a + b) + " motorway"); // "it is 2468 motorway"a和之间的加法b首先执行,这导致两个变量之间的加法运算。然后,字符串连接用于表达式的其余部分,因为第一个操作数是"itis"。使用toString将数字转换为字符串第三种方法是使用toString()方法。此方法适用于所有JavaScript数据类型,包括数字。它转换它所使用的数字的值并返回它。例如:const number = 10;console.log(number); // 10console.log(typeof number); // "number"const numberStr = number.toString();console.log(numberStr); // "10"console.log(typeof numberStr); // "string"此示例显示与第一种方法相同的结果。您还可以在下面演示中看到它的实际效果。<!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></head><body>      <div>        Variable Value: <span id="variableValue"></span>      </div>      <div>        Original Type: <span id="originalType"></span>      </div>      <div>        After Converting Value: <span id="afterConvertValue"></span>      </div>      <div>        Type After Converting with toString(): <span id="afterConvert"></span>      </div>       <script>        const number = 10;        document.getElementById("variableValue").textContent = number;        document.getElementById("originalType").textContent = typeof number;        const numberStr = number.toString();        document.getElementById("afterConvertValue").textContent = numberStr;        document.getElementById("afterConvert").textContent = typeof numberStr;    </script></body></html>使用字符串将数字转换为字符串第四种方法是使用String()构造函数。此函数接受要转换的变量作为第一个参数。它将参数转换为字符串并返回。例如:const number = 10;console.log(number); // 10console.log(typeof number); // "number"const numberStr = String(number);console.log(numberStr); // "10"console.log(typeof numberStr); // "string"在控制台中记录的值number及其类型时,结果分别为10和number。转换后,结果分别10为字符串和string。您可以在下面的演示中看到示例。<!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></head><body>      <div>        Variable Value: <span id="variableValue"></span>      </div>      <div>        Original Type: <span id="originalType"></span>      </div>      <div>        After Converting Value: <span id="afterConvertValue"></span>      </div>      <div>        Type After Converting with String(): <span id="afterConvert"></span>      </div>       <script>        const number = 10;        document.getElementById("variableValue").textContent = number;        document.getElementById("originalType").textContent = typeof number;        const numberStr = String(number);        document.getElementById("afterConvertValue").textContent = numberStr;        document.getElementById("afterConvert").textContent = typeof numberStr;    </script></body></html>总结本教程向您展示了四种可用于在JavaScript中将数字转换为字符串的方法。尽管这些方法在与数字一起使用时可以产生相同的结果,但在某些情况下,一种方法会比其他方法更好。String()使用and之间的主要区别在于toString()它String()适用于undefined和null值,而toString()没有。因此,如果您的值应该包含一个数字,但您希望在将其转换为字符串时安全,则可以使用String().至于字符串插值和字符串连接,它们最适合在字符串中使用数字时使用。否则,使用这些方法会降低代码的可读性。...

前端开发TIPS:模拟如何实现重载以及默认参数

前端开发TIPS:模拟如何实现重载以及默认参数。模拟实现重载以及默认参数众所周知,js是函数不支持重载和默认参数的,但是我们可以使用一些其他方法来模拟这个方法的实现。首先看一下重载的定义:函数名相同,函数的参数列表不同(包括参数个数和参数类型),至于返回类型可同可不同。以及默认参数的定义:默认参数指的是当函数调用中省略了实参时自动使用的一个值。那么如何实现这两个功能呢,一个很简单的方法就是使用arguments来进行模拟。下面先说实现重载的方法function overLoad(){//用这样的方法模拟重载    if(arguments[0]){//如果存在第一个参数        if(arguments[1]){//如果第一个参数第二个参数都存在            //to do...            alert(arguments[0]+arguments[1]);        }        else{//如果只有第一个参数            alert(arguments[0]);            //to do...        }    }    else{//如果无参        alert("null");        //to do...    }}接下来是实现默认参数的方法function defaultArg(){//用这样的方法模拟默认参数    var a = arguments[0]?arguments[0]:"hello";//第一个参数的默认值为hello    var b = arguments[1]?arguments[1]:"world";//第二个参数的默认值为world    //...    alert(a+b);}下面进行一下测试//重载测试overLoad();//nulloverLoad("hello ");//hello overLoad("hello ","world");//hello world//默认参数测试defaultArg();//hello worlddefaultArg("你好 ");//你好 worlddefaultArg("你好 ","世界");//你好 世界js的函数支持重载吗JavaScript的函数支持重载吗?对于这个问题,主要有两个点,第一,JavaScript的函数;第二,重载。首先,说一下重载。所谓重载,简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。所以说,重载主要需要两点:第一,同样的函数名。第二,不同的函数参数。明确了重载的定义之后,我们再回到JavaScript这里。追本溯源,现在一说到JavaScript,我们就可以联想到ECMAScript,即JavaScript的标准。那么,这个标准里面对函数做出了那些规范呢?首先,ECMAScript是没有函数签名的概念的,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。其次,如果在ECMAScript中定义了两个名字相同的函数,则该名字只属于后定义的函数,如下:function add(num){    return num+1;}function add(num){    return num+2;}var result = add(4);  //结果为6在上面的例子中,add()函数被定义了两次,然而,当我们调用他的时候,却直接调用了第二个函数,这说明在JavaScript中,后定义的函数会覆盖先定义的函数。说到这里,是不是就可以判定JavaScript不支持函数重载了呢?让我来介绍一下JavaScript里面的一个arguments对象。首先,ECMAScript函数的参数与其他语言的函数参数有一点不同。ECMAScript函数不介意传进来的参数个数和类型。也就是说,在你定义了函数只接受两个参数之后,你仍然可以在调用的时候传递零或多个参数。这并不会报错。原因就在于arguments对象。ECMAScript中,函数的参数始终是存放在一个数组中,而通过arguments对象,就可以访问到这个数组。所以,只需要使用length属性就可以确定调用函数时传递了多少个参数。说到这里,我们可以来尝试这样写:function add(num1, num2){    if(arguments.length == 1){        alert("你输入的只有一个数字:"+arguments[0]+" 请重新输入");    }else if(arguments.length == 2){        alert("你输入数字的和为:" + arguments[0]+arguments[1]);    }}通过这个例子,我们可以看出,通过检查传入函数中参数的数量,JavaScript函数可以做出不同的反应,这可以间接达到重载的目的。所以,JavaScript是可以模仿函数的重载的。 ...

解决前端开发报错(SyntaxError: missing : after property id)的问题

前端开发项目中,运行的时候发现报错:SyntaxError:missing:afterpropertyid。也挺恼火的,找了半天没发现问题所在。后面经过大神知道发现问题所在。分享下给大家!哪里出错了?当使用对象初始化语法创建对象的时候,需要使用半角冒号 (:)将属性键与属性值隔开。var obj = { propertyKey: 'value' };示例冒号与等号下面的代码会运行失败,原因是对象初始化语法中不允许使用等号来代替冒号。var obj = { propertyKey = 'value' };// SyntaxError: missing : after property id修复方法就是使用冒号,或者是在对象创建之后使用方括号语法来为其设定新的属性。var obj = { propertyKey: 'value' };// or alternativelyvar obj = { };obj['propertyKey'] = 'value';空属性不能像下面这样创建空属性:var obj = { propertyKey; };// SyntaxError: missing : after property id假如你需要创建一个无值属性,那么需要将它的值设置为 null。var obj = { propertyKey: null };计算得来的属性如果需要使用表达式来创建属性键,那么需要使用方括号。否则属性名称不会进行计算:var obj = { 'b'+'ar': 'foo' };// SyntaxError: missing : after property id把计算表达式放置到方括号([])中:var obj = { ['b'+'ar']: 'foo' };...

前端开发收藏:常用的JS表单验证

前端开发收藏:常用的JS表单验证。长度限制<p>1. 长度限制</p><form name=a onsubmit="return test()">  <textarea name="b" cols="40" rows="6" placeholder="不能超过50个字符!"></textarea> <br />  <input type="submit" name="Submit" value="check"> </form><script language="javascript"> function test() {  if(document.a.b.value.length>50)  {  alert("不能超过50个字符!");  document.a.b.focus();  return false;  }} </script>只能是汉字 <p>2. 只能是汉字 </p> <input type="text" onblur="isChinese(this.value)" placeholder="请输入中文!" />  <script language="javascript">  function isChinese(obj){  var reg=/^[\u0391-\uFFE5]+$/;  if(obj!=""&&!reg.test(obj)){  alert('必须输入中文!'); return false;  }  }</script>只能是英文字母 <script type="text/javascript">//验证只能是字母function checkZm(zm){  var zmReg=/^[a-zA-Z]*$/;  if(zm!=""&&!zmReg.test(zm)){  alert("只能是英文字母!"); return false;  } } </script>只能是数字 <script language=javascript> //验证只能为数字 function checkNumber(obj){  var reg = /^[0-9]+$/;  if(obj!=""&&!reg.test(obj)){  alert('只能输入数字!');  return false;  } } </script>只能是英文字母和数字 <script type="text/javascript">//验证只能是字母和数字 function checkZmOrNum(zmnum){  var zmnumReg=/^[0-9a-zA-Z]*$/;  if(zmnum!=""&&!zmnumReg.test(zmnum)){  alert("只能输入是字母或者数字,请重新输入"); return false;  } } </script>检验时间大小(与当前时间比较)<script type="text/javascript"> //检验时间大小(与当前时间比较)  function checkDate(obj){  var obj_value=obj.replace(/-/g,"/");//替换字符,变成标准格式(检验格式为:'2009-12-10')  // var obj_value=obj.replace("-","/");//替换字符,变成标准格式(检验格式为:'2010-12-10 11:12')  var date1=new Date(Date.parse(obj_value));  var date2=new Date();//取今天的日期  if(date1>date2){  alert("不能大于当前时间!");  return false;  }  }  </script>屏蔽关键字(这里屏蔽***和****)<script type="text/javascript"> function test(obj) {  if((obj.indexOf ("***") == 0)||(obj.indexOf ("****") == 0)){  alert("屏蔽关键字(这里屏蔽***和****)!"); return false;}  }  </script>两次输入密码是否相同 <script type="text/javascript"> function check(){  with(document.all){  if(input1.value!=input2.value)  {  alert("密码不一致")  input1.value = "";  input2.value = "";  }  else { alert("密码一致"); document.forms[0].submit();  } } }  </script>表单项不能为空 <script language="javascript">  function CheckForm(obj)  {  if (obj.length == 0) {  alert("姓名不能为空!");  return false;  }  return true;  alert("姓名不能为空!");  }  </script>邮箱验证<script language="javascript"> function test(obj){ //对电子邮件的验证 var myreg = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/; if(!myreg.test(obj)) { alert('请输入有效的邮箱!'); return false; } } </script>验证手机号<script type="text/javascript"> function validatemobile(mobile)  {  if(mobile.length==0)  {  alert('手机号码不能为空!'); return false;  }  if(mobile.length!=11)  {  alert('请输入有效的手机号码,需是11位!'); return false;  }   var myreg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/;  if(!myreg.test(mobile))  {  alert('请输入有效的手机号码!');  return false;  }  }  </script>验证身份证号码(需是有效身份证)<script type="text/javascript">// 构造函数,变量为15位或者18位的身份证号码function clsIDCard(CardNo) { this.Valid=false; this.ID15=''; this.ID18=''; this.Local=''; if(CardNo!=null)this.SetCardNo(CardNo);}// 设置身份证号码,15位或者18位clsIDCard.prototype.SetCardNo = function(CardNo) { this.ID15=''; this.ID18=''; this.Local=''; CardNo=CardNo.replace(" ",""); var strCardNo; if(CardNo.length==18) { pattern= /^\d{17}(\d|x|X)$/; if (pattern.exec(CardNo)==null)return; strCardNo=CardNo.toUpperCase(); } else { pattern= /^\d{15}$/; if (pattern.exec(CardNo)==null)return; strCardNo=CardNo.substr(0,6)+'19'+CardNo.substr(6,9) strCardNo+=this.GetVCode(strCardNo); } this.Valid=this.CheckValid(strCardNo);}// 校验身份证有效性clsIDCard.prototype.IsValid = function() { return this.Valid;}// 返回生日字符串,格式如下,1981-10-10clsIDCard.prototype.GetBirthDate = function() { var BirthDate=''; if(this.Valid)BirthDate=this.GetBirthYear()+'-'+this.GetBirthMonth()+'-'+this.GetBirthDay(); return BirthDate;}// 返回生日中的年,格式如下,1981clsIDCard.prototype.GetBirthYear = function() { var BirthYear=''; if(this.Valid)BirthYear=this.ID18.substr(6,4); return BirthYear;}// 返回生日中的月,格式如下,10clsIDCard.prototype.GetBirthMonth = function() { var BirthMonth=''; if(this.Valid)BirthMonth=this.ID18.substr(10,2); if(BirthMonth.charAt(0)=='0')BirthMonth=BirthMonth.charAt(1); return BirthMonth;}// 返回生日中的日,格式如下,10clsIDCard.prototype.GetBirthDay = function() { var BirthDay=''; if(this.Valid)BirthDay=this.ID18.substr(12,2); return BirthDay;}// 返回性别,1:男,0:女clsIDCard.prototype.GetSex = function() { var Sex=''; if(this.Valid)Sex=this.ID18.charAt(16)%2; return Sex;}// 返回15位身份证号码clsIDCard.prototype.Get15 = function() { var ID15=''; if(this.Valid)ID15=this.ID15; return ID15;}// 返回18位身份证号码clsIDCard.prototype.Get18 = function() { var ID18=''; if(this.Valid)ID18=this.ID18; return ID18;}// 返回所在省,例如:上海市、浙江省clsIDCard.prototype.GetLocal = function() { var Local=''; if(this.Valid)Local=this.Local; return Local;}clsIDCard.prototype.GetVCode = function(CardNo17) { var Wi = new Array(7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2,1); var Ai = new Array('1','0','X','9','8','7','6','5','4','3','2'); var cardNoSum = 0; for (var i=0; i<CardNo17.length; i++)cardNoSum+=CardNo17.charAt(i)*Wi[i]; var seq = cardNoSum%11; return Ai[seq];}clsIDCard.prototype.CheckValid = function(CardNo18) { if(this.GetVCode(CardNo18.substr(0,17))!=CardNo18.charAt(17))return false; if(!this.IsDate(CardNo18.substr(6,8)))return false; var aCity={11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江 ",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北 ",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏 ",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外"}; if(aCity[parseInt(CardNo18.substr(0,2))]==null)return false; this.ID18=CardNo18; this.ID15=CardNo18.substr(0,6)+CardNo18.substr(8,9); this.Local=aCity[parseInt(CardNo18.substr(0,2))]; return true;}clsIDCard.prototype.IsDate = function(strDate) { var r = strDate.match(/^(\d{1,4})(\d{1,2})(\d{1,2})$/); if(r==null)return false; var d= new Date(r[1], r[2]-1, r[3]); return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[2]&&d.getDate()==r[3]);} function valiIdCard(idCard){ var checkFlag = new clsIDCard(idCard); if (!checkFlag.IsValid()) { alert("输入的身份证号无效,请输入真实的身份证号!"); document.getElementByIdx("idCard").focus(); return false; }else{ alert("是有效身份证!"); } }</script>如果大家觉得有用,赶紧收藏起来吧!!!!...

Form前端开发:继续深入form 标签与FormData 的应用

前端开发:一起了解下HTML表单(form)中form-data的玩法”。今天继续以 <form> 这个标签为主题,探讨浏览器如何处理这个HTML标签,以及身为开发者的我们应该注意哪些事情。具体来说,这篇文章会包含下列几个有关于表单应用的部分:<form/>标签背后做了哪些事FormData在JavaScript当中的应用FormData与 fetch 的搭配JavaScript如何操作档案上传再谈form标签HTML的form标签里头其实有许多浏览器帮你实作的细节,开发者有时候会忽略。以下面的例子来说:<form method="POST" action="/upload" enctype="multipart/form-data">  <input type="text" name="name" />  <input type="file"  name="file" />  <button>    Submit  </button></form>在没有任何JavaScript程式码的情况下,按下Submit按钮之后浏览器会帮你做这些事情:序列化input当中的name与file栏位以POST方法送出 Content-Type:multipart/form-data 的HTTP请求读取档案并加入到请求内容中(如果档案存在)在单页应用、前端框架还不流行的时候,用form表单填写资料送出,然后重新导向到其他页面是常见的做法。但是当填写的资料变多,或是只有部分区域需要更新(例如留言等)时每次都要整页更新对使用者来说体验并不好,所以逐渐衍生出透过ajax打API,并用JavaScript动态更新资料的做法。虽然动态更新的方式的确改善了使用者体验,但是要实现一个好的表单设计却需要考虑许多细节:错误处理状态转换资料保存Accessibility很多时候只要一个环节没有做好,使用者反而还宁愿用单纯整页更新的表单标签来操作。对于像是后台应用来说,用 <form> 表单来做整页更新往往可以省下很多开发时间,甚至依赖浏览器的内建机制运作起来更加稳定。FormData在JavaScript中的应用FormData定义了一个介面方便开发者做像是key/value的应用,最常见的就是表单处理。一个 FormData 的宣告可以这样子写:const formData = new FormData()//                key     valueformData.append('name', 'Kalan');如果将form的element放到 FormData 当中,会自动将里头填写的资讯直接序列化成FormData:<form id="form" enctype="multipart/form-data" action="/upload" method="POST">  <input type="text" name="name" />  <input type="file" name="file" />  <button>Submit</button></form><script>  const formData = new FormData(document.getElementById('form'));  formData.get('name'); // 取得目前 input 的值  formData.get('file'); // 取得目前的檔案</script>打开console,发现如下显示:除此之外,如果将FormData放到fetch的body里头,浏览器会自动帮你以 multipart/form-data 的形式传送:const formData = new FormData();formData.append('name', 'Kalan');formData.append('file', new File(['Hello World'], 'file.txt', { type: 'text/plain' }))fetch('/upload', {  method: 'POST',  body: formData})在执行完上面的JavaScript程式码并观察Network的请求,可以发现尽管没有特别定义Content-Type,浏览器还是会帮我们以 multipart/form-data 的方式传送,formdata的序列化也是由浏览器完成总结这两篇文章解释了 multipart/form-data 的应用与实际使用方法。第一篇文章解释了 Content-Disposition 的含义,boundary的用途,以及multipart/form-data的请求如何构造;第二篇文章说明了在实务上我们可以怎么使用form与FormData,并透过JavaScript来做FormData的处理与档案上传。对于伺服器端来说,在网页上做档案上传的动作也是一个HTTP请求,所以伺服器端必须要根据Header当中的资讯以及 multipart/form-data 定义的格式来解析资料,才有办法正确拿到档案内容,通常这些解析都已经被框架处理掉,但是在这边要特别注明的是,在网页上传递档案没有太神奇的魔法,背后仍然是奠基在HTTP请求之上。...

前端开发:一起了解下HTML表单(form)中form-data的玩法

前端开发:收藏表单验证JS验证的应用【非插件】。但是我们今天不是讨论是否采用插件的话题,而是另一个主题:form-data。前言form表单在网页中是相当常见的应用,不只能够传输纯文字,也能够达到档案上传的功能。不过也因为form的行为跟其他传输方式较为不同,有时候也产生疑惑与误解。这篇文章试着从阅读规范理解来龙去脉后,深入理解form背后到底做了哪些事情,以及 form-data 与其他传输方式的不同之处,最后再提及HTML的 <form/> 标签背后做了哪些事情。主要涵盖下列几个重点:是什么以及为什么需要它如何理解请求格式知道form-data解决了什么问题为什么需要form-data?资料的传递需要双方对资料格式有一定的认知。在网路的世界里,我们使用protocol来规范资料传递的形式。透过HTTP的 Content-Type 标头,我们可以知道这个请求的内容是什么,进而用对应的方式解读资料。MIMEType定义了传输格式的种类:Content-Type:application/json 代表请求内容是JSONContent-Type:image/png代表请求内容是图片档其中 multipart/form-data 就属于 Content-Type 的其中之一。一般的 Content-Type 往往只能传送一种形式的资料,但在网页的应用当中我们还可能想要上传档案、图片、影片在表单里头,这样的需求促成了 multipart/form-data 规范的出现(RFC7578【https://tools.ietf.org/html/rfc7578】)form-data请求解析multipart/form-data最大的用处在于使用者可以把复数个资料格式一次传送(一个请求)出去,主要用在HTML的表单里头,或是在实作档案上传功能时使用到。接下来我们来观察一下一个 multipart/form-data 的格式长怎么样。要发送一个ContentType为 multipart/form-data 的请求,可以用HTML的form标签达成(或是使用JavaScript的FormData):<form enctype="multipart/form-data" action="/upload" method="POST">  <input type="text" name="name" />  <input type="file" name="file" />  <button>Submit</button></form>当点击Submit按钮时,浏览器会发送一个POST请求:POST /upload HTTP/1.1Host: localhost:3000Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFYGn56LlBDLnAkfdUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36------WebKitFormBoundaryFYGn56LlBDLnAkfdContent-Disposition: form-data; name="name"Test------WebKitFormBoundaryFYGn56LlBDLnAkfdContent-Disposition: form-data; name="file"; filename="text.txt"Content-Type: text/plainHello World------WebKitFormBoundaryFYGn56LlBDLnAkfd--因为网页上的请求都是基于HTTP,所以 multipart/form-data 也会是一个HTTP请求,格式被规范在RFC当中。要理解一个 multipart/form-data 请求有两个重点:知道boundary的作用知道每个格式的意义boundary的作用Content-Type:multipart/form-data;boundary=——WebKitFormBoundaryFYGn56LlBDLnAkfd在Content-Type当中,我们可以看到boundary后面接着一坨奇怪的字串。这个boundary的作用是什么呢?前面有提到,multipart/form-data的目的在于让不同格式的资料可以透过同一个请求发送,所以要有一个方式判断每个资料的界限在哪里,以queryparameter为例:a=b&c=d的 & 就是一个分界点,让电脑有办法知道什么时候分割资料。每次电脑看到这个boundary的时候就知道这个属性的资料已经读取完毕,可以开始读取下一个资料了。在规范当中并没有完全限制boundary的格式,但还是有定义长度跟允许的字元:开头是两个hypen总长度在70以内(不包含hypen本身)只接受ASCII7bit因此像 helloworldboundary 这样的字串也是完全合法的boundary。Content-Disposition在 multipart/form-data 里面,Content-Disposition的作用在于描述这个资料的格式。Content-Disposition: form-data; name="name"明了这是 form-data 里面一个field,名字为name。如果是档案的话后面还会额外加上filename,并且在下一行加入 Content-Type 来描述档案的类型:Content-Disposition: form-data; name="file"; filename="text.txt"Content-Type: text/plain空一行之后接着才是资料内容:------WebKitFormBoundaryFYGn56LlBDLnAkfdContent-Disposition: form-data; name="name"Test------WebKitFormBoundaryFYGn56LlBDLnAkfdContent-Disposition: form-data; name="file"; filename="text.txt"Content-Type: text/plainHello World------WebKitFormBoundaryFYGn56LlBDLnAkfd--范例当中我使用纯文字档上传,如果用图片档或是其他格式档案的话则会以binary显示。Content-Disposition: form-data; name="file"; filename="image.png"Content-Type: image/pngPNGIHDR¤@¬ÃiCCPICC ProfileHTSÙϽétBoô*%ôÐ{³@B!!ØPGp,¨2 cd,(¶A±a :l¨¼<ÂÌ{ë½·Þ¿ÖY÷»;ûì½ÏYçܵÏ(省略)实作一个 multipart/form-data 请求知道了 multipart/form-data 的请求格式之后,就可以自己写一个来观察看看了。在这边使用 node.js 当作范例:const http = require('http');const fs = require('fs');const content = fs.readFileSync('./text.txt');const formData = {  name: 'Kalan',  file: content,};let payload = '';const boundary = 'helloworld';Object.keys(formData).forEach((k) => {  let content;  if (k === 'file') {    content = [      `\r\n--${boundary}`,      `\r\nContent-Disposition: multipart/form-data; name=${k}; filename="text.txt"`,      `\r\nContent-Type: text/plain`,      `\r\n`,      `\r\n${formData[k]}`,    ].join('');  } else {    content = [      `\r\n--${boundary}`,      `\r\nContent-Disposition: multipart/form-data; name=${k}`,      `\r\n`,      `\r\n${formData[k]}`,    ].join('');  }  payload += content;});payload += `\r\n--${boundary}--`;const options = {  host: 'localhost',  port: '3000',  path: '/upload',  protocol: 'http:',  method: 'POST',  headers: {    'Content-Type': 'multipart/form-data; boundary=helloworld',    'Content-Length': Buffer.byteLength(payload),  },};const req = http.request(options, (res) => {});req.write(payload);req.end();实作上很简单,就只是将规范定义的格式填入requestbody而已,比较要注意的地方在于每个boundary都会以两个hypen开头,最后一个boundary则会再以两个hypen当作结尾。之后我们透过Wireshark观察封包内容是否有被正确解析:可以看到Encapsulatedmultipartpart的部分,name=Kalan与档案内容的部分都有被正确解析。这说明了几件事:multipart/form-data也是HTTP请求的一种只要符合格式不用浏览器也可以发送请求档案内容必须在伺服器端解析(请求只是将一坨binarydata传过去)application/x-www-form-urlencoded如果在表单当中使用GET方法送出,那么所有表单的内容都以urlencoded的方式被传送。举例来说,以下的HTML点击Submit按钮后会变成/upload?name=Kalan&file=filename,就算 enctype 指定 multipart/form-data 还是会以 application/x-www-form-urlencoded 的形式送出。<form enctype="multipart/form-data" action="/upload" method="GET">  <input type="text" name="name" />  <input type="file" name="file" />  <button>Submit</button></form>总结这篇文章试着从规范理解multipart/form-data,一起探讨form-data解决了哪些问题,并试着自己建立一个符合规范的 multipart/form-data 请求,进而对这个构造比较特别的HTTP请求有更深入的了解。multipart/form-data对于网页应用来说有几个好处:不同格式的资料可以透过一个请求发送可以达到使用者传送档案的需求浏览器有统一的规范可以实作对开发者来说,理解 multipart/form-data 有几个目的:知道在网页上达成档案上传的原理基于HTTP请求如何规范不同格式资料传输对于原理的掌握加快开发速度下一篇文章会以 <form> 这个标签为主题,探讨form标签与FormData的应用,以及身为开发者的我们应该注意哪些事情。请持续关注Web前端之家动态吧!...

前端开发:收藏表单验证JS验证的应用【非插件】

前端开发的应用:收藏表单验证JS验证,不用插件去实现哦。我们一起来了解下吧!应用说明所用到的三个事件:onfocus(焦点聚焦事件)、onblur(焦点离开事件)、onkeyup(按键抬起的事件)。利用事件触发函数,函数中执行校验的信息。利用checkform判断表单中的内容是否规范,如果规范submit按钮可以提交表单信息。如下图所示:接下来,我们一起看下代码吧。HTML:<form action="demo.html" onsubmit="return checkForm()">            <div>                <div class="text">                    <p>用户名</p>                    <input id="value" onfocus="shoeTips('hint','用户名长度不能小于六')" onblur="hint_hide()" onkeyup="hint()" type="text" Name="Userame" placeholder="用户名" />                    <span id="hint"></span>                </div>                <div class="text">                    <p>密码</p>                    <input id="pass_value" onfocus="shoeTips('pass_hint','密码长度不能小于六')" onblur="pass_hide()" onkeyup="checkPass()" type="password" name="password" placeholder="密码" />                    <span id="pass_hint"></span>                </div>                <div class="text">                    <p>确认密码</p>                    <input id="passpass_value" onfocus="shoeTips('passpass_hint','两次密码要一致')" onblur="passpass_hide()" onkeyup="checkPassPass()" type="password" name="password" placeholder="确认密码" />                    <span id="passpass_hint"></span>                </div>                <div class="text">                    <p>邮箱</p>                    <input id="email" onfocus="shoeTips('email_hint','邮箱格式要正确')" onblur="emailHide()" onkeyup="emailCheck()" type="email" name="email" placeholder="邮箱" />                    <span id="email_hint"></span>                </div>                <div class="text">                    <p>手机号</p>                    <input id="phone" type="text" onfocus="shoeTips('phone_hint','格式为十一位数字的手机号')" onblur="phoneHide()" onkeyup="phoneCheck()" Name="Phone" placeholder="手机号">                    <span id="phone_hint"></span>                </div>                <div class="submit">                    <input type="submit" value="提交" />                </div>            </div></form>JS代码:function shoeTips(spanId, tips) {var span = document.getElementById(spanId);span.innerHTML = tips;}/** * 校验用户名 */function hint() {var value = document.getElementById("value").value;var hint = document.getElementById("hint");if(value.length < 6) {hint.innerHTML = "用户名太短";return false;} else {hint.innerHTML = "用户名合格";return true;}} function hint_hide() {var hint = document.getElementById("hint");hint.innerHTML = "";}/** * 校验密码 */ function checkPass() {var value = document.getElementById("pass_value").value;var hint = document.getElementById("pass_hint");if(value.length < 6) {hint.innerHTML = "密码太短";return false;} else {hint.innerHTML = "密码格式合格";return true;}} function pass_hide() {var hint = document.getElementById("pass_hint");hint.innerHTML = "";}/*** * 确认密码的校验 */function checkPassPass() {var papavalue = document.getElementById("passpass_value").value;var value = document.getElementById("pass_value").value;var papahint = document.getElementById("passpass_hint");if(papavalue != value) {papahint.innerHTML = "两次密码不一致";return false;} else {papahint.innerHTML = "";return true;}} function passpass_hide() {var papahint = document.getElementById("passpass_hint");papahint.innerHTML = "";}/** * 校验邮箱 */function checkEmail(strEmail) {          var emailReg = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/;    if ( emailReg.test(strEmail) ) {        return true;    }    else {//      alert("您输入的Email地址格式不正确!");        return false;    }};function emailCheck() {var emailValue = document.getElementById("email").value;var email_hint = document.getElementById("email_hint");var flag = checkEmail(emailValue);if(flag) {email_hint.innerHTML = "邮箱格式正确";return true;} else {email_hint.innerHTML = "邮箱格式错误";return false;}} function emailHide() {var email_hint = document.getElementById("email_hint");email_hint.innerHTML = "";}/** * 校验手机号 */function checkMobile( strMobile ){//13588888888    var regu = /^[1][345678][0-9]{9}$/;    var re = new RegExp(regu);    if (re.test(strMobile)) {        return true;    }    else {        return false;    }};function phoneCheck() {var phone = document.getElementById("phone").value;var phone_hint = document.getElementById("phone_hint");var flag = checkMobile(phone);if(flag) {phone_hint.innerHTML = "手机号格式正确";return true;} else {phone_hint.innerHTML = "手机号格式错误";return false;}} function phoneHide() {var phone_hint = document.getElementById("phone_hint");phone_hint.innerHTML = "";} function checkForm() {var flag = emailCheck() && checkPass() && checkPassPass() && hint() && phoneCheck();return flag;}大家可以去试试吧。...

前端开发es6应用:匹配两个数组对象的方法

前端开发es6应用:匹配两个数组对象的方法。判断两个数组用的value是否相等  this.list = [            {              user_type: 0,              user_id: 1003,              department_id: 1,              department_name: "公司xx",              mobile: "",              realname: "廖xx",              com_name: "任公司",              label: "廖建平",              value: 1003            },            {              user_type: 0,              user_id: 1004,              department_id: 1,              department_name: "公司领导",              mobile: "",              realname: "贺金生",              com_name: "任公司",              label: "贺xx",              value: 1004            },            {              user_type: 0,              user_id: 1005,              department_id: 1,              department_name: "公司领导",              mobile: "",              realname: "李欢",              com_name: "任公司",              label: "李xx",              value: 1005            }]  this.selectData = [            {              user_type: 0,              user_id: 1003,              department_id: 1,              department_name: "公司xx",              mobile: "",              realname: "廖xx",              com_name: "任公司",              label: "廖建平",              value: 1003            },            {              user_type: 0,              user_id: 1004,              department_id: 1,              department_name: "公司领导",              mobile: "",              realname: "贺金生",              com_name: "任公司",              label: "贺xx",              value: 1004            },           ]方法一    let result = []            for (let i = 0; i < this.selectData.length; i++) {              let obj = this.selectData[i]              for (let j = 0; j < this.list.length; j++) {                let aj = this.list[j]                if (obj.value === aj.value) {                  result.push(aj)                  break                }              }            }            console.log("result", result)方法二let arr3 = this.selectData.filter(obj =>           this.list.some(obj1 => obj.value == obj1.value)         )         console.info("arr3", arr3)         this.result = arr3方法三let arr4 = []          let arr5          this.list.filter(obj => arr4.push(obj.value))          arr5 = this.selectData.filter(obj => arr4.indexOf(obj.value) !== -1)           this.result = arr5试试吧。...

前端开发TIPS:JS原始值和引用值问题

前端开发TIPS:JS原始值和引用值问题。原始值->基本类型NumberStringBooleanundefinednull存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置动态语言->脚本语言->解释型语言->弱类型语言静态语言->编译型语言->强类型语言null空值初始化组件函数销毁函数占位引用值objectarrayfunctiondateRegExp如果一个值是引用类型的,那么它的存储空间将从堆中分配。由于引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>引用值</title></head><body>    <script type="text/javascript">        var arr1 = [1,2,3,4];        var arr2 = arr1;        //arr1.push(5);//此时打印arr2为1,2,3,4,5        arr1 = [1,2];//重新赋值不会影响arr2        document.write(arr2);    </script></body></html>...

看到“游戏开发大神毛星云离世”的消息后,作为Web前端开发或后台程序员的我们需要思考些什么?

前端开发程序员【昂不是大神】,是深有体会。在IT行业外的很多朋友,经常会给IT技术的人贴上一些标签:IT男、宅、地中海(秃顶),找不到女朋友等等。的确,作为开发人员,压力非常大,整天对着电脑,看着一堆的代码,超负荷的脑力劳动。至于加班,那是常有的事情,有时候还会通宵,再好的身体也会被拖垮。面对压力,程序员应该需要思考什么?面对长时间、超负荷的脑力劳动,程序员们需要大量耗费自己的身体机能;一旦你没有调整好,就会出现问题,造成严重的后果。所以我们需要采取一些措施去缓解压力。接下来分享下我的一些经验。上面是我的在KEEPAPP上的数据,我觉得运动能够大大减少你身体的负荷,很大程度缓解你的精神压力,并且运动完后,你的工作效率会提高很多。大家可以参考一下我的减压方法:1、每周跑步2-3次,如果你肺活量不够,可以先尝试快走+慢跑,慢慢就会习惯。2、有时候加班太晚,回到家可以做做简单的锻炼,不但可以释放一些压力,还可以达到健身的效果,我每天下班很累,回到住处,我一般会利用烧水洗澡这段时间做一些俯卧撑、蹲起和仰卧起坐,直到满头大汗,然后美美的洗个热水澡,那时候感觉太舒服了,什么压力都忘光了。3、平时多和朋友聊聊天,这样有利于缓解自己的工作压力,如果你有一两个知心朋友,那你们可以互相抱怨一下工作的压力,不要挤满在自己的内心,我们应该把这些压力释放掉一些,剩下的压力把它转化为动力,我每次和我的好朋友倾述完我都觉得轻松了不少,我觉得这个释放压力的方法还是可以的,但我不建议你经常抱怨生活,抱怨工作,你偶尔抱怨一下还是可以的。4、骑行-我住在深圳,交通压力也大,所以绿色出行非常不错,平时约几个朋友,一路骑行,观赏美丽风景,偶尔会度假。回来后,发现心情好太多了。总结作为程序员,需要比别人花费更多的时间很精力去应付我们的工作。超负荷的工作压力随时都会压垮我们的身体,所以锻炼是最好的方法。俗话说:身体是革命的本钱,身体没了,什么也没了。最后在此也提醒大家多锻炼,保护好自己的身体,钱是赚不完的。...

前端开发:分享js创建对象的基本应用

javascript是一种“基于prototype的面向对象语言“,与java有非常大的区别,无法通过类来创建对象。那么,既然是面象对象的,如何来创建对象呢?接下来我们一起讨论下js创建对象的几种方式和对象方法。GOOOO!工厂模式:工厂是函数的意思。工厂模式核心是定义一个返回全新对象的函数。function getObj(name, age) { let obj = {} obj.name = name obj.age = age return obj}let person1 = getObj("cc", 31)缺点:不知道新创建的对象是什么类型构造函数模式:通过一个构造函数,得到一个对象实例。构造函数和工厂模式区别是:1,构造函数函数体加了this2,构造函数没有return3,构造函数调用时,用new关键字 function CreateObj(name, age) {  this.name = name  this.age = age } let person1 = new CreateObj("cc", 31) console.log(person1) console.log(person1.constructor === CreateObj); // true console.log(person1 instanceof CreateObj); // true关于构造函数的两个问题:1,构造函数和普通函数唯一区别是调用方式。构造函数要用new关键字。如果不用new,则是往Global对象上加属性。下面例子中,CreateObj方法,往window对象上加了name和age属性。 function CreateObj(name, age) {  this.name = name  this.age = age } CreateObj('cc', 10) console.log(window.name) // 'cc'2,构造函数存在的问题:构造函数体内的方法,每次创建一个实例,都会创建一遍。person1.sayName( ) === person2.sayName( ) // false解决方法是,将sayName定义在createObj外面。 function sayName() {  console.log(this.name) } function CreatePerson(name, age) {  this.name = name  this.age = age  this.sayName = sayName } let person1 = new CreatePerson('joy', 31) person1.sayName()但是,这样会让自定义类型引用的代码不能很好聚在一起。原型模式:原理是,每个函数都有一个prototype属性。prototype是一个对象,里面定义的属性和方法会被所有实例共享。关于原型模式,有两个等式: function Person() { } let person1 = new Person() console.log(person1.__proto__ === Person.prototype)  // true console.log(Person.prototype.constructor === Person); // true关于原型对象的三个方法:isPrototype,getPrototypeof,setPrototypeOf,Object.create()。// isPrototypeOf判断构造函数的原型对象是否是实例的原型对象function Person() {} let person1 = new Person() console.log(Person.prototype.isPrototypeOf(person1)); // true// 获取对象的原型对象 function Person() {} let person1 = new Person() console.log(Object.getPrototypeOf(person1) === Person.prototype);// 将某个对象,设为另一个对象的原型对象 let person1 = {name: "cc"} let person2 = {age: 32} Object.setPrototypeOf(person1,person2) console.log(person1.name, person1.age); // cc 32// 以某个对象为原型对象,创建一个新对象  let person1 = {name: "cc"} let person2 = Object.create(person1) person2.age = 30 console.log(person2.name, person2.age);当访问一个对象person的name属性时,是按照以下步骤去访问:1,person上如果有name属性(即便这个属性是null,也会返回null),返回name属性值;没有,继续去原型对象Person.prototype上找2,如果原型上有name属性,返回原型上name属性值;没有,则返回undefined判断一个属性是在实例上,还是在原型上,可以用hasOwnProperty。function Person() {}Person.prototype.name = "cc"let person1 = new Person()console.log(person1.name) // 'cc'console.log(person1.hasOwnProperty("name")); // false判断一个对象上是否有个某个属性,用in操作符。// 对象自身 or 原型上找到,都返回true function Person() {} Person.prototype.name = "cc" let person1 = new Person() console.log("name" in person1) // true console.log(person1.hasOwnProperty("name")); // false访问对象的属性的方法:Object.keys( )for ... in // 继承属性也会遍历出来Object.getOwnPropertyNames(obj) // 会列出可枚举和不可枚举属性,其他和 Object.keys( )结果一样Object.getOwnPropertySymbols(obj) // 和getOwnPropertyNames类似,只是仅针对symbolReflect.ownKeys(obj) // 和Object.keys( )结果一样其他访问对象属性和属性值的方法:Object.values()对象值组成的数组,会省掉Symbol型。Object.entries()对象键值对组成的数组,会将键,转化成字符串,省掉Symbol型。 function Person() {} Person.prototype.name = "cc" let person = new Person() person.age = 21 let sy = Symbol('sy') person[sy] = 'smile' console.log(Object.values(person)) // [ 21 ] console.log(Object.entries(person)) // [ [ 'age', 21 ] ]看完以上内容,您学会了吗?...

前端开发数组操作:实例分析find,some,filter,reduce应用以及区别

前端开发数组过滤器:filter()的使用笔记今天我们继续学习下javascript中关于数组的应用方法:find,some,filter,reduce的区别和用法。区分清楚Array中filter、find、some、reduce这几个方法的区别,根据它们的使用场景更好的应用在日常编码中。Array.findArray.find返回一个对象(第一个满足条件的对象)后停止遍历const arrTest = [    { id: 1, name: "a" },    { id: 2, name: "b" },    { id: 3, name: "b" },    { id: 4, name: "c" }] // 过滤条件function getName(val) {    return arrTest => arrTest.name === val}// 如果我们是想找到第一个满足条件的数据,应该使用`Array.find`console.log(arrTest.find(getName("b")))// { id: 2, name: "b" }Array.someArray.some返回是否满足条件的布尔值const arrTest = [    { id: 1, name: "a", status: "loading" },    { id: 2, name: "b", status: "loading" },    { id: 3, name: "b", status: "success" }] // 过滤条件function getStatus(val) {    return arrTest => arrTest.status === val}// 如果我们需要查找一个数组中是否存在某个数据的时候,使用Array.some直接拿到结果console.log(arrTest.some(getStatus("success")))// trueArray.filterArray.filter遍历整个Array返回一个数组(包含所有满足条件的对象)const arrTest = [    { id: 1, name: "a", status: "loading" },    { id: 2, name: "b", status: "loading" },    { id: 3, name: "b", status: "success" }]// 过滤条件function getStatus(val) {    return arrTest => arrTest.status === val} // 如果我们是需要过滤出一个数组中所有满足条件的数据,应该使用Array.filterconsole.log(arrTest.filter(getStatus("loading")))// [//   { id: 1, name: "a", status: "loading" },//   { id: 2, name: "b", status: "loading" }// ]Array.reduceArray.reduce为数组的归并方法,使用场景很多,比如求和、求乘积,计次,去重,多维转一维,属性求和等...本节示例主要实现Array.reduce对一组数据进行条件过滤后,返回一个新的数组。const arrTest = [    { id: 1, status: "loading" },    { id: 2, status: "loading" },    { id: 3, status: "success" }]console.log(    arrTest.reduce((acc, character) => {        return character.status === "loading"            ? acc.concat(                  Object.assign({}, character, { color: "info" })              )            : acc    }, []))// [//   { id: 1, status: "loading", color: "info" },//   { id: 2, status: "loading", color: "info" }// ]与Array.filter返回的数组的不同,filter返回的是原数组中符合条件的对象集合,filter与Array.map结合也可以实现上面的结果,为什么使用reduce更好呢?// Array.map 和 Array.filter 组合console.log(    arrTest        .filter(character => character.status === "loading")        .map(character =>            Object.assign({}, character, { color: "info" })        ))// [//   { id: 1, status: "loading", color: "info" },//   { id: 2, status: "loading", color: "info" }// ]总结同时使用Array.filter和Array.map的时候,对整个数组循环了2遍。第一次是过滤返回一个新的数组,第二次通过map又构造一个新的数组。使用了两个数组方法,每一个方法都有各自的回调函数,而且filter返回的数组以后再也不会用到。使用Array.reduce同样的结果,代码更优雅。...

前端开发数组过滤器:filter()的使用笔记

前端开发数组过滤器:filter()的使用笔记。filter()方法会创建一个新数组,数组中的数据是经过指定数据中过滤出来的符合条件的数据filter()的两大特点1.filter()不会对空数组进行检测2.filter()不会改变原数组filter()方法的用法:array.filter(function(currentValue,index,arr), thisValue)//currentValue:当前元素的值//index:当前元素的下标//arr:原数组用法案例获取数组中符合条件的元素const school = [  {    occupation:"老师",    age:40  },  {    occupation:"学生",    age:23  },{    occupation:"程序猿",    age:1  }]var newShool = school.filter(item => item.age > 20)console.log(newShool);//[ { occupation: '老师', age: 40 }, { occupation: '学生', age: 23 } ]...

前端开发每日一学:总结下数组常用方法

前端开发每日一学:总结下数组常用方法。数组元素增添和删除操作数组头部unshift(value)变异方法 数组头部插入一个元素shift()变异方法 移出数组头部的第一个元素操作数组尾部push(value) 变异方法 向数组尾部压入一个元素pop() 变异方法 弹出数组尾部一个元素任意位置增删splice(start,length[…,value]) 变异方法第二个参数为个数,不为0的时候表示需要从start开始删除length个元素支持增删一起做。数组拼接concat(…arr) 字符串有该类似方法 数组转换成字符串join(str)以指定字符进行拼接将数组转换成字符串toString()以","号进行分割将数组转换字符串截取数组slice(start[,end])支持负数 字符串有该类似方法 数组排序方法排序sort(callback)变异方法 对数组进行排序回调函数返回a-b为从大到小进行排序,b-a则为从小到大进行排序。//callback回调函数格式function callback(a,b) {    return a-b}顺序翻转reverse()变异方法 对数组的顺序进行反转数组迭代方法数组遍历查找返回值为下标indexOf(targetValue)从左到右数组进行遍历,查找目标值的所在下标,返回第一次出现的下标值.lastIndexOf(targetValue)从右到左对数组进行遍历,查找目标值所在下标,返回第一次出现的下标值.findIndex(callback)通过回调函数对数组元素进行条件判断,返回第一次满足条件的元素下标值返回值为内容find(callback)通过回调函数对数组元素进行条件判断,返回第一次满足条件的元素值 数组遍历处理callback(value[,index[,array]])//callback回调函数格式function callback( value, index, array) {//处理...return ... //此处是否不需要retrun语句下面对其进行标注}forEach(callback)对数组进行遍历处理仅处理无返回值。callback不需要return语句map(callback)遍历数组每一个元素使用callback对数组进行处理,并且将callback处理过的返回值加入到新数组并返回新数组。filter(callback)对数组进行过滤操作,返回callback返回值为true的元素组成的新数组。every(callback)对数组进行遍历,如果callback返回值都为true,则方法的返回值为truesome(callback)对数组进行遍历,如果callback返回值有一个为true,则方法的返回值为truecallback(total,value[,index[,array]])//callback回调函数格式function callback(total, value, index, array) {  return total + value;}reduce(callback[,初始值])从左往右数组依次缩短,同时上一次处理的结果total传入下次回调函数的作为参数供回调函数使用reduceRight(callback[,初始值])从右往左数组依次缩短,同时上一次处理的结果total传入下次回调函数的作为参数供回调函数使用数组其他方法includes(value) ES6 判断数组是否包含某个元素。...

前端开发:可枚举属性和可迭代对象

前端开发中关于Javascript数据遍历循环的两个属性:可枚举属性和可迭代对象。可枚举的属性可枚举对象的一个定义特征是,当我们通过赋值运算符将属性赋值给对象时,我们将内部可枚举标志(enumerable)设置为true。这是默认值。但是,我们可以通过将其设置为false来更改此行为。经验法则是,可枚举属性总是出现在for...in循环中。让我们看看这一点:const users = {}users.languages = 'JavaScript'Object.getOwnPropertyDescriptor(users, 'languages')// output -> { value: 'JavaScript', writable: true, enumerable: true, configurable: true }// 在循环中对我们使用的属性进行更多的控制Object.defineProperty(users, 'role', { value: 'Admin', writable: true, enumerable: false })for (const item in users) {  console.log(item) // languages}可以看到,我们为users变量添加了一个languages属性,使用Object.getOwnPropertyDescriptor方法输出languages属性描述符的enumerable属性为true。使用Object.defineProperty为添加role属性,并将enumerable设置为false,在for...in循环中并没有输出role属性。即for...in循环中的属性为可枚举属性。可迭代对象如果一个对象定义了它的迭代行为,那么它是可迭代的。在本例中,将在for...of构造中循环的值将定义其迭代行为。可迭代的内置类型包括Array、String、Set和Map对象不可迭代,因为它没有指定@iterator方法。基本上,在JavaScript中,所有可迭代对象都是可枚举对象,但并非所有可枚举对象都是可迭代对象。这里有一种概念化的方法:for...in查找数据中的对象,而for...of查找重复序列。让我们看看这一切在与Array数据类型一起使用时的效果:const languages = ['JavaScript', 'Python', 'Go']// 与 for...in 循环一起使用for (const language in languages) {  console.log(language)}// output// 0// 1// 2// 与 for...of 循环一起使用for (const language of languages) {  console.log(author)}// output -> JavaScript Python Go在使用这种构造时,需要牢记的一点是,如果调用了typeof,并且输出为object,那么您可以使用for...in循环。让我们看看这个对languages变量的操作:typeof languages // "object" -> 因此我们可以在中使用 for乍一看,这可能令人惊讶,但需要注意的是,数组是一种特殊类型的对象,以索引为键。知道for...in将在构造中查找对象可以极大地帮助我们。当for...in循环找到一个对象时,它将在每个键上循环。我们可以将for..in循环在languages数组上的方式可视化如下:const languages = {  0: 'JavaScript',  1: 'Python',  2: 'Go'}注意:如果它可以被追踪到一个对象(或者从对象原型链继承它),for...in将以没有特定顺序遍历键。同时,如果它实现了一个迭代器for..of构造,它将在每次迭代中循环遍历该值。在forEach与map方法虽然forEach和map方法可以用来实现相同的目标,但它们的行为和性能特性有所不同。在基本级别,当函数被调用时,它们都会接收一个回调作为参数。考虑以下片段:const scoresEach = [2, 4 ,8, 16, 32]const scoresMap = [2, 4 ,8, 16, 32]const square = (num) => num * num让我们详细介绍一下它们在操作上的一些差异。forEach返回undefined,而map返回一个新的array:let newScores = []const resultWithEach = scoresEach.forEach(score => {  const newScore = square(score)  newScores.push(newScore)})const resultWithMap = scoresMap.map(square)console.log(resultWithEach) // undefinedconsole.log(resultWithMap) // [4, 16, 64, 256, 1024]Map是一个纯函数,同时forEach执行一些突变:console.log(newScores) // [4, 16, 64, 256, 1024]在我看来,map支持函数式编程范式。我们不必总是执行突变来获得期望的结果,不像forEach,我们必须突变newScores变量。在每次运行时,当提供相同的输入时,map函数将产生相同的结果。同时,forEach对应项将从上一个突变的先前值中提取。链式调用使用map可以进行链式调用,因为返回的结果是一个数组。因此,可以对结果立即调用任何其他数组方法。换句话说,我们可以调用filter、reduce、some等方法。这在forEach中是不可能的,因为返回的值为undefined的。性能map方法的性能往往优于forEach方法。检查使用map和forEach实现的等效代码块的性能。平均而言,您将看到map函数的执行速度至少快50%。总结在上面讨论的所有循环结构中,给我们最多控制的是for..of循环。我们可以将其与关键字return、continue和break一起使用。这意味着我们可以指定对数组中的每个元素要发生什么,以及是否要提前离开或跳过。...

Vue前端开发:如何实现多语言的功能【vue-i18】

随着业务不断增长,大大小小的公司都会尝试去拓展市场,尤其是国外,所以产品本身需要增强功能,例如:增加一个国外版的前后台网站等等。今天我们分析下如何在vue框架里,实现多语言的功能,我们熟悉的就是vue-i18插件,一起来学下如何去构建这个工程吧。总的来说,一个Web应用中,需要做多语言切换的内容常见的包括如下方面:1、模板中的内容,如Vue.js的<template>标签中的文字内容2、JS代码中的文字内容3、图片中的文案内容4、页面title5、第三方组件中的文案(比如,我的项目中用到了Vux的组件)6、后端接口中需要展示到前端的数据内容7、后端接口返回的错误提示思路1、首先,需要确定以什么样的方式来获取到当前应该展示何种语言我采用的是用URL传递?lang=en或者?lang=zh-CN这样的传递参数的形式。这样做的好处在于可以通过链接指定用哪种语言。但是,只依赖于地址栏参数也是不方便的。比如,在页面跳转的时候,这个地址栏参数可能就丢失了。这会导致你在页面跳转之后就不知道该用哪种语言展示了。而理想的的方式应该是,进入某个页面的时候带有这个参数(这个时候就获取到该使用何种语言了),等再跳转到其它页面的时候就不必再带这个lang参数了,因为此时你已经知道该用哪种语言了。所以,应该在一进入第一个页面的时候就把这个参数存下来,比如,存在localstorage中,存在vuex的state中。这里,就引出来一个语言判断的优先级问题。因为地址栏里可能有lang参数,localstorage中可能也有相关的存储字段(因为上次访问过本应用),你可能还想设置默认的降级语言,等等。其优先级应该如何处理呢?正确的优先级应该是:先看地址栏参数中有没有;再看localstorage中有没有;然后再通过navigator.language获取浏览器默认语言,看是否是你的应用所支持的语言,若是,则采用之;最后才是使用回退语言(例如,比较通用的英语)。当然,你可以根据你的需求来做一些简化。2、其次,采用什么工具来解决语言转换和打包的问题?(1)i18n相关工具的选择——由谁来提供多语言转换函数(通常是$t)?目前国际化通用方式多数基于i18n,我们也无需再去造轮子了。但就i18n的具体使用上,有很多不同的NPM模块。比如vuex-i18n、vue-i18n、simplest-i18n等。因为多数复杂一点的项目都会上vuex,所以复杂一点的项目选择vuex-i18n会比vue-i18n更方便。而simplest-i18n这个很小众的模块,其实也有它的好处。它支持下面这样的写法:在模板中:<span>$t('真实姓名', 'Real Name')</span>或者在JS中:this.$t('真实姓名', 'Real Name')即将语言写在一起,$t函数的每一个参数都是一种语言,一目了然,还是比较方便阅读的。对小项目来说,不失为一种选择。其基本使用如下:t.js文件:import i18n from 'simplest-i18n';import getLang from '../../getLang'; const t = i18n({  locale: getLang.lang, // 当前语言  locales: getLang.langs // 支持的语言列表});export default t;然后在应用的入口文件中对Vue.js进行扩展:import t from './t';Vue.$t = Vue.prototype.$t = t;这样就把$t这个方法挂载到了Vue.js的全局。Vue实例中也可以通过this.$t访问到,使用上还是非常简单的。但是,对于大项目来说,把语言包都写在代码里面,对维护并不友好。而且,靠它也解决不了我所用到的Vux组件的多语言化的问题。所以最终,我选择了vuex-i18n作为基础。(2)组织和处理语言包的工具——语言包怎么组织,怎么打包处理?对于这个问题,我首先需要解决Vux第三方组件的多语言化问题。首先,在语言包的组织方面,比较常见的是写成JSON配置文件。不过,我最终采用了Yaml这种格式,它支持将多语言字段写在一起。比如:config.ymlconfirm:  zh-CN: 确认  en: confirm而不是像下面那样将一个字段的多语言拆成几处,比如:confirm:确认confirm:confirm这样带来的好处就是,可以方便地对照一个字段的不同语言版本,而且要修改或删除某一个字段时,也可以在一处完成,无需切换。况且,Yaml文件的语法也更加简单明了,省去了JSON文件必须写双引号、不可以出现注释等诸多麻烦。其次,在语言包的打包方面,我找到了vux-loader。它可以和现有的webpack配置结合,不仅能完成Vux组件多语言配置的打包,还允许在自定义的Vue组件中使用<i18n>标签。比如,在自定义组件中我可以这么写:<i18n>confirm:  zh-CN: 确认  en: confirm<i18n>打包时,vux-loader会将<i18n>标签中的多语言配置信息导出至我们所配置的一个Yaml文件中,而把<i18n>标签从我们的自定义组件中移除。那么,对于Yaml文件如何处理呢?可以用json-loader和yaml-loader。它们可以将Yaml文件转换成我们所需要的json格式,方便在JS函数中使用,就像这样:constcomponentsLocales=require('json-loader!yaml-loader!../../locales/components.yml');//这就得到了一个语言包的json格式3、如何通知后端接口返回何种语言的数据?因为涉及到许多接口都要通知后端采用哪种语言,所以,我选择了使用header头的方式。在axios的interceptor中给请求统一添加了header头:Accept-Language,并把这个值的内容设置成前端所获得应使用的语言(如,zh-CN或en等)。这样,就集中在一处把这个问题处理掉了。实践1、获取当前应该采用何种语言的getLang模块的实现import { getQueryObj } from '../utils/url';import { setItem, getItem } from '../utils/storage';const langs = ['zh-CN', 'en']; // 支持哪些语言const defaultLang = 'en'; // 默认语言,暂时并没有对外抛出function getLang() {  let queries = getQueryObj();  let storeLang = getItem('lang');  let rawLang;  let flag = false;  if (queries && queries['lang']) {    rawLang = queries['lang'];    setItem('lang', rawLang);  } else {    rawLang = storeLang || navigator.language;  }  langs.map(item => {    if (item === rawLang) {      flag = true;    }  });  return flag ? rawLang : defaultLang;}const lang = getLang(langs, defaultLang);export default {    lang, // 获取到当前语言    langs // 所支持的语言列表}2、Vux组件的多语言包的配置可以从Vux的官方github中找到src/locales/all.yml拷贝过来(同一目录下的src/locales/zh-CN.yml、src/locales/en.yml分别是其中文部分和英文部分),根据你自己的需要略作修改即可。然后在你的应用的应用的入口文件中引入:const vuxLocales = require('json-loader!yaml-loader!../../locales/all.yml');3、vux-loader的配置webpack.dev.conf.js中:resolve(vuxLoader.merge(devWebpackConfig, {    plugins_dir: [        'vux-ui',        {            name: 'i18n',            vuxStaticReplace: false,            staticReplace: false,            extractToFiles: 'src/locales/components.yml',            localeList: ['en','zh-CN']        }    ]}))webpack.prod.conf.js中:resolve(vuxLoader.merge(buildWebpackConfig, {    plugins_dir: [        'vux-ui',        {            name: 'i18n',            vuxStaticReplace: false,            staticReplace: false,            extractToFiles: 'src/locales/components.yml',            localeList: ['en','zh-CN']        }    ]}))其中的localeList:['en','zh-CN']就是指定你的应用支持哪几种语言。而extractToFiles:'src/locales/components.yml'就是指定你的自定义组件中所用到的那些<i18n>标签中的语言包信息,应该导出到哪个Yaml文件中。也就是说,你在各个自定义组件中使用的<i18n>标签中的语言包信息都会被vux-loader集中抽取到这个文件中。然后在应用的入口文件中引入这个语言包文件:const componentsLocales = require('json-loader!yaml-loader!../../locales/components.yml');4、自定义组件内外文案的多语言化(1)对于自定义组件内部的文案的多语言化信息,写在组件的<i18n>标签中即可。同时,为了避免不同的自定义组件中多语言字段的命名冲突,在每个字段的名字前面加上以组件名-式的前缀。(2)对于页面的标题、一些错误提示等文案,它们是出现在组件之外的,因此不适合写在组件的<i18n>标签中,所以我们单独新建一个global.yml来存放这些全局性的多语言信息。这些内容直接写在global.yml中即可,并且,为了表面与其它的语言包字段相冲突,我们在每个字段的前面加上global-前缀。然后在应用的入口文件中引入这个语言包文件:const componentsLocales = require('json-loader!yaml-loader!../../locales/global.yml');5、vuex-i18n的实现在src/store/index.js文件中:import VuexI18n from 'vuex-i18n';exportdefaultnewVuex.Store中增加:i18n: VuexI18n.store在应用的入口文件中:import VuexI18n from 'vuex-i18n';import getLang from '../../getLang';Vue.use(VuexI18n.plugin, store);const vuxLocales = require('json-loader!yaml-loader!../../locales/all.yml');const componentsLocales = require('json-loader!yaml-loader!../../locales/components.yml');const finalLocales = {  'en': Object.assign(vuxLocales['en'], componentsLocales['en']),  'zh-CN': Object.assign(vuxLocales['zh-CN'], componentsLocales['zh-CN'])}for (let i in finalLocales) {  Vue.i18n.add(i, finalLocales[i])}Vue.i18n.set(globalVars.lang);6、图片的多语言化对于图片中的文案信息,多语言化主要有这么两种方式:一是根据不同的语言展示不同的图片;二是尽将文字从图片背景中分离出来,采用文字层加背景图片层的方式,这样文字层就可以作为普通文本来实现多语言化了。都比较简单,不再赘述。7、在当前页面通过按钮切换当前语言后,如何更新当前页面的内容?如果你的应用并不需要在页面内部切换语言版本,那么直接通过URL中传入不同的lang参数就可以了,并不涉及到此问题。第一种方式:刷新页面<button @click="changeLang('zh-CN')">中文</button><button @click="changeLang('en')">英文</button>changeLang(lang){    location.href = this.$utils.url.replaceParam(this.$router.history.current.path, 'lang', lang);},第二种方式:watch当前页data中lang字段的变化,通过v-if局部刷新某些相关组件:data(){    return {        lang: this.$i18n.locale()    }}changeLang(lang){    this.$i18n.set(lang);    this.lang = this.$i18n.locale();},watch: {    lang(newVal, oldVal) {        if(newVal === oldVal) {            return;        }        // 在这里通过改变某个标志位 结合 v-if 来触发某个局部组件的重新渲染    }}第三种方式:结合vuex派发全局的语言状态,接收到状态变化时进行更新,或者自己简单地改写vuex-i18n的实现。这种方式相对复杂一些。具体根据自己的业务需求选择。8、Yaml中特殊字符的转义对于一些包含特殊字符的yaml键值,比如[、]等,需要进行转义。转的方式是给键值加上单引号引起来。如果你的语言包信息中有单引号,则必须连续使用两个单引号转义。例如:str: 'labor''s day'总结上述分享的只是关于多语言基础功能,更加深入的知识,需要大家自己去探讨,如有任何问题可以留言或者加QQ群咨询。学习参考:https://v3.vuejs.org/https://www.npmjs.com/package/vuex-i18nhttps://mp.weixin.qq.com/s/WbDid7tmz9eyQA8Uu0a0-g...

前端开发:使用React和Tailwind CSS构建网站

本教程展示了如何使用React和TailwindCSS创建产品网站。我们将介绍如何使用创建React应用程序配置覆盖(CRACO)使用TailwindCSS设置React ;Tailwind的CSS实用程序类和变体以及如何使用它们;如何轻松使网站暗模式兼容;什么是群体;以及如何启用变体。先决条件在开始之前,您需要安装Node.js和npm。如果您安装了Node.js,那么您将安装npm。要检查是否安装了Node,请在命令行中运行以下命令:node -v您应该能够看到版本。对npm执行相同的操作:npm -v需要注意的是,TailwindCSS需要Node.js版本12.13.0或更高版本。如果您遇到任何一个错误,那么您必须安装Node.js。您可以按照Node网站上的安装说明进行操作,也可以按照我们的文章“使用nvm安装多个版本的Node.js ”进行操作。设置React和TailwindCSS注意:如果您不熟悉CreateReactApp,请先查看“ CreateReactApp:GetReactProjectsReadyFast ”。首先,使用以下命令创建一个React项目create-react-app:npx create-react-app react-shop然后,将目录更改为创建的项目:cd react-shop接下来,我们将安装TailwindCSS所需的依赖项:npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9我们正在安装具有PostCSS7兼容性版本的TailwindCSS,因为在撰写本文时,CreateReactApp(或CRA)不支持PostCSS8。我们也在安装autoprefixer,因为它是2.0版之后的TailwindCSS所必需的。设置CRACO通常,要配置CRA,您需要运行react-scriptseject或npmruneject使用CRA的默认脚本。但是,这非常不方便,因为它会将隐藏在CRA中的所有配置(如webpack配置、Babel配置、PostCSS配置等)弹出到您的项目中,编辑它可能会变得很麻烦或产生CRA无法解决的问题将无法再支持。这就是CreateReactAppConfigurationOverride(或CRACO)的用武之地。CRACO是一个向CRA添加简单配置层的库。与其将CRA中的所有配置都弹出到你的项目中——例如,只是向Webpack添加一些配置——所有新的配置或对原始配置的更改都将放在一个新文件中craco.config.js。CRACO允许您配置CRA,以便轻松地充分利用它。我们在这里需要CRACO来覆盖PostCSS配置并添加tailwindcss插件。所以,让我们先安装它:npm install @craco/craco使用CRA时,脚本package.json如下所示:"scripts": {  "start": "react-scripts start",  "build": "react-scripts build",  "test": "react-scripts test",  "eject": "react-scripts eject"}由于我们使用CRACO来执行默认情况下无法使用CRA执行的操作,因此我们需要更改脚本以使用CRACO来构建项目或在开发中运行它:"scripts": {  "start": "craco start",  "build": "craco build",  "test": "craco test",  "eject": "react-scripts eject"},我们已经在,和脚本中替换react-scripts了。我们没有对脚本进行任何更改。cracostartbuildtesteject接下来,craco.config.js在项目的根目录中创建CRACO配置文件:module.exports = {  style: {    postcss: {      plugins: [        require('tailwindcss'),        require('autoprefixer'),      ],    },  },}此配置文件将tailwindcss和autoprefixer插件添加到postcss.现在我们将为TailwindCSS生成配置文件:npx tailwindcss init注意:如果您使用的是Node.jsv14,则会报告有关运行此命令时抛出的错误的问题,该错误显示“找不到模块'autoprefixer'”。这将tailwind.config.js在项目的根目录中创建文件。它将包含以下内容:module.exports = {  purge: [],  darkMode: false, // or 'media' or 'class'  theme: {    extend: {},  },  variants: {    extend: {},  },  plugins: [],}以下是每个配置键的含义:purge:这用于指定TailwindCSS应该扫描的文件并查看正在使用哪些TailwindCSS类,以便它可以删除生产中所有未使用的样式和类。darkMode:这指定了项目中暗模式的行为。该值可以是media-意味着将根据暗模式媒体查询应用暗模式样式,这取决于用户操作系统的默认模式。它也可以是class,这意味着当HTML文档中的父元素具有dark类时,将应用暗模式样式。theme:这可用于修改主题的调色板、字体、断点等。我们将在本教程后面看到如何更改主题。variants:这允许您将其他变体应用于TailwindCSS的核心插件。我们将在后面的教程中看到它是如何工作的。plugins:添加插件的部分,可以添加额外的实用程序类、自定义变体、基本样式或更多。现在,我们只做两个更改。首先,我们将更改purge密钥:purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],这告诉顺风CSS翻阅所有js,jsx,ts和tsx中的文件src目录中,public/index.html文件找出哪些类将会从顺风CSS使用,并删除任何未使用的类。第二个变化是暗模式:darkMode: "media", // or false or 'class'为了在本教程中简单起见,我们将仅根据用户的操作系统偏好保留暗模式。使用TailwindCSS设置我们的React项目的最后一步是将一些TailwindCSS样式包含在src/index.css. 将此文件的内容替换为以下内容:@tailwind base;@tailwind components;@tailwind utilities;该@tailwind指令基本上将样式导入到index.css. 并且默认情况下,CRA进口src/index.css在src/index.js:import './index.css';这意味着TailwindCSS样式将应用到我们的React项目中,我们准备开始构建一个漂亮的网站!了解TailwindCSS实用程序和变体在开始编码之前,让我们了解一下TailwindCSS实用程序类和变体是什么。TailwindCSS旨在让样式组件更容易,并帮助您专注于制作可重用的组件。实用程序类是范围广泛的类,它们允许您以您能想到的任何方式设置组件的样式,而无需编写任何CSS。例如,要为<div>元素设置边框样式、更改字体大小、更改背景和文本颜色,您需要使用CSS编写如下内容:div {  border: 1px solid #f00;  font-size: 15px;  background-color: #ff007f;  color: #fff;}使用TailwindCSS,您只需使用实用程序类即可:<div class="border border-red-100 text-lg bg-red-400 text-white"></div>以下是此示例中每个类的含义:border:设置边框宽度为1pxborder-red-100:将边框颜色设置为红色(基于主题)text-lg:给出字体大小1.125rem和行高1.75rembg-red-400:将背景颜色设置为红色(基于主题)text-white:将文本颜色设置为白色您还可以使用许多其他类,以及许多不同深浅的颜色,这使主题化更容易。使用实用程序类,您几乎不需要真正编写任何CSS。好的,但是媒体查询呢?伪类呢?黑暗模式呢?可以在不必自己编写任何CSS的情况下完成这些吗?这就是变体出现的时候。变体允许您根据设备断点、元素状态或是否启用暗模式为元素添加样式。因此,以前您可能已经这样做了,以根据设备的大小更改元素的宽度:div {  width: 50%;}  @media screen and (max-width: 1024px) and (min-width: 768px) {  div {    width: 80%;  }}  @media screen and (max-width: 767px){  div {    width: 100%  }}使用TailwindCSS,它可以简单地完成如下:<div class="w-full md:w-3/4 lg:w-1/2"></div>这个应用w-1/2类(这意味着width:50%当)min-width:1025px适用于当前屏幕的宽度,应用w-3/4类(这意味着width:80%当)min-width:768px适用于当前屏幕的宽度,并应用w-full类(这意味着width:100%;)当其他变体不要再申请。这无疑使您在每个项目中必须完成的繁琐工作变得更加轻松快捷。起初,它可能看起来令人困惑,但是当您开始更多地涉足它时,您会意识到使用实用程序类和变体是如何成为第二天性的。您可以在项目的官方文档【https://tailwindcss.com/docs】中阅读有关配置Tailwind的更多信息。实现我们的组件回到我们的网站。我们正在创建一个简单的网站,它将以简洁的设计展示产品。为简单起见,我们将使用来自FakeStoreAPI【https://fakestoreapi.com/】的假数据。我们将采用示例JSON响应,而不是实际执行对API的请求,并将其放置在我们项目的JSON文件中。同样,这只是为了本教程的简单性。转到产品端点并复制响应。然后,创建文件src/data/products.json并将响应粘贴到其中。它应该是一个类似于这样的对象数组:{  "id": 1,  "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",  "price": 109.95,  "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",  "category": "men's clothing",  "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg"}让我们从实现Product组件开始。此组件将是一个卡片组件,用于显示有关产品的信息。src/components/Product.js使用以下内容创建:function Product ({product: {title, price, description, category, image}}) {  return (    <div>      <div style={{backgroundImage: `url(${image})`}}></div>      <div>        <h1>{title.substr(0, 50)}</h1>        <h3>{category}</h3>        <p>{price}$</p>        <div>{description.substr(0, 100)}</div>      </div>    </div>  );}export default Product;如您所见,该Product组件仅显示产品详细信息。我们目前还没有添加任何样式类。接下来,转到src/App.js并将内容更改为以下内容:import "./App.css";import Product from "./components/Product";import products from "./data/products.json";function App() {  return (    <div>      <div>        {products.map((product) => (          <Product product={product} key={product.id} />        ))}      </div>    </div>  );}export default App;在这里,我们将products.json文件导入为products. 然后,我们products使用Product我们之前创建的组件循环并显示每个产品。再次注意,我们没有添加任何样式类。现在让我们启动服务器。运行以下命令:npm start你会看到只有一堆文本,但没有任何样式。添加一些背景颜色让我们开始添加一些样式。首先,我们将更改页面的背景颜色。为此,我们将利用Tailwind的背景颜色类。背景颜色类的格式为bg-{color}-{numericScale},其中numericScale是可选的。默认情况下,颜色可为white,black,gray,red,blue,green,yellow,orange,indigo,purple和pink。数字标度定义了颜色的深浅,其中50最浅的深浅和900最深的深浅。例如,如果您希望背景颜色为浅红色,则可以使用bg-red-200.在我们的网站中,我们将背景颜色设置为浅灰色,因此我们将类添加bg-gray-200到最外层的<div>元素中src/App.js:return (  <div className="bg-gray-200">    <div>      {products.map((product) => (        <Product product={product} key={product.id} />      ))}    </div>  </div>);如果您现在检查网站(如果您的服务器没有继续运行,请确保再次运行它),您会看到背景已更改为浅灰色。更改内容宽度接下来我们要做的是在屏幕宽度至少为时将内容的宽度更改为屏幕实际宽度的50% 768px,但在小型设备上保持全宽。我们将利用Tailwind的宽度类,我们之前已经讨论过了。宽度类的格式为w-{size},其中size可以是0到96的范围,指的是中的值rem;像1/2或的比率3/5,或其他指百分比的比率;或关键字,如auto自动宽度或full100%宽度。要指定根据屏幕尺寸的宽度,我们使用像变种sm,md,lg等这些变体指定的需要的以应用规则的最小屏幕尺寸。在我们的例子中,由于我们希望宽度至少为的屏幕的宽度是父级的50% 768px,我们将使用md带有的变体w-1/2:return (  <div className="bg-gray-200">    <div className="md:w-1/2">      {products.map((product) => (        <Product product={product} key={product.id} />      ))}    </div>  </div>);现在宽度将更改为屏幕宽度的一半。但是,将其水平居中会好得多。为此,我们将使用Tailwind的边距实用程序类。边距类采用格式m{side}-{value},其中side是可选的,并且可以特定于元素的每一侧,例如t顶部、b底部、l左侧和r右侧,或者使用特定的水平y或垂直使用x。value可以在0到96的范围内,可以仅px用于1px或auto。不仅如此,您还可以通过添加-到类的开头来添加负边距。例如,-m-2。由于我们将元素水平居中,我们将使用mx-auto:return (  <div className="bg-gray-200">    <div className="md:w-1/2 mx-auto">      {products.map((product) => (        <Product product={product} key={product.id} />      ))}    </div>  </div>);你可以看到它是居中的。设计产品组件现在让我们继续讨论Product组件。我们还将为产品卡添加背景颜色。我们将其设置为白色,因此我们将使用bg-white. 我们还将使其全宽,因此我们将使用w-full. 为了将产品卡彼此分开,我们将使用mb-5以下方法为元素添加边距底部:return (  <div className="bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}}></div>    <div>      <h1>{title.substr(0, 50)}</h1>      <h3>{category}</h3>      <p>{price}$</p>      <div>{description.substr(0, 100)}</div>    </div>  </div>);你可以在网站上看到变化:正如您在我们的Product组件中看到的那样,在最外面的元素中,我们有两个元素,一个包含产品的背景图像,另一个包含信息。我们希望将它们并排显示。我们需要做的第一件事是将最外层的显示更改<div>为flex。为此,我们将使用Tailwind的显示类。与我们之前提到的类不同,显示类没有格式。它们只是我们想要的显示器的名称。因此,要将元素的display属性更改为flex,只需添加flex类:return (  <div className="flex bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}}></div>    <div>      <h1>{title.substr(0, 50)}</h1>      <h3>{category}</h3>      <p>{price}$</p>      <div>{description.substr(0, 100)}</div>    </div>  </div>);接下来,我们将<div>像以前一样使用宽度类更改元素的宽度:return (  <div className="flex bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}} className="w-5/12"></div>    <div className="w-7/12">      <h1>{title.substr(0, 50)}</h1>      <h3>{category}</h3>      <p>{price}$</p>      <div>{description.substr(0, 100)}</div>    </div>  </div>);如果您现在检查网站,您会看到图像和文本现在彼此相邻。添加一些间距还有很多需要解决。首先,让我们为产品信息容器添加一些填充。为此,我们将使用Tailwind的填充类。填充类与我们之前检查的边距类完全相似,除了我们使用p代替m。因此,我们将添加p-5到产品信息容器中。我们还将使用mt-4以下方法为描述容器添加一些边距:return (  <div className="flex bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}} className="w-5/12"></div>    <div className="w-7/12 p-5">      <h1>{title.substr(0, 50)}</h1>      <h3>{category}</h3>      <p>{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);我们还将为整个容器添加顶部和底部边距,src/App.js以便第一个和最后一个产品从两侧都不会正好位于页面边缘。为此,我们将添加类py-4:return (  <div className="bg-gray-200 py-4">    <div className="md:w-1/2 mx-auto">      {products.map((product) => (        <Product product={product} key={product.id} />      ))}    </div>  </div>);我们将看到该网站现在开始变得更好。改进组件的排版现在让我们稍微处理一下排版。您可以看到产品信息看起来都一样。我们无法从描述等中区分标题与类别。首先,让我们更改一些文本的颜色。为此,我们将使用Tailwind的文本颜色类。这些类的格式类似于背景颜色类,但替换b为text. 例如,要使文本的颜色为绿色,请添加类text-green-100.因此,让我们将类别的文本颜色更改text-gray-400为使其与其他文本相比略微褪色,让我们将价格文本颜色更改text-red-500为使其突出。我们还将为价格增加一个最高利润,以确保它最突出:return (  <div className="flex bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}} className="w-5/12"></div>    <div className="w-7/12 p-5">      <h1>{title.substr(0, 50)}</h1>      <h3 className="text-gray-400">{category}</h3>      <p className="text-red-400 mt-4">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);如果您现在访问该网站,您会看到文本在区分不同部分方面看起来更清晰一些:接下来,让我们更改字体大小。为此,我们将使用Tailwind的字体大小类。这些类的格式是text-{size},其中size范围从sm到9xl。我们将通过text-4xl为至少768px使用md变体的具有宽度的text-xl屏幕和较小的屏幕添加类来使价格的字体大小更大,我们将通过text-2xl为具有宽度的屏幕添加类来使标题更大至少768px也是:return (  <div className="flex bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}} className="w-5/12"></div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);文字现在看起来好多了。定位产品图片接下来,让我们修复图像以使其完全显示并正确定位背景图像。首先,我们将更改背景图像大小。为此,我们将使用Tailwind的背景尺寸类。这些类的格式是bg-{size},wheresize可以是auto,contain或cover。在我们的例子中,将bg-contain确保看到整个图像。其次,我们将更改背景重复的属性以确保图像不会重复多次。为此,我们将使用Tailwind的背景重复类。这些类的格式是bg-{repeatValue},其中repeatValue是您赋予background-repeat属性的bg-repeat-round值,或舍入值和bg-repeat-space空间值。在我们的例子中,我们将使用bg-no-repeat.第三,我们将更改背景位置属性,使图像始终居中。为此,我们将使用Tailwind的背景位置类。这些类的格式是bg-{position},position您要赋予background-position属性的值在哪里。我们将添加类bg-center:return (  <div className="flex bg-white w-full mb-5">    <div style={{backgroundImage: `url(${image})`}} className="w-5/12 bg-contain bg-no-repeat bg-center"></div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);现在,我们可以完整地看到图像。您会注意到一些图像触及容器的边缘。为了解决这个问题,我们将向<div>背景图像元素添加一个包装元素并为其添加一些填充:return (  <div className="flex bg-white w-full mb-5">    <div className="w-5/12 p-2">      <div style={{backgroundImage: `url(${image})`}} className="bg-contain bg-no-repeat bg-center w-full h-full"></div>    </div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);请注意,我们已经将之前赋予背景图像的宽度移动到包装元素,并且我们已经将w-full和添加h-full到背景图像元素以确保它采用100%其父元素的宽度和高度。添加框阴影和圆角我们的产品现在看起来好多了。我们将为当前样式添加两个最后的润色。首先,我们将为每个产品添加一些阴影。我们将使用Tailwind的boxshadow类。这些类的格式是shadow-{size},wheresize是可选的,范围从sm到2xl。也可以none是去除任何框阴影或inner将阴影置于内部。其次,我们将使产品卡的边框有点圆。我们将使用Tailwind的边界半径类。这些类的格式rounded-{position}-{size},其中size是可选的,可以从范围sm到3xl或可none为0边界半径或full使其充分圆润。position也是可选的,可以是特定位置,如t顶部或l左侧,也可以特定于某个边缘,如tl左上角。我们将添加shadow-sm到产品卡中,为其添加一个小阴影,rounded-lg并使边框变圆:return (  <div className="flex bg-white w-full mb-5 shadow-sm rounded-lg">    <div className="w-5/12 p-2">      <div style={{backgroundImage: `url(${image})`}} className="bg-contain bg-no-repeat bg-center w-full h-full"></div>    </div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);最后,我们的产品列表页面看起来像下面的截图。自定义主题到目前为止,我们所做的所有样式都基于Tailwind的默认样式。但是,Tailwind还允许我们自定义主题。我们可以更改颜色、字体系列等。所有这些更改都是在tailwind.config.js.让我们试着稍微改变一下颜色。有多种方法可以更改主题的颜色。一种方法是定义您自己的颜色。例如,要为我们的主题添加新颜色,我们可以在中执行以下操作tailwind.config.js:module.exports = {  purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],  darkMode: "media", // or 'media' or 'class'  theme: {    extend: {      colors: {        turquoise: "#40e0d0"      }    },  },  variants: {    extend: {},  },  plugins: [],};请注意,在inside中theme.extend,我们添加了一个colors对象,然后在其中添加了turquoise带有绿松石色十六进制代码的键。现在我们可以像使用默认颜色一样使用该颜色。例如,要将背景颜色设置为绿松石色,您可以使用bg-turquoise.另一种自定义主题颜色的方法是更改默认颜色。正如前面提到的,在顺风默认的颜色white,black,gray,red,blue,green,yellow,orange,indigo,purple和pink。您可以更改这些颜色的实际值。例如,要将更改yellow为更多的芥末黄,请执行以下操作:module.exports = {  purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],  darkMode: "media", // or 'media' or 'class'  theme: {    extend: {      colors: {        yellow: "#e1ad01"      }    },  },  variants: {    extend: {},  },  plugins: [],};现在,当您使用黄色的默认类时,您将获得您在此处定义的黄色。您还可以使用数字刻度为不同的颜色深浅指定值:module.exports = {  purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],  darkMode: "media", // or 'media' or 'class'  theme: {    extend: {      colors: {        yellow: {          200: "#feca1d",          400: "#e1ad01",          700: "#b48a01"        }      }    },  },  variants: {    extend: {},  },  plugins: [],};您还可以使用lightest, light, DEFAULT, dark,之类的键darkest:module.exports = {  purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],  darkMode: "media", // or 'media' or 'class'  theme: {    extend: {      colors: {        yellow: {          light: "#feca1d",          DEFAULT: "#e1ad01",          dark: "#b48a01"        }      }    },  },  variants: {    extend: {},  },  plugins: [],};添加调色板更改颜色的第三种方法是使用TailwindCSS中的其他调色板,这就是我们将要做的。首先,需要colors从tailwindcss/colors开始tailwind.config.js:const colors = require("tailwindcss/colors")接下来,我们将红色更改为玫瑰调色板,将灰色更改为蓝灰色:const colors = require("tailwindcss/colors")module.exports = {  purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],  darkMode: "media", // or 'media' or 'class'  theme: {    extend: {      colors: {        gray: colors.blueGray,        red: colors.rose      }    },  },  variants: {    extend: {},  },  plugins: [],};如果您现在查看网站,您会发现我们使用的颜色略有变化。如果你想很好地看到颜色的差异,你可以尝试将灰色改为琥珀色:const colors = require("tailwindcss/colors")module.exports = {  purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],  darkMode: "media", // or 'media' or 'class'  theme: {    extend: {      colors: {        gray: colors.amber,        red: colors.rose      }    },  },  variants: {    extend: {},  },  plugins: [],};你会看到背景现在是黄色的。您还可以更改字体系列等等,所有这些都来自tailwind.config.js,同时仍然使用TailwindCSS提供的相同类。这样,您可以轻松自定义主题以适合您的设计。添加深色模式该dark变体允许我们轻松地为暗模式设置元素样式,同时为亮模式设置样式。一开始,当我们建立我们的网站,我们改变了dark重点tailwind.config.js来media。这意味着当浏览器或操作系统设置为暗模式时将应用暗模式。如果您想测试网站在暗模式下的外观,但没有将其设置为暗模式,则可以在ChromeDevTools中进行模拟。按打开DevTools F12,然后按CTRL+ SHIFT+ P(或macOS上的CMD+ SHIFT+ P)并在出现的下拉列表中输入“显示渲染”并选择显示的选项。最后,向下滚动到“模拟CSS媒体功能首选颜色方案”并选择prefers-color-scheme:dark。通过选择,可以执行相同的操作来测试灯光模式prefers-color-scheme:light。让我们首先通过添加类dark:bg-gray-800在黑暗模式下更改网站的背景颜色src/App.js:return (  <div className="bg-gray-200 py-4 dark:bg-gray-800">    <div className="md:w-1/2 mx-auto">      {products.map((product) => (        <Product product={product} key={product.id} />      ))}    </div>  </div>);如果您现在检查并且您的浏览器/操作系统设置为暗模式(或模拟),您将看到背景颜色已更改为更深的灰色阴影。现在让我们对产品卡进行更改。我们将类添加dark:bg-gray-300到最外层元素:return (  <div className="flex bg-white w-full mb-5 shadow-sm rounded-lg dark:bg-gray-300">    <div className="w-5/12 p-2">      <div style={{backgroundImage: `url(${image})`}} className="bg-contain bg-no-repeat bg-center w-full h-full"></div>    </div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);如果您现在检查,您会注意到产品卡的背景颜色已更改,但您还会注意到图像现在看起来不太好,因为它具有白色背景。让我们在黑暗模式下为背景包装器添加一个白色背景,让它变得更好。这可以通过添加类来完成dark:bg-white。此外,类别文本颜色现在几乎不可见,因此我们将通过添加类将其更改为更暗的颜色dark:text-gray-700:return (  <div className="flex bg-white w-full mb-5 shadow-sm rounded-lg dark:bg-gray-300">    <div className="w-5/12 p-2 dark:bg-white rounded-tl-lg rounded-bl-lg">      <div style={{backgroundImage: `url(${image})`}} className="bg-contain bg-no-repeat bg-center w-full h-full"></div>    </div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400 dark:text-gray-700">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);我们网站的最终外观如下所示。为插件分组和启用变体默认情况下,并非所有插件都启用了某些变体,因为这会导致文件过大。因此,如果我们需要使用这些变体,我们必须为tailwind.config.js我们想要的插件手动启用它们。这里的插件是我们一直在使用的类。例如,背景颜色属于backgroundColor插件。未启用的变体之一是group-hover。组是一组组合在一起的元素,因此任何状态(例如悬停)都可以影响整个组。通过将group类添加到容器来声明组。然后,您可以将该group-hover变体与作为容器子元素的元素中的一个实用程序类一起使用。group-hover除非悬停组中的任何元素(即容器元素内的任何元素),否则不会应用您使用的实用程序类。我们将制作每张产品卡片a group,然后在悬停时我们将放大图像。因此,我们将group类Product添加到组件中的最外层元素,然后将以下类添加到具有背景图像的元素:transition-transform:Tailwind的过渡类之一。它仅将transition属性应用于transform更改。duration-300:Tailwind的过渡持续时间类之一。它应用atransition-duration值300ms。group-hover:transform:如上所述,group-hovervariant确保transform仅在组中的元素悬停时才应用该类。transform是Tailwind的转换类之一。它允许添加其他与转换相关的类。group-hover:scale-125:scale-125该类是Tailwind的规模类之一。它将scaleX和Y的设置为1.25,但您需要先添加transform类。使用上述类,一旦产品中的任何元素悬停,图像就会放大。我们还将将该类添加overflow-hidden到Product组件的最外层元素,以确保如果图像超出其容器,则不会溢出。我们还将使用hover:shadow-2xl使产品卡的阴影更大transition-shadowduration-300,以确保过渡是无缝的:return (  <div className="flex bg-white w-full mb-5 shadow-sm rounded-lg dark:bg-gray-300 group overflow-hidden hover:shadow-2xl transition-shadow duration-300">    <div className="w-5/12 p-2 dark:bg-white rounded-tl-lg rounded-bl-lg">      <div style={{backgroundImage: `url(${image})`}} className="bg-contain bg-no-repeat bg-center w-full h-full transition-transform duration-300 group-hover:transform group-hover:scale-125"></div>    </div>    <div className="w-7/12 p-5">      <h1 className="md:text-2xl">{title.substr(0, 50)}</h1>      <h3 className="text-gray-400 dark:text-gray-700">{category}</h3>      <p className="text-red-400 mt-4 text-xl md:text-4xl">{price}$</p>      <div className="mt-4">{description.substr(0, 100)}</div>    </div>  </div>);注意:如果您正在模拟暗模式(或使用暗模式),您可能会在亮模式下看到更好的效果,因此请务必切换到亮模式。如果您现在尝试将鼠标悬停在产品上,您会看到阴影放大并且图像放大。结论我们使用React创建了一个简洁的响应式网站,而无需编写任何CSS!这就是TailwindCSS的魅力所在。TailwindCSS消除了乏味、重复的工作或编写CSS。它还有助于创建主题,并允许您专注于创建具有时尚设计的可重用组件,这非常适合React。我们在本文中介绍的内容只是触及了您可以使用TailwindCSS轻松创建的所有美丽事物的表面。...

前端开发:JavaScript实现随机抽奖小应用

前端开发:JavaScript实现随机抽奖小应用。比如您的公司年终会举行年会,弄个幸运同事,几等奖啥的,可以拿来试试。废话不多说,直接上DEMO咯。<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>前端开发:JavaScript实现随机抽奖小应用 - Web前端之家www.jiangweishan.com</title>    <link rel="icon" href="">    <style>        .outerContainer {            margin-top: 100px;        }                .question {            margin-top: 30px;            width: 100%;            height: 50px;            line-height: 50px;            font-size: 32px;            transition: all .3s linear;            text-align: center;            font-weight: bolder;        }                .number {            margin-top: 30px;            display: block;            left: 200px;            text-align: center;        }                .number input {            height: 40px;            font-size: 20px;            line-height: 40px;            padding-left:15px;        }                .btnWrapper {            margin-top: 30px;            width: 100%;            height: 30px;            text-align: center;        }                .btnWrapper button {            border:0 none;            outline: none;            color: #fff;            cursor: pointer;            border-radius: 30px;            width: 120px;            height: 35px;            line-height: 35px;            background:#f00;            font-weight:bold;        }                .viewDiv {            margin: 50px auto;            width: 900px;            height: 300px;            text-align: center;            font-size: 30px;            line-height: 50px;            border: 1px solid #ddd;            padding:20px 0;        }                .foot {            margin: 0 auto;            text-align: center;        }    </style></head><body>    <div class="outerContainer">        <div class="question">设定中奖人数</div>        <div class="number">            <input type="text" style="color: #999;" value="请输入需要的人数" onblur="if (this.value == '') {this.value = '请输入需要的人数';this.style.color = '#999';}" onfocus="if (this.value == '请输入需要的人数') {this.value = '';this.style.color = '#424242';}">        </div>        <div class="btnWrapper">            <button>开始抽签</button>        </div>        <div class="viewDiv"></div>    </div>    <script>        var input = document.getElementsByTagName('input')[0];        var viewDiv = document.getElementsByClassName('viewDiv')[0];        var btn = document.getElementsByTagName('button')[0];        var question = document.getElementsByClassName('question')[0];        var arr = []; // 存放抽取处的学号        var count = 0; // 计数器,用以question 的颜色修改器        setInterval(function() {            var temp = count % 6;            switch (temp) {                case 0:                    question.style.color = 'red';                    break;                case 1:                    question.style.color = 'green';                    break;                case 2:                    question.style.color = 'blue';                    break;                case 3:                    question.style.color = 'grey';                    break;                case 4:                    question.style.color = 'purple';                    break;                case 5:                    question.style.color = 'black';                    break;                default:                    break;            }            count++;        }, 700);        document.onkeydown = function(e) {            // 摁下回车键 触发 btn 的onclick事件            if (e.keyCode == 13) {                btn.onclick();            }        }        btn.onclick = function() {            // 检查输入的内容是否是是1~30人            // 若是班级人数不止三十人,改成 input.value < 班级人数 + 1            var check = (function() {                if (input.value > 0 && input.value < 31) {                    return true;                } else {                    return false;                }            }());            // 如果输入的是正确的,那么进行抽签            if (check) {                var num = input.value;                arr = [];                for (var i = 0; arr.length < num; i++) {                    // 生成1 ~ 30 的随机数                    // 需要更改人数,直接修改 乘号后面的 30 未你们班需要的人数即可                    var temp = Math.floor(Math.random() * 30 + 1); // 1 ~ 30                    var flag = true;                    arr.forEach(function(value) {                        // 遍历数组,防止生成的随机数和已有的数字重复                        if (value == temp) {                            flag = false;                        }                    })                    if (flag) {                        arr.push(temp);                    }                }                // 将抽出的人数的学号进行升序排序                arr.sort(function(a, b) {                    return a - b;                })                var str = arr.join(", ");                viewDiv.innerHTML = " <span style='color : red'>恭喜以下小可爱/帅哥 被抽中!</span> </br> " + str;            } else {                // 若不是,则输出错误提示                // 人数可以修改                viewDiv.innerHTML = "<span style='color : red'>请输入正确的人数(1 ~ 30)哦</span>";            }        }    </script></body></html>试试吧!...

前端开发:js表单操作-简单计算器

前端开发的小应用:js表单操作-简单计算器。之前也分享过类似的计算器的应用。大家也可以看下:用javascript制作一个简单的计算器功能用JS来模拟下计算器DEMO功能用原生JS模拟做一个简单计算器的功能还有很多,大家可以点击链接查看:计算器。今天继续分享一种,试试。先看下图:废话不说,上代码:<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>js表单操作-简单计算器 | Web前端之家www.jiangweishan.com</title><style type="text/css">body {font-size:12px;font-family:Arial, Georgia, "Times New Roman", Times, serif;color:#555;text-align:center;background-color:#e2e2e2;}h6{margin:0;font-size:12px;}#calculator {width:240px;height:auto;overflow:hidden;margin:10px auto;border:#fff 1px solid;padding-bottom:10px;background-color:#f2f2f2;}#calculator div {clear:both;}#calculator ul{padding:0;margin:5px 14px;border:#fff 1px solid;height:auto;overflow:hidden}#calculator li{list-style:none;float:left;width:32px;height:32px;margin:5px;display:inline;line-height:32px;font-size:14px;background-color:#eaeaea;}#calculator li.tool{background-color:#e2e2e2;}#calculator li:hover{background-color:#f9f9f9;cursor:pointer;}#calculator li:active{background-color:#fc0;cursor:pointer;}#calculator li.tool:active{background-color:#d8e8ff;cursor:pointer;}#calcu-head {text-align:left;padding:10px 15px 5px;}span.imyeah {float:right;color:#ccc;}span.imyeah a{color:#ccc;}.screen{width:200px;height:24px;line-height:24px;padding:4px;border:#e6e6e6 1px solid;border-bottom:#f2f2f2 1px solid;border-right:#f2f2f2 1px solid;margin:10px auto;direction:ltr;text-align:right;font-size:16px;color:#999;}#calcu-foot{text-align:left;padding:10px 15px 5px;height:auto;overflow:hidden;}span#note{float:left;width:210px;height:auto;overflow:hidden;color:red;}span.welcome{clear:both;color:#999;}span.welcome a{float:right;color:#999;}</style><script language="javascript">//此处插入上面的js代码</script></head><body><div id="calculator"><div id="calcu-head"><span class="imyeah">&copy;&nbsp;<a href="https://www.jiangweishan.com" target="_blank">Web前端之家</a></span><h6>简单的计算器</h6></div><form name="calculator" action="" method="get"><div id="calcu-screen"><!--配置显示窗口,使用onfocus="this.blur();"避免键盘输入--><input type="text" name="numScreen" class="screen" value="0" onfocus="this.blur();" /></div><div id="calcu-btn"><ul> <!--配置按钮--><li onclick="command(7)">7</li><li onclick="command(8)">8</li><li onclick="command(9)">9</li><li class="tool" onclick="del()">&larr;</li><li class="tool" onclick="clearscreen()">C</li><li onclick="command(4)">4</li><li onclick="command(5)">5</li><li onclick="command(6)">6</li><li class="tool" onclick="times()">&times;</li><li class="tool" onclick="divide()">&divide;</li><li onclick="command(1)">1</li><li onclick="command(2)">2</li><li onclick="command(3)">3</li><li class="tool" onclick="plus()">+</li><li class="tool" onclick="minus()">-</li><li onclick="command(0)">0</li><li onclick="dzero()">00</li><li onclick="dot()">.</li><li class="tool" onclick="persent()">%</li><li class="tool" onclick="equal()">=</li></ul></div><div id="calcu-foot"><span id="note"></span><span class="welcome">欢迎使用javascript计算器!<a href="https://www.jiangweishan.com" target="_blank">反馈</a></span></div></form></div><script>     var num=0,result=0,numshow="0"; var operate=0; //判断输入状态的标志 var calcul=0; //判断计算状态的标志 var quit=0; //防止重复按键的标志 function command(num){ var str=String(document.calculator.numScreen.value); //获得当前显示数据 str=(str!="0") ? ((operate==0) ? str : "") : ""; //如果当前值不是"0",且状态为0,则返回当前值,否则返回空值; str=str + String(num); //给当前值追加字符 document.calculator.numScreen.value=str; //刷新显示 operate=0; //重置输入状态 quit=0; //重置防止重复按键的标志 } function dzero(){ var str=String(document.calculator.numScreen.value); str=(str!="0") ? ((operate==0) ? str + "00" : "0") : "0"; //如果当前值不是"0",且状态为0,则返回当str+"00",否则返回"0"; document.calculator.numScreen.value=str; operate=0; } function dot(){ var str=String(document.calculator.numScreen.value); str=(str!="0") ? ((operate==0) ? str : "0") : "0"; //如果当前值不是"0",且状态为0,则返回当前值,否则返回"0"; for(i=0; i<=str.length;i++){ //判断是否已经有一个点号 if(str.substr(i,1)==".") return false; //如果有则不再插入 } str=str + "."; document.calculator.numScreen.value=str; operate=0; } function del(){ //退格 var str=String(document.calculator.numScreen.value); str=(str!="0") ? str : ""; str=str.substr(0,str.length-1); str=(str!="") ? str : "0"; document.calculator.numScreen.value=str; } function clearscreen(){ //清除数据 num=0; result=0; numshow="0"; document.calculator.numScreen.value="0"; } function plus(){ //加法 calculate(); //调用计算函数 operate=1; //更改输入状态 calcul=1; //更改计算状态为加 } function minus(){ //减法 calculate(); operate=1; calcul=2; } function times(){ //乘法 calculate(); operate=1; calcul=3; } function divide(){ //除法 calculate(); operate=1; calcul=4; } function persent(){ //求余 calculate(); operate=1; calcul=5; } function equal(){ calculate(); //等于 operate=1; num=0; result=0; numshow="0"; } // function calculate(){ numshow=Number(document.calculator.numScreen.value); if(num!=0 && quit!=1){ //判断前一个运算数是否为零以及防重复按键的状态 switch(calcul){ //判断要输入状态 case 1:result=num+numshow;break; //计算"+" case 2:result=num-numshow;break; //计算"-" case 3:result=num*numshow;break; case 4:if(numshow!=0){result=num/numshow;}else{document.getElementById("note").innerHTML="被除数不能为零!"; setTimeout(clearnote,4000)} break; case 5:result=num%numshow;break; } quit=1; //避免重复按键 } else{ result=numshow; } numshow=String(result); document.calculator.numScreen.value=numshow; num=result; //存储当前值 } function clearnote(){ //清空提示 document.getElementById("note").innerHTML=""; } </script></body></html>...

前端开发TIP:如何在JavaScript中循环遍历JSON响应

从远程服务器获取数据时,服务器的响应通常为JSON格式。在此快速提示中,我将演示如何使用JavaScript解析服务器的响应,以便访问所需的数据。该过程通常包括两个步骤:将数据解码为本机结构(例如数组或对象),然后使用JavaScript的一种内置方法遍历该数据结构。在本文中,我将使用大量可运行的示例来介绍这两个步骤。什么是JSON?JSON(JavaScriptObjectNotation,JS对象简谱)是一种轻量级的数据交换格式。它基于ECMAScript(欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。数据可以通过两种方式存储在JSON中:名称/值对的集合(又名JSON对象)值的有序列表(又名JSON数组)从Web服务器接收数据时,数据始终是字符串,这意味着将其转换为可以使用的数据结构是您的工作。如果您想了解有关JSON如何工作的更多信息,请访问JSON网站。从远程API提取JSON在以下示例中,我们将使用icanhazdadjokeAPI。正如您在其文档中所看到的那样,在将Accept标头设置为GET的情况下发出GET请求,application/json将会看到API返回JSON有效负载。让我们从一个简单的例子开始:const xhr = new XMLHttpRequest();xhr.onreadystatechange = () => {  if (xhr.readyState === XMLHttpRequest.DONE) {    console.log(typeof xhr.responseText);    console.log(xhr.responseText);  }};xhr.open('GET', 'https://icanhazdadjoke.com/', true);xhr.setRequestHeader('Accept', 'application/json');xhr.send(null);// string// {"id":"daaUfibh","joke":"Why was the big cat disqualified from the race? Because it was a cheetah.","status":200}如我们所见,服务器返回了一个字符串。我们需要先将其解析为JavaScript对象,然后才能遍历其属性。我们可以使用JSON.parse()做到这一点:if (xhr.readyState === XMLHttpRequest.DONE) {  const res = JSON.parse(xhr.responseText);  console.log(res);};// Object { id: "fiyPR7wPZDd", joke: "When does a joke become a dad joke? When it becomes apparent.", status: 200 }一旦我们将响应作为JavaScript对象,就可以使用多种方法遍历该对象。使用for...in循环for…in循环遍历对象的所有可枚举属性:const res = JSON.parse(xhr.responseText);for (const key in res){  if(obj.hasOwnProperty(key)){    console.log(`${key} : ${res[key]}`)  }}// id : H6Elb2LBdxc// joke : What's blue and not very heavy?  Light blue.// status : 200请注意,for...of循环将遍历整个原型链,因此在这里我们hasOwnProperty用来确保属性属于我们的res对象。使用Object.entries,Object.values或Object.entries上面的一种替代方法是使用Object.keys(),Object.values()或Object.entries()中的一个。这些将返回一个数组,然后我们可以对其进行迭代。让我们来看看使用Object.entries。这将返回我们传递给它的对象的键/值对的数组:const res = JSON.parse(xhr.responseText);Object.entries(res).forEach((entry) => {  const [key, value] = entry;  console.log(`${key}: ${value}`);});// id: SvzIBAQS0Dd // joke: What did the pirate say on his 80th birthday? Aye Matey!// status: 200请注意,const[key,value]=entry;语法是ES2015中引入的数组解构示例。这更加简洁,避免了前面提到的原型问题,并且是我首选的遍历JSON响应的方法。使用提取API尽管上面使用XMLHttpRequest对象的方法效果很好,但很快就会变得笨拙。我们可以做得更好。该提取API是基于承诺的API,它使一个更清洁,更简洁的语法和可以帮助您摆脱回调地狱。它提供了fetch()在window对象上定义的方法,您可以使用该方法执行请求。此方法返回一个Promise,可用于检索请求的响应。让我们重写前面的示例以使用它:(async () => {  const res = await fetch('https://icanhazdadjoke.com/', {    headers: { Accept: 'application/json' },  });  const json = await res.json();  Object.entries(json).forEach(([key, value]) => {    console.log(`${key}: ${value}`);  });})();// id: 2wkykjyIYDd// joke: What did the traffic light say to the car as it passed? "Don't look I'm changing!"// status: 200FetchAPI返回响应流。这不是JSON,因此JSON.parse()需要尝试使用它的response.json()函数而不是对其进行调用。这将返回一个Promise,该Promise会将响应的正文文本解析为JSON的结果进行解析。处理数组如本文顶部所述,值的有序列表(也称为数组)是有效的JSON,因此在完成之前,让我们研究如何处理此类响应。对于最后一个示例,我们将使用GitHub的RESTAPI来获取用户存储库的列表:(async () => {  async function getRepos(username) {    const url = `https://api.github.com/users/${username}/repos`;    const response = await fetch(url);    const repositories = await response.json();    return repositories;  }  const repos = await getRepos('jameshibbard');  console.log(repos);})();// Array(30) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ]如您所见,API返回了一个对象数组。要访问每个单独的对象,我们可以使用常规forEach方法:repos.forEach((repo) => {  console.log(`{$repo.name} has ${repo.stargazers_count} stars`);});// Advanced-React has 0 stars // angular2-education has 0 stars// aurelia-reddit-client has 3 stars// authentication-with-devise-and-cancancan has 20 stars// ...另外,您当然可以使用上面讨论的任何方法来遍历对象的所有属性,并将它们记录到控制台:repos.forEach((repo) => {  Object.entries(repo).forEach(([key, value]) => {    console.log(`${key}: ${value}`);  });});// name: Advanced-React// full_name: jameshibbard/Advanced-React// private: false// ...结论在这个快速提示中,我们研究了什么是JSON。我已经演示了如何将服务器的JSON响应解析为本机数据结构(例如数组或对象),以及如何遍历这种结构以访问其中包含的数据。...

前端开发:解析TypeError: invalid 'instanceof' operand 'x' 报错应用

前端开发过程中,会遇到TypeError:invalid'instanceof'operand'x'这样的报错。我们查看console的信息,如下:TypeError: invalid 'instanceof' operand "x" (Firefox)TypeError: "x" is not a function (Firefox)TypeError: Right-hand side of 'instanceof' is not an object (Chrome)TypeError: Right-hand side of 'instanceof' is not callable (Chrome)分析后,发现:哪里出错了?instanceof 操作符 希望右边的操作数为一个构造对象,即一个有 prototype 属性且可以调用的对象。例子"test" instanceof ""; // TypeError: invalid 'instanceof' operand ""42 instanceof 0;      // TypeError: invalid 'instanceof' operand 0function Foo() {}var f = Foo();        // Foo() is called and returns undefinedvar x = new Foo();x instanceof f;       // TypeError: invalid 'instanceof' operand fx instanceof x;       // TypeError: x is not a function为了解决上述问题,你可能需要将instanceof 操作符 换成 typeof 操作符, 或者确保你使用的是函数名称,而不是函数计算的结果。typeof "test" == "string"; // truetypeof 42 == "number"      // truefunction Foo() {}var f = Foo;               // Do not call Foo.var x = new Foo();x instanceof f;            // truex instanceof Foo;          // true...

Vue前端开发:实现圆环进度条

前端开发:实现圆环进度条。数据展示,一直是各行各业乐此不疲的需求,具体到前端开发行业,则是各种各种图表数据展示,各种表格数据展示,烦不胜烦(繁不胜繁)!前几天刚做了折线图、柱状图、饼状图之类的图表数据展示效果,今天又碰到了类似圆环进度条的展示效果。天天跟数据打交道,天天跟接口打交道,项目做了不少,菜逼还是菜逼,都是泪啊!其实说白了,是自己对canvas不熟,对CSS3不熟,所以就找了一个现成的轮子:<template> <div class="content" ref="box"> <svg style="transform: rotate(-90deg)" :width="width" :height="width" xmlns="http://www.w3.org/2000/svg">  <circle :r="(width-radius)/2"  :cy="width/2"  :cx="width/2"  :stroke-width="radius"  :stroke="backgroundColor"  fill="none"  />  <circle ref="$bar"  :r="(width-radius)/2"  :cy="width/2"  :cx="width/2"  :stroke="barColor"  :stroke-width="radius"  :stroke-linecap="isRound ? 'round' : 'square'"  :stroke-dasharray="(width-radius)*3.14"  :stroke-dashoffset="isAnimation ? (width-radius) * 3.14 : (width - radius) * 3.14 * (100 - progress) / 100"  fill="none"  /> </svg> <div class="center_text" :style="{color, fontSize}">  <p v-if="!$slots.default" class="title">{{progress}}%</p>  <slot></slot> </div> </div></template><script>export default { props: { radius: {  type: [Number, String],  default: 20 }, // 进度条厚度 progress: {  type: [Number, String],  default: 20 }, // 进度条百分比 barColor: {  type: String,  default: "#1890ff" }, // 进度条颜色 backgroundColor: {  type: String,  default: "rgba(0,0,0,0.3)" }, // 背景颜色 isAnimation: {  // 是否是动画效果  type: Boolean,  default: true }, isRound: {  // 是否是圆形画笔  type: Boolean,  default: true }, id: {  // 组件的id,多组件共存时使用  type: [String, Number],  default: 1 }, duration: {  // 整个动画时长  type: [String, Number],  default: 1000 }, delay: {  // 延迟多久执行  type: [String, Number],  default: 200 }, timeFunction: {  // 动画缓动函数  type: String,  default: "cubic-bezier(0.99, 0.01, 0.22, 0.94)" }, circleWidth: {  //圆环宽度  type: Number,  default: 100, }, color: {  //文字颜色  type: String,  default: '#000' }, fontSize: {  //文字大小  type: String,  default: '18px' } }, data() { return {  width: this.circleWidth,  idStr: `circle_progress_keyframes_${this.id}` }; }, beforeDestroy() { // 清除旧组件的样式标签 document.getElementById(this.idStr) && document.getElementById(this.idStr).remove(); window.addEventListener(() => {}); }, mounted() { let self = this; this.setCircleWidth(); this.setAnimation(); // 此处不能使用window.onresize window.addEventListener(  "resize",  debounce(function() {  self.setCircleWidth();  self.setAnimation(self);  }, 300) ); }, methods: { setCircleWidth() {  let box = this.$refs.box;  let width = box.clientWidth;  let height = box.clientHeight;  let cW = width > height ? height : width;  this.width = cW; }, setAnimation() {  let self = this;  if (self.isAnimation) {  // 重复定义判断  if (document.getElementById(self.idStr)) {   console.warn("vue-circle-progress should not have same id style");   document.getElementById(self.idStr).remove();  }  // 生成动画样式文件  let style = document.createElement("style");  style.id = self.idStr;  style.type = "text/css";  style.innerHTML = `  @keyframes circle_progress_keyframes_name_${self.id} {  from {stroke-dashoffset: ${(self.width - self.radius) * 3.14}px;}  to {stroke-dashoffset: ${((self.width - self.radius) *  3.14 *  (100 - self.progress)) /  100}px;}}  .circle_progress_bar${  self.id  } {animation: circle_progress_keyframes_name_${self.id} ${   self.duration  }ms ${self.delay}ms ${self.timeFunction} forwards;}`;  // 添加新样式文件  document.getElementsByTagName("head")[0].appendChild(style);  // 往svg元素中添加动画class  self.$refs.$bar.classList.add(`circle_progress_bar${self.id}`);  } } }};</script><style scoped>.content {height:100%;display:flex;justify-content:center;align-items: center;}.center_text {position:absolute;}</style>使用方法:<CircleProgress :id="'circle1'" :circleWidth="40" :radius="7" :progress="30" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#FF4F4F'" /><CircleProgress :id="'circle2'" :circleWidth="40" :radius="7" :progress="50" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#FF902A'" /><CircleProgress :id="'circle3'" :circleWidth="40" :radius="7" :progress="89" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#FFDB4F'" /><CircleProgress :id="'circle4'" :circleWidth="40" :radius="7" :progress="25" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#B8D87E'" />使用时需要注意一下,如果你的页面中同时使用了超过两个以上的这种圆环进度条,就需要给每个圆环进度条设置不同的id,否则,所有圆环最终展示的数据都会是最后一个圆环的数据。代码中有一个防抖动的函数,这里就贴一下这个函数:function debounce(func, wait, immediate) { let timeout, args, context, timestamp, result const later = function () { // 据上一次触发时间间隔 const last = +new Date() - timestamp // 上次被包装函数被调用时间间隔last小于设定时间间隔wait if (last < wait && last > 0) {  timeout = setTimeout(later, wait - last) } else {  timeout = null  // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用  if (!immediate) {  result = func.apply(context, args)  if (!timeout) context = args = null  } } }...

Web前端开发:无服务器功能及其部署指南

在过去的几年中,无服务器功能(有时也称为“无服务器”或“无服务器计算”)已成为一种流行的技术。但是,这个词仍然有很多困惑。没有服务器如何运行代码?该技术的优缺点是什么?您可能在什么情况下使用它?在本文中,我希望回答这些问题,并为您提供有关该技术的良好概述。什么是无服务器功能?第一次听到“无服务器”一词肯定会引起好奇。“如何在没有服务器的情况下在网络上运行代码?” 你可能想知道。实际上,这意味着作为开发人员,您不必担心代码在其上运行的服务器。无服务器提供者将硬件的供应,配置网络,安装软件和扩展都抽象化了。从开发角度来看,无服务器功能是您上传到无服务器提供程序(例如AWS或Google)的一捆代码。可以将该代码配置为通过URL响应请求,按计划运行(即通过cron作业)或从其他服务或无服务器功能调用。无服务器功能非常适合在前端应用程序中添加一些后端功能,而又无需运行完整服务器的复杂性和成本。另一方面,您还可以使用无服务器功能构建整个应用程序。结合提供文件存储,数据库系统和身份验证的其他云服务,可以构建大型,健壮和可扩展的应用程序,而无需配置单个服务器。无服务器功能的优点无服务器功能在按需启动的微型容器中运行。它们是为运行时间较短的流程而设计的,因此记帐时要牢记这一点。与通常按小时计费的完整服务器实例不同,无服务器功能通常按GB-秒计费。以毫秒为单位的最小计费持续时间,低频或零星的工作负载作为无服务器功能运行比传统服务器实例便宜得多。轻量级工作负载和原型制作甚至可以属于某些提供商的免费套餐。无服务器功能的按需调用意味着它们可以快速轻松地扩展,而无需开发人员方面的额外工作。这使它们成为流量不可预期的高峰的理想选择,因为将自动提供更多功能实例来处理负载。之后,该功能将按比例缩小,这意味着您无需为未使用的容量付费。无服务器模型的主要优点是不必处理服务器。运行Web应用程序需要大量时间和服务器管理方面的专业知识,才能使软件与最新的安全补丁保持同步,并确保正确配置服务器以确保其安全性和性能。对于初创企业和小型企业,雇用人员来处理服务器管理是一笔很大的额外开销。借助无服务器,开发人员可以专注于创建解决方案。无服务器功能的缺点当然,没有技术是完美的,无服务器功能并非没有缺点。正如我之前提到的,无服务器模型在设计时考虑到了短暂的过程。以分钟为单位的最大执行时间(例如,在AWS上为15,在Google上为9),它不适合长时间运行的工作,例如处理大量数据。另一个广泛讨论的问题是冷启动时间。这是提供商在准备好开始运行之前为无服务器功能预配和初始化容器所花费的时间。一旦函数完成运行,如果再次执行代码,则容器将保留很短的时间以备重用。这种“冷启动”延迟可能会使功能的响应时间增加半秒到一秒。有一些解决方法,包括Serverless框架的WarmUp插件,该插件按计划ping您的函数以使容器保持活动状态。尽管无服务器功能使您不必担心服务器配置和维护,但这并不是说没有学习曲线。使用无服务器构建应用程序需要与使用传统的单片代码库不同的思维方式。您必须以不同的方式来构造代码,将功能分解为更小的,离散的服务,以适应无服务器功能的约束。部署也更加复杂,因为每个功能都是独立版本和更新的。还存在供应商锁定的问题,这有时被称为无服务器技术的不利方面。按照目前的情况,该领域的主要提供商(AWS,Google,Azure)具有各自不同的实现和管理工具。这可能使将无服务器应用程序从一个云提供商迁移到另一云提供商变得困难。诸如无服务器框架之类的项目已尝试抽象化基础服务,以使应用程序在提供程序之间可移植。无服务器功能用例尽管无服务器功能可以用于构建整个应用程序,但让我们看一些不那么雄心勃勃的用例,其中无服务器可以使普通开发人员受益。表格邮件除了用户希望在用户点击“发送”时通过电子邮件将其发送给客户的联系方式之外,网站完全是静态的并不少见。该站点的托管提供程序可能支持也可能不支持服务器端脚本,即使如此,它也可能不是您所熟悉的语言。通过将无服务器功能设置为表单邮件程序,可以将功能添加到静态主机上的站点。Cron工作有时您可能需要在后台运行计划任务。通常,您必须为一台服务器付费才能设置cron作业,而该服务器将在两次作业之间处于空闲状态。借助无服务器功能,您只需支付作业的运行时间(如果属于免费套餐,则可能根本不用花钱)。缩略图生成器想象一下,您的React应用程序允许用户上传照片,以在整个应用程序中用作头像。您想调整上传图像的大小,以免通过提供远远超出所需大小的图像来浪费带宽。可以使用无服务器功能来处理上传请求,将图像调整为所需的大小,然后保存到诸如S3或GoogleStorage之类的服务中。无服务器功能的实际示例为了更深入地了解如何使用无服务器功能,我们来看一个真实的示例。我们将创建一个带有新闻简报注册表单的静态页面,该页面使用无服务器功能将用户名和电子邮件地址保存到Google电子表格中。取决于提供程序,无服务器功能可以用多种语言编写,但是我们将使用JavaScript,因为Netlify支持Node.js函数。我将假设您已经在本地计算机上安装了最新版本的Node/npm,以便进行后续操作。1.注册一个Netlify帐户在本示例中,我们将使用Netlify作为主机,因为它们提供了包括无服务器功能的免费层,并且非常容易启动和运行。首先,跳至他们的网站并注册一个免费帐户[https://app.netlify.com/signup]。2.安装NetlifyCLI工具为了在本地测试示例站点并部署到Netlify,我们将使用其CLI工具。可以从命令行将其作为全局npm模块安装:npm install -g netlify-cli安装CLI后,运行以下命令将打开浏览器窗口,以将CLI连接到您的帐户:netlify login3.创建一个项目文件夹并安装依赖项让我们为项目创建一个文件夹,并初始化一个新的npm项目:mkdir serverless-mailinglist && cd serverless-mailinglistnpm init -y这将为我们设置package.json项目文件,准备安装依赖项。说到这,我们将需要几个软件包来实现我们的无服务器功能:npm install dotenv google-spreadsheet第一个是dotenv,它是一个软件包,该软件包可让我们从.env项目根目录中的文件中加载值,并将其公开给Node脚本(我们的无服务器功能),就好像它们是环境变量一样。另一个是google-spreadsheet,这是一个包装了GoogleSheetsAPI并易于使用的软件包。4.启用GoogleSheetsAPI并创建凭据为了使用SheetsAPI,我们需要做一些准备工作。首先,您需要转到API控制台来为您的Google帐户启用API。从顶部的菜单中创建一个新项目,然后单击“启用”按钮。完成后,您将需要创建一个服务帐户。此帐户将为您提供一组凭据,这些凭据具有访问API所需的权限。为此,请按照下列步骤操作:确保您位于SheetsAPI管理屏幕上。单击左侧边栏中的凭据,然后单击+创建凭据,然后从下拉列表中选择服务帐户。填写表格,为服务帐户选择一个名称。您选择的名称加上项目名称将成为服务帐户ID的一部分。例如,如果您将帐户命名为“MailingList”,而项目名称是“SitepointServerlessDemo”,则ID类似于mailing-list@sitepoint-serverless-demo.iam.gserviceaccount.com。点击创建。您可以跳过页面上其余的两个可选部分。单击继续,然后单击完成。接下来,单击新创建的服务帐户。这将带您到一个显示帐户详细信息的屏幕。点击顶部菜单中的KEYS,然后点击AddKey和Createnewkey。选择JSON作为密钥类型。单击CREATE按钮,然后JSON密钥文件将下载到您的计算机。(注意:这是唯一的副本,因此请确保安全!)5.创建注册表单页面让我们继续创建一个简单的注册页面,该页面将允许用户将其详细信息提交到我们的邮件列表。index.html在项目根目录中创建一个文件,内容如下:<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8">    <title>Sign Up For Beta Form</title>    <link rel="stylesheet" href="style.css">    <link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'>  </head>  <body>    <form action="/.netlify/functions/subscribe" method="post">      <div class="header">         <p>Get Great Content Every Week</p>      </div>      <div class="description">        <p>I publish new articles every week. Be sure to subscribe to my newsletter to make sure you never miss a post!</p>      </div>      <div class="input">        <input type="text" class="button" id="name" name="name" placeholder="YOUR NAME">      </div>      <div class="input">        <input type="text" class="button" id="email" name="email" placeholder="NAME@EXAMPLE.COM">        <input type="submit" class="button" id="submit" value="SIGN UP">      </div>    </form>  </body></html>和一个style.css文件,具有以下规则:body {  background: #A6E9D7;  font-family: 'Lato', sans-serif;  color: #FDFCFB;  text-align: center;  background-image: url(https://images.pexels.com/photos/326311/pexels-photo-326311.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940)}form {  width: 450px;  margin: 17% auto;}.header {  font-size: 35px;  text-transform: uppercase;  letter-spacing: 5px;}.description {  font-size: 14px;  letter-spacing: 1px;  line-height: 1.3em;  margin: -2px 0 45px;}.input {  display: flex;  align-items: center;}.button {  height: 44px;  border: none;}#email {  width: 75%;  background: #FDFCFB;  font-family: inherit;  color: #737373;  letter-spacing: 1px;  text-indent: 5%;  border-radius: 5px 0 0 5px;}#name {  width: 100%;  background: #FDFCFB;  font-family: inherit;  color: #737373;  letter-spacing: 1px;  text-indent: 5%;  border-radius: 5px;  margin-bottom: 1em;}#submit {  width: 25%;  height: 46px;  background: #E86C8D;  font-family: inherit;  font-weight: bold;  color: inherit;  letter-spacing: 1px;  border-radius: 0 5px 5px 0;  cursor: pointer;  transition: background .3s ease-in-out;}#submit:hover {  background: #d45d7d;}input:focus {  outline: none;  outline: 2px solid #E86C8D;  box-shadow: 0 0 2px #E86C8D;}6.创建一个无服务器功能来处理表单现在我们有了表单,我们需要为无服务器功能创建代码,该代码将处理POST请求,并通过API将数据保存到Google电子表格中。为了使Netlify部署我们的功能,我们必须遵循它们的命名约定,并netlify/functions/在我们的项目文件夹中创建文件夹路径。在新功能文件夹中,创建一个JavaScript文件subscribe.js:if (!process.env.NETLIFY) {  require('dotenv').config();}const { parse } = require('querystring');const { GoogleSpreadsheet } = require('google-spreadsheet');exports.handler = async (event, context) => {  const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID_FROM_URL);  await doc.useServiceAccountAuth({    client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,    private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')  });  await doc.loadInfo();  const sheet = doc.sheetsByIndex[0];   try {    if (event.httpMethod === 'POST') {      /* parse the string body into a useable JS object */      const data = parse(event.body);      await sheet.addRow(data);      return {        statusCode: 302,        headers: {          Location: '/success.html'        }      };    } else {      return {        statusCode: 500,        body: 'unrecognized HTTP Method, must be POST'      };    }  } catch (err) {    console.error('error ocurred in processing ', event);    console.error(err);    return {      statusCode: 500,      body: err.toString()    };  }};注意:该功能代码改编自具有NetlifyDev的博客文章GoogleSheetsv4API【https://www.swyx.io/netlify-google-sheets/】。Netlify的默认配置意味着该netlify/functions路径下的JavaScript文件可以在/.netlify/functions/URL(注意之前的句点netlify)加上文件名减去扩展名后调用。该文件netlify/functions/subscribe.js将在相对URL上可用/.netlify/functions/subscribe。基于节点的无服务器功能的基本要求是导出一个处理程序功能,该功能将在端点接收到请求时被调用。该函数传递两个参数。该event参数提供对请求详细信息的访问,例如标头和HTTP方法。通过该context参数,可以访问有关调用函数的上下文的信息,例如,包括已认证用户的详细信息。函数代码本身使用提供的凭据连接到GoogleSheetsAPI。然后,它解析请求正文,并通过API将提交的名称和电子邮件地址添加到电子表格中。完成后,该函数将返回302响应,以将用户重定向到成功页面。(创建此页面留给读者完成。)为了能够在本地测试该功能,我们需要.env在项目根目录中创建一个文件,并添加一些变量:GOOGLE_SERVICE_ACCOUNT_EMAIL=mailing-list@sitepoint-serverless-demo.iam.gserviceaccount.comGOOGLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANB \\etcGOOGLE_SPREADSHEET_ID_FROM_URL=1N8afdqnJjBhwXsvplIgU-5JoAFr3RapIrAS9oUybFnU服务帐户电子邮件是您在步骤4中创建的电子邮件,私钥来自您下载的JSON密钥文件。最后一个,即电子表格ID,我们将在下一步中获取。7.创建电子表格并共享转到Google表格并创建一个新的电子表格。赋予它什么标题都没有关系,但是请记下URL中的ID,并将其添加到.env上一步中创建的文件中。在电子表格的第一行中,添加两个列标题:name和email(请注意,区分大小写与HTML表单中的输入名称匹配非常重要)。由无服务器功能创建的条目将作为附加行添加在此下方。现在,您必须授予创建的服务帐户访问电子表格的权限。单击共享按钮,然后在输入框中输入服务帐户的电子邮件地址。确保分配编辑者权限。8.使用NetlifyCLI在本地进行测试NetlifyCLI工具的一个不错的功能之一是,它允许您在发布到他们的服务之前在本地测试代码。要启动开发服务器,请运行以下命令:netlify dev一个新的浏览器选项卡将自动打开,并显示该站点。填写并提交表单将运行无服务器功能(本地提供),然后在成功时重定向浏览器。如果您跳至Google表格上的电子表格,则应该在新行中看到输入的详细信息。9.部署到NetlifyCLI工具在模拟计算机上本地运行的Netlify服务方面做得很好,但是如果您想查看项目在其服务器上运行,则还可以使用CLI发布项目。运行以下命令:netlify deploy然后按照提示进行操作。您的站点(包括无服务器功能)将发布到Web上。不要忘记,您还需要设置环境变量以镜像.env文件中的那些变量。您可以从Netlify网站的管理面板或通过CLI工具进行设置:netlify env:set VAR_NAME value无服务器:只是时尚,还是后端的未来?同时,无服务器已成为时尚,并被誉为后端应用程序的未来。自2014年以来,亚马逊的Lambda功能已经存在,并且是AWS的重要产品。当然,在许多情况下,仍需要运行24/7并具有完全shell访问权限的实际服务器的灵活性和功能。但是,正如我们已经看到的,对于某些类型的工作负载,无数的廉价成本,可伸缩性和低维护优势使其成为一个不错的选择。随着无服务器生态系统中书籍,课程,框架和服务的增加,可以肯定的是,无服务器功能将长期存在。...

前端开发TIPS:Js中new操作符

前端开发TIPS:Js中new操作符。众所周知,在JS中,new的作用是通过构造函数来创建一个实例对象。像下面这样:(和普通函数不一样,当函数用作构造函数时,首字母一般要大写,以作区分。)function Foo(name) {  this.name = name;}console.log("new Foo('mm')的类型:",typeof new Foo('mm')); // objectconsole.log("Foo的类型:",typeof Foo); // function创建了一个空对象var obj=new Object();在Js代码中,new操作符的主要作用是产生对象。通过new创建空对象,为创建对象打基底。设置原型链obj.__proto__= Func.prototype;S中在利用new操作符建好基底后,就开始下一步的Js代码操作,设置原型链。new通过构造函数创建出的实例可以访问到构造函数原型链中的属性,换言之,通过new操作符,原型链链接了实例和构建函数。(改变this指向)让Func中的this指向obj,并执行Func的函数体。var result =Func.call(obj);一般情况下,在Js代码组中,出现this时,构造函数内部是正常工作,但当通过new操作符改变this指向后,所出现的返回值会被正常的返回出去。判断Func的返回值类型:如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。if (typeof(result) == "object"){  func=result;}else{  func=obj;}从上述一组new操作符代码中可看出,new还可用来判断Func的返回值类型。如果返回值是值类型,则正常返回。如果是引用类型,就返回到引用类型的对象。...

前端开发TIPS:类选择器和name属性选择器

前端开发TIPS:类选择器和name属性选择器。类选择器:function getElementsByClass(className) {   var classArr = [];   var tags = document.getElementsByTagName("*");   for (var i = 0; i < tags.length; i++) {    if (tags[i].nodeType == 1) {     if (tags[i].getAttribute("class") == className) {      classArr.push(tags[i]);     }    }   }   return classArr;}其实name属性选择器跟类选择器一样,只是判断条件稍微变了一下而已:function getElementsByName(name) {   var nameArr = [];   var num = 0;   var tags = document.getElementsByTagName("*");   for (var i = 0; i < tags.length; i++) {    if (tags[i].nodeType == 1) {     if (tags[i].getAttribute("name") == name) {      nameArr.push(tags[i]);     }    }   }   return nameArr;}name属性选择器大多用在表单的操作方面。以上代码中有一个nodeType的属性,它是用来判断节点的类型,nodeType共有12个值,1代表节点元素,2代表属性,3代表元素或属性中的文本内容。这三个数值用的是比较多的,其他9个用的不多,想了解的话可以去看一下API。在这里,我们需要得到元素节点,所以就会判断当前元素的nodeType是否为1。再来贴一下用递归来实现获取元素的所有子节点(含孙子节点):  /**    * 递归获取所有子节点   *    node代表想要获取所有子节点的父节点   type取值:   1  Element         代表元素   2  Attr          代表属性   3  Text          代表元素或属性中的文本内容   4  CDATASection      代表文档中的 CDATA 部分(不会由解析器解析的文本)   5  EntityReference     代表实体引用   6  Entity         代表实体   7  ProcessingInstruction  代表处理指令   8  Comment         代表注释   9  Document        代表整个文档(DOM 树的根节点)   10 DocumentType      向为文档定义的实体提供接口   11 DocumentFragment    代表轻量级的 Document 对象,能够容纳文档的某个部分   12 Notation        代表 DTD 中声明的符号  */  var allChildNodes = function (node, type) {   // 1.创建全部节点的数组   var allCN = [];   // 2.递归获取全部节点   var getAllChildNodes = function (node, type, allCN) {    // 获取当前元素所有的子节点nodes    var nodes = node.childNodes;    // 获取nodes的子节点    for (var i = 0; i < nodes.length; i++) {     var child = nodes[i];     // 判断是否为指定类型节点     if (child.nodeType == type) {      allCN.push(child);     }     getAllChildNodes(child, type, allCN);    }   }   getAllChildNodes(node, type, allCN);   // 3.返回全部节点的数组   return allCN;  }  // 调用:  // 获取body中全部节点  allChildNodes(document.querySelector('body'), 1);    //获取body中全部纯文本节点  allChildNodes(document.querySelector('body'), 3)...

前端开发TIPS:筛选数组

前端开发TIPS:筛选数组。废话不多说,上代码。<!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> <script>  function Search(arr) {   var newArr = [];   for (var i = 0; i < arr.length; i++) {    if (arr[i] > 10) {     newArr[newArr.length] = arr[i];   }  }   return newArr;  }  var arr = [1, 2, 45, 31, 7, 30, 22, 3, 5, 17];  Search(arr);  alert('您输入的数字分别为'+arr+'\n'+'大于10的数有:'+Search(arr)); </script></head><body></body></html>...

前端开发:JavaScript中reduce()的小应用

前端开发:JavaScript中reduce()的小应用。reduce()方法可以搞定的东西,for循环,或者forEach方法有时候也可以搞定,那为啥要用reduce()?这个问题,之前我也想过,要说原因还真找不到,唯一能找到的是:通往成功的道路有很多,但是总有一条路是最捷径的,亦或许reduce()逼格更高...语法arr.reduce(callback,[initialValue])reduce()方法对数组中的每一个元素执行一个reducer函数(由你提供),从而得到一个单一的输出值。reduce()方法将一个数组中的所有元素还原成一个单一的输出值,输出值可以是数字、对象或字符串。reduce()方法有两个参数,第一个是回调函数,第二个是初始值。回调函数回调函数在数组的每个元素上执行。回调函数的返回值是累加结果,并作为下一次调用回调函数的参数提供。回调函数带有四个参数。Accumulator(累加器)——累加器累加回调函数的返回值。CurrentValue(当前值)——处理数组的当前元素。CurrentIndex(当前索引)——处理数组当前元素的索引。SourceArray(源数组)CurrentIndex和SourceArray是可选的。初始值如果指定了初始值,则将累加器设置为initialValue作为初始元素。否则,将累加器设置为数组的第一个元素作为初始元素。arr.reduce(callback(accumulator, currentValue[,index[,array]])[, initialValue])在下面的代码片段中,第一个累加器(accumulator)被分配了初始值0。currentValue是正在处理的numbersArr数组的元素。在这里,currentValue被添加到累加器,在下次调用回调函数时,会将返回值作为参数提供。const numbersArr = [67, 90, 100, 37, 60];const total = numbersArr.reduce(function(accumulator, currentValue){ console.log("accumulator is " + accumulator + " current value is " + currentValue); return accumulator + currentValue;}, 0);console.log("total : "+ total);输出:accumulator is 0 current value is 67accumulator is 67 current value is 90accumulator is 157 current value is 100accumulator is 257 current value is 37accumulator is 294 current value is 60total : 354JavaScriptreduce用例1.对数组的所有值求和在下面的代码中,studentResult数组具有5个数字。使用reduce()方法,将数组减少为单个值,该值将studentResult数组的所有值和结果分配给total。const studentResult = [67, 90, 100, 37, 60];const total = studentResult.reduce((accumulator, currentValue) => accumulator +currentValue, 0);console.log(total); // 3542.对象数组中的数值之和通常,我们从后端获取数据作为对象数组,因此,reduce()方法有助于管理我们的前端逻辑。在下面的代码中,studentResult对象数组有三个科目,这里,currentValue.marks取了studentResult对象数组中每个科目的分数。const studentResult = [ { subject: '数学', marks: 78 }, { subject: '物理', marks: 80 }, { subject: '化学', marks: 93 }];const total = studentResult.reduce((accumulator, currentValue) => accumulator + currentValue.marks, 0);console.log(total); // 2513.展平数组“展平数组”是指将多维数组转换为一维。在下面的代码中,twoDArr2维数组被转换为oneDArr一维数组。此处,第一个[1,2]数组分配给累加器accumulator,然后twoDArr数组的其余每个元素都连接到累加器。const twoDArr = [ [1,2], [3,4], [5,6], [7,8] , [9,10] ];const oneDArr = twoDArr.reduce((accumulator, currentValue) => accumulator.concat(currentValue));console.log(oneDArr);// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]4.按属性分组对象根据对象的属性,我们可以使用reduce()方法将对象数组分为几组。通过下面的代码片段,你可以清楚地理解这个概念。这里,result对象数组有五个对象,每个对象都有subject和marks属性。如果分数大于或等于50,则该主题通过,否则,主题失败。reduce()用于将结果分组为通过和失败。首先,将initialValue分配给累加器,然后push()方法在检查条件之后将当前对象添加到pass和fail属性中作为对象数组。const result = [ {subject: '物理', marks: 41}, {subject: '化学', marks: 59}, {subject: '高等数学', marks: 36}, {subject: '应用数学', marks: 90}, {subject: '英语', marks: 64},];let initialValue = { pass: [], fail: []}const groupedResult = result.reduce((accumulator, current) => { (current.marks >= 50) ? accumulator.pass.push(current) : accumulator.fail.push(current); return accumulator;}, initialValue);console.log(groupedResult);输出:{ pass: [  { subject: ‘化学', marks: 59 },  { subject: ‘应用数学', marks: 90 },  { subject: ‘英语', marks: 64 } ], fail: [  { subject: ‘物理', marks: 41 },  { subject: ‘高等数学', marks: 36 } ]}5.删除数组中的重复项在下面的代码片段中,删除了plicatedArr数组中的重复项。首先,将一个空数组分配给累加器作为初始值。accumulator.includes()检查duplicatedArr数组的每个元素是否已经在累加器中可用。如果currentValue在累加器中不可用,则使用push()将其添加。const duplicatedsArr = [1, 5, 6, 5, 7, 1, 6, 8, 9, 7];const removeDuplicatedArr = duplicatedsArr.reduce((accumulator, currentValue) => { if(!accumulator.includes(currentValue)){ accumulator.push(currentValue); } return accumulator;}, []);console.log(removeDuplicatedArr);// [ 1, 5, 6, 7, 8, 9 ]...

前端开发小应用:简单的倒计时功能

前端开发小应用:简单的倒计时功能。<!DOCTYPE html><html lang="en"> <head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>前端开发小应用:简单的倒计时功能</title>  <style>    * {      margin: 0;      padding: 0;    }        .wrap {      overflow: hidden;      width: 500px;      height: 500px;      background-color: #eeeeee;      margin: 0 auto;    }        h2 {      margin-top: 20px;      text-align: center;      color: #fff;    }        input {      width: 70px;    }        .ipt {      text-align: center;      margin-top: 50px;    }        .run {      width: 100px;      height: 100px;      background-color: #000;      text-align: center;      line-height: 100px;      color: #fff;      font-size: 30px;      border-radius: 50%;      margin: 30px auto 0;    }        .juli {      text-align: center;      margin-top: 30px;    }        .sytime {      text-align: center;      margin-top: 60px;      font-size: 25px;      color: #fff;    }        .sytime span {      font-size: 30px;      color: red;    }        .juli span {      font-size: 18px;      color: red;    }  </style></head> <body>  <div class="wrap">    <h2>倒计时</h2>    <!-- 表单 -->    <div class="ipt">      请输入: <input type="text">年<input type="text">月<input type="text">日    </div>    <!-- 开始按钮 -->    <div class="run">开始</div>    <!-- 距离时间 -->    <p class="juli">现在距离-<span class="julitime">0000</span>-还剩:</p>    <!-- 剩余时间 -->    <div class="sytime">      <span>00</span>天      <span>00</span>小时      <span>00</span>分      <span>00</span>秒    </div>  </div>  <script>    // 获取元素    // 表单    var ipt = document.getElementsByTagName('input');    // 按钮    var btn = document.getElementsByClassName('run')[0];    // 距离年份    var julitime = document.getElementsByClassName('julitime')[0];    // 倒计时    var sytime = document.getElementsByClassName('sytime')[0];    var time = sytime.getElementsByTagName('span');    console.log(ipt, btn, julitime, time);     var timerId = null;    // 点击事件     btn.onclick = function() {      if (ipt[1].value > 12 || ipt[2].value > 30) {        alert('月份要小于12且日要小于30');        return;      } else if (ipt[0].value.trim() == '' || ipt[1].value.trim() == '' || ipt[2].value.trim() == '') {        alert('内容不能为空');        return;      }      timerId = setInterval(countTime, 1000);     }       function countTime() {      // 获取输入年份      var ipty = ipt[0].value;      // 获取输入月份      var iptm = ipt[1].value;      // 获取输入日份      var iptd = ipt[2].value;      // console.log(ipty, iptm, iptd);      var str = ipty + '-' + iptm + '-' + iptd;      // console.log(str);      // 赋值给距离时间      julitime.innerHTML = str;      // 当前距离1970,1,1毫秒数      var nowDate = +new Date();      // 输入时间距离1970,1,1毫秒数      var inputFr = +new Date(ipty + '-' + iptm + '-' + iptd)        // 未来减去现在 秒数      var times = (inputFr - nowDate) / 1000;      var d = parseInt(times / 60 / 60 / 24) //天      d = d < 10 ? '0' + d : d;      var h = parseInt(times / 60 / 60 % 24) //时      h = h < 10 ? '0' + h : h;       var m = parseInt(times / 60 % 60); //分      m = m < 10 ? '0' + m : m;       var s = parseInt(times % 60); //秒      s = s < 10 ? '0' + s : s;       // console.log(d, h, m, s);      time[0].innerHTML = d;      time[1].innerHTML = h;      time[2].innerHTML = m;      time[3].innerHTML = s;    }  </script></body> </html>...

前端开发:介绍js中实现换行的几种方法

早上学点知识,介绍js中实现换行的几种方法,一起来了解下吧。js中实现换行的方法:方法1:使用换行符1、\n换行符在JavaScript中我们可以直接在要换行的地方使用\n进行换行:alert("第一行\n第二行");2、\r换行符alert("第一行\r第二行");方法2:使用HTML的标签当可向HTML文档中写入内容时,可以使用HTML的标签来进行换行。示例:使用document.write()。document.write("第一行<br>第二行")输出:第一行第二行...

Web前端开发Tips:实现一个简单的留言本功能

前端开发小应用:实现一个简单的留言本功能。先看看效果图如下:DEMO代码如下:<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Web前端开发Tips:实现一个简单的留言本功能-WEB前端之家/https://www.jiangweishan.com</title><style>*{margin:0;padding:0;}body{padding:100px;}.box{width:80%;margin:50pxauto;}h2{text-align:center;}textarea{width:94%;height:100px;border:1pxsolid#ccc;outline:none;resize:none;padding:10px3%;font-size:14px;color:#666;}.ulist{margin-top:10px;}.ulistli{list-style:none;padding:5px;background-color:#f5f5f5;color:#333;font-size:14px;margin:5px0;}h4{margin-top:30px;}.ulistlia{float:right;text-decoration:none;}.boxbutton{margin-top:10px;display:block;border:none;width:100%;line-height:40px;background:#84bef3;color:#fff;}.item{margin-top:30px;}</style></head><body><divclass="box"><h2>Web前端之家-留言本</h2><divclass="item"><textareaname=""id=""></textarea><button>发布</button></div><h4>留言列表:</h4><ulclass="ulist"></ul></div><scripttype="text/javascript">varbtn=document.querySelector('button')vartextarea=document.querySelector('textarea')varul=document.querySelector('ul')btn.onclick=function(){if(textarea.value==''){alert('没有输入内容~~~~')returnfalse}else{varli=document.createElement('li')li.innerHTML=textarea.value+"<ahref='javascript:;'>删除</a>"ul.insertBefore(li,ul.children[0])varas=document.querySelectorAll('a')for(vari=0;i<as.length;i++){as[i].onclick=function(){ul.removeChild(this.parentNode)}}}}</script></body></html>试试吧,您需要需要更多的功能,可以在这个上面进行优化哟。...

前端开发TIPS:json对象和string对象转换

前端开发TIPS:json对象和string对象转换。看一段JSON代码:var json = {aa:true,bb:true}; var json1 = {aa:'b',bb:{cc:true,dd:true}};js操作json对象for(var item in json){     alert(item); //结果是 aa,bb, 类型是 string     alert(typeof(item));     alert(eval("json."+item)); //结果是true,true类型是boolean     eval(("json."+item+"=false;")); //改变json对象的值 }json对象转化为String对象的方法/** * json对象转字符串形式 */ function json2str(o) {     var arr = [];     var fmt = function(s) {         if (typeof s == 'object' && s != null) return json2str(s);         return /^(string|number)$/.test(typeof s) ? "'" + s + "'" : s;     }     for (var i in o) arr.push("'" + i + "':" + fmt(o[i]));     return '{' + arr.join(',') + '}'; }string对象转化为json对象function stringToJson(stringValue) {     eval("var theJsonValue = "+stringValue);     return theJsonValue; }json数组转化为String对象的方法(要调要上面那个方法)function JsonArrayToStringCfz(jsonArray){    var JsonArrayString = "[";     for(var i=0;i<jsonArray.length;i++){         JsonArrayString=JsonArrayString+JsonToStringCfz(jsonArray[i])+",";     }     JsonArrayString = JsonArrayString.substring(0,JsonArrayString.length-1)+"]";     return JsonArrayString; }利用json.jsjson转string<script src="json2.js"></script> <script>     var date = {myArr : ["a" , "b" , "c" , "d"] , count : 4};     var str = JSON.stringify(date);     alert(str); </script>...

前端开发库:教你Laravel Livewire入门

作为开发人员,我们一直在寻找使我们的生活更轻松的工具,库和框架。对于Laravel开发人员而言,这没什么不同。这就是为什么我们喜欢这个框架的原因,因为一切对我们来说都很容易-使我们能够专注于构建出色的应用程序,而不会为如何实现实现而烦恼。在本教程中,我们将研究另一个工具,该工具可以使您成为Laravel开发人员更加轻松。具体来说,我们将研究Livewire,这是Laravel的全栈框架,可让我们无需编写大量JavaScript即可构建动态接口。基本上所有事情都是由您作为Laravel开发人员已经熟悉的工具完成的。什么是Livewire?Livewire[https://laravel-livewire.com/]是一个库,它使我们能够使用Blade和一些JavaScript来构建反应和动态接口。我说“一点”,是因为我们只打算编写JavaScript来通过浏览器事件传递数据并对其进行响应。您可以使用Livewire来实现以下功能,而无需重新加载页面:分页表格验证通知文件上传预览请注意,Livewire不仅限于上述功能。您可以使用更多功能。上面的功能只是您可能想在应用程序中实现的最常见功能。Livewire与VueVue一直是Laravel开发人员向其应用程序添加交互性的首选前端框架。如果您已经在使用Vue为应用程序供电,则Livewire是可选的供您学习。但是,如果您不熟悉使用Laravel进行前端,并且打算将Livewire替换为Vue,那么可以,可以使用Livewire替代Vue。学习曲线不会像学习Vue那样陡峭,因为您将主要使用Blade来编写模板文件。有关Livewire和Vue进行比较的更多信息,请查看“ LaravelLivewirevsVue[https://blog.logrocket.com/livewire-vs-vue/] ”。应用概述我们将创建一个实时CRUD应用程序。因此,它基本上是没有页面重新加载的CRUD应用。Livewire将处理更新UI所需的所有AJAX请求。这包括通过搜索字段过滤结果,通过列标题排序以及简单的分页(上一个和下一个)。创建和编辑用户将使用Bootstrap模式。您可以在其GitHubrepo【https://github.com/sitepoint-editors/livecrud】上查看该项目的源代码。先决条件本教程假定您具有PHP应用程序开发经验。Laravel的经验会有所帮助,但不是必需的。如果您只知道纯PHP或其他PHP框架,仍然可以继续学习。本教程假定您在计算机上安装了以下软件:PHPMySQLNGINXComposerNodeandnpm如果您使用的是Mac,则比安装MySQL和NGINX更为方便的选择是安装DBngin和LaravelValet。设置项目您现在可以创建一个新的Laravel项目:composer create-project laravel/laravel livecrud浏览livecrud将要生成的文件夹。这将是根项目文件夹,您可以在其中执行本教程中的所有命令。下一步是使用您选择的数据库管理工具创建MySQL数据库。将数据库命名为livecrud。安装后端依赖项后端只有一个依赖关系,那就是Livewire。使用以下命令安装它:composer require livewire/livewire:2.3注意:我们正在安装创建演示时使用的特定版本。如果将来要阅读此书,建议您安装最新版本。不要忘记在其GitHub存储库上检查项目的变更日志,以确保您没有丢失任何内容。设置数据库更新用于创建users表的默认迁移,并添加我们将要使用的自定义字段:// database/migrations/<timestamp>_create_users_table.phppublic function up(){    Schema::create('users', function (Blueprint $table) {        $table->id();        $table->string('name');        $table->string('email')->unique();        $table->enum('user_type', ['admin', 'user'])->default('user'); // add this        $table->tinyInteger('age'); // add this        $table->string('address')->nullable(); // add this        $table->timestamp('email_verified_at')->nullable();        $table->string('password');        $table->rememberToken();        $table->timestamps();    });}接下来,更新database/factories/UserFactory.php文件并为我们添加的自定义字段提供值:// database/factories/UserFactory.phppublic function definition(){    return [        'name' => $this->faker->name,        'email' => $this->faker->unique()->safeEmail,        'email_verified_at' => now(),        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password        'remember_token' => Str::random(10),        // add these        'user_type' => 'user',        'age' => $this->faker->numberBetween(18, 60),        'address' => $this->faker->address,    ];}最后,打开database/seeders/DatabaseSeeder.php文件并取消对该调用的注释以创建虚拟用户:// database/seeders/DatabaseSeeder.phppublic function run(){    \App\Models\User::factory(100)->create();}不要忘记.env使用将要使用的测试数据库来更新文件。在这种情况下,我将数据库命名为livecrud。完成后,运行迁移和种子器以填充数据库:php artisan migratephp artisan db:seed设置前端依赖项为简化起见,我们将使用Laravel支架进行Bootstrap。要使用它,您首先需要安装laravel/ui软件包:composer require laravel/ui接下来,安装Bootstrap4。这将在您的webpack.mix.js文件上添加配置并创建resources/js/app.js和resources/sass/app.scss文件:php artisan ui bootstrap接下来,将FontAwsome添加到resources/sass/app.scss文件中。默认情况下,其中应该已经有字体,变量和引导程序导入:// Fonts@import url("https://fonts.googleapis.com/css?family=Nunito");// Variables@import "variables";// Bootstrap@import "~bootstrap/scss/bootstrap";// add these:@import "~@fortawesome/fontawesome-free/scss/fontawesome";@import "~@fortawesome/fontawesome-free/scss/brands";@import "~@fortawesome/fontawesome-free/scss/regular";@import "~@fortawesome/fontawesome-free/scss/solid";完成后,安装所有依赖项:npm install @fortawesome/fontawesome-freenpm install创建Livewire组件您可以使用以下make:livewire命令来创建新的Livewire组件:php artisan make:livewire LiveTable这将创建以下文件:app/Http/Livewire/LiveTable.php —组件的控制器resources/views/livewire/live-table.blade.php —组件的视图文件打开resources/views/livewire/live-table.blade.php文件并添加以下内容:<div>    <div class="row mb-4">        <div class="col-md-12">          <div class="float-right mt-5">              <input wire:model="search" class="form-control" type="text" placeholder="Search Users...">          </div>        </div>    </div>    <div class="row">        @if ($users->count())        <table class="table">            <thead>                <tr>                    <th>                        <a wire:click.prevent="sortBy('name')" role="button" href="#">                            Name                            @include('includes.sort-icon', ['field' => 'name'])                        </a>                    </th>                    <th>                        <a wire:click.prevent="sortBy('email')" role="button" href="#">                            Email                            @include('includes.sort-icon', ['field' => 'email'])                        </a>                    </th>                    <th>                        <a wire:click.prevent="sortBy('address')" role="button" href="#">                            Address                            @include('includes.sort-icon', ['field' => 'address'])                        </a>                    </th>                    <th>                        <a wire:click.prevent="sortBy('age')" role="button" href="#">                            Age                            @include('includes.sort-icon', ['field' => 'age'])                        </a>                    </th>                    <th>                        <a wire:click.prevent="sortBy('created_at')" role="button" href="#">                        Created at                        @include('includes.sort-icon', ['field' => 'created_at'])                        </a>                    </th>                    <th>                        Delete                    </th>                    <th>                        Edit                    </th>                </tr>            </thead>            <tbody>                @foreach ($users as $user)                    <tr>                        <td>{{ $user->name }}</td>                        <td>{{ $user->email }}</td>                        <td>{{ $user->address }}</td>                        <td>{{ $user->age }}</td>                        <td>{{ $user->created_at->format('m-d-Y') }}</td>                        <td>                            <button class="btn btn-sm btn-danger">                            Delete                            </button>                        </td>                        <td>                            <button class="btn btn-sm btn-dark">                            Edit                            </button>                        </td>                    </tr>                @endforeach            </tbody>        </table>        @else            <div class="alert alert-warning">                Your query returned zero results.            </div>        @endif    </div>    <div class="row">        <div class="col">            {{ $users->links() }}        </div>    </div></div>这是很多代码,所以让我们从头到尾进行分解。首先,我们有用于搜索用户的搜索字段。我们希望用户在键入时能够看到其查询结果。我们实现的方法是使用wire:model。这使我们可以从组件类(LiveTable)中传入变量的名称。然后,无论用户在此字段中键入什么内容,都将同步到该变量的值。在这种情况下,我们将绑定search变量:<input wire:model="search" class="form-control" type="text" placeholder="Search Users...">稍后在LiveTable组件类的代码中,您将看到绑定变量,如下面的代码所示。这些在Livewire中称为属性。如果您来自Vue,则相当于该州。只能public直接从前端访问属性:// app/Http/Livewire/LiveTable.php<?phpclass LiveTable extends Component{  public $search = ''; // don't add this yet}接下来,我们有了表头。在这里,我们wire:click.prevent用来侦听link元素中的click事件。这些在Livewire中称为动作。实际上,它们使您可以侦听浏览器事件,但可以使用后端中的方法对其进行响应。使用.prevent阻止默认浏览器操作。您为此提供的值是您要在组件类中执行的方法的名称。在这种情况下为sortBy。然后,我们传递要排序的列的名称:<th>  <a wire:click.prevent="sortBy('name')" role="button" href="#">      Name      @include('includes.sort-icon', ['field' => 'name'])  </a></th>这是组件类中相应方法的外观。我们稍后将添加代码:// app/Http/Livewire/LiveTable.phppublic function sortBy($field){  //}在以上代码中,我们包含了另一个名为的视图文件sort-icon。创建一个resources/views/includes/sort-icon.blade.php文件并添加以下内容。这将根据用户选择的当前排序呈现当前排序图标:@if ($sortField !== $field)    <i class="text-muted fas fa-sort"></i>@elseif ($sortAsc)    <i class="fas fa-sort-up"></i>@else    <i class="fas fa-sort-down"></i>@endif标记就差不多了。其余代码基本上与标准Blade模板相同。因此,我们仍然使用该links()方法显示分页,并使用@if指令有条件地显示某些内容。现在,我们进入组件类。打开app/Http/Livewire/LiveTable.php文件并对其进行更新,使其包含以下代码:<?phpnamespace App\Http\Livewire;use Livewire\Component;use Livewire\WithPagination;use App\Models\User;class LiveTable extends Component{    use WithPagination;    public $sortField = 'name'; // default sorting field    public $sortAsc = true; // default sort direction    public $search = '';    public function sortBy($field)    {        if ($this->sortField === $field) {            $this->sortAsc = !$this->sortAsc;        } else {            $this->sortAsc = true;        }        $this->sortField = $field;    }    public function render()    {        return view('livewire.live-table', [            'users' => User::search($this->search)                ->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')                ->simplePaginate(10),        ]);    }}如前所述,我们已通过将该search变量的值绑定到客户端中的特定文本字段wire:model。因此,每次用户键入内容时,search变量也会更新。并且在更新时,该组件也会重新呈现。这是因为在render()函数中,我们依赖于search变量的值来获取用户数据。因此,对于每次击键,我们实际上都是通过提供用户的查询和选定的排序来从数据库中获取数据(我们稍后将在“优化”部分中研究如何改进此操作):User::search($this->search)                ->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')                ->simplePaginate(10)该sortBy()方法是我们用来更新用于对users表进行排序的字段的方法。每个字段可以按升序或降序排序。默认情况下,单击排序字段将按升序对其进行排序。再次单击将执行相反的操作:public function sortBy($field){    if ($this->sortField === $field) {        $this->sortAsc = !$this->sortAsc; // if field is already sorted, use the opposite instead    } else {        $this->sortAsc = true; // sort selected field by ascending by default    }    $this->sortField = $field;}在过滤users表时,我们使用该search()方法。但是我们还没有添加。更新app/Models/User.php文件以包括该search()方法。这会将用户表过滤为仅返回类型为的用户user。然后,其余条件将是我们要用于过滤搜索字段的字段:protected $casts = [    //];public static function search($query){    return empty($query) ? static::query()->where('user_type', 'user')        : static::where('user_type', 'user')            ->where(function($q) use ($query) {                $q                    ->where('name', 'LIKE', '%'. $query . '%')                    ->orWhere('email', 'LIKE', '%' . $query . '%')                    ->orWhere('address', 'LIKE ', '%' . $query . '%');            });}使用Livewire组件一旦完成,搜索和排序功能就可以使用了。打开routes/web.php文件,并用以下内容替换现有路由:Route::get('/', function () {    return view('index');});接下来,创建一个resources/views/index.blade.php文件并添加以下内容。这是我们使用创建的LiveTable组件的地方。我们可以像使用标准组件一样将其呈现到页面中。唯一的区别是,我们需要为组件名称加上前缀,livewire:并且还需要使用它@livewireScripts来呈现LivewireJavaScript文件:<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>{{ config('app.name') }}</title>    <link rel="stylesheet" type="text/css" href="{{ asset('css/app.css') }}"></head><body>    <div class="container">        <div class="row justify-content-center">            <div class="col-md-9">                <livewire:live-table />            </div>        </div>    </div>    @livewireScripts    <script src="{{ asset('js/app.js') }}"></script></body></html>此时,您现在可以开始运行该应用程序了。最简单的方法是使用Artisan服务该项目:php artisan serve然后在浏览器中通过http://127.0.0.1:8000/访问该应用。如果您通过LaravelValet或其他工具设置了本地域,则也可以使用它。删除用户接下来,让我们实现用户的删除。就像之前一样,我们使用wire:click侦听“删除”按钮上的点击事件。仅这次,我们不会直接在组件类中调用方法。那是因为这是一个删除操作。我们不希望用户误删除某人,因此我们需要显示某种确认信息才能继续删除。这是LivewireEvents的完美用例。这使我们能够与服务器之间发送和接收特定事件。您可以通过调用$emit()方法来使用它。它的第一个参数将是事件的名称,而后面的参数是您要传递给该事件的侦听器的参数。在这种情况下,我们有deleteTriggered 事件,我们将用户的ID和名称作为参数传递给侦听器。打开resources/views/livewire/live-table.blade.php文件并更新删除按钮的代码:<button class="btn btn-sm btn-danger" wire:click="$emit('deleteTriggered', {{ $user->id }}, '{{ $user->name }}')">  Delete</button>然后,我们可以在服务器或客户端上监听该事件。由于我们只想在触发此事件时显示确认,因此我们在客户端侦听它。创建一个resources/js/users.js文件并添加以下内容。如您所见,我们通过传递给侦听器的参数来访问用户的id和name:Livewire.on("deleteTriggered", (id, name) => {    const proceed = confirm(`Are you sure you want to delete ${name}`);    if (proceed) {        Livewire.emit("delete", id);    }});用户同意后,我们将发出事件,该事件实际上将删除用户。要在后端侦听事件,请创建一个$listeners数组,其中包含侦听器的名称以及它们映射到的类方法。在这种情况下,事件的名称与方法相同,因此我们只需添加即可。delete.该delete()方法随后将删除具有以下内容的用户id:// app/Http/Livewire/LiveTable.phpprotected $listeners = ['delete'];public function sortBy($field){    //}public function delete($id){    User::find($id)        ->delete();}如果要在删除用户时发出某种通知,则可以调度浏览器事件:User::find($id)        ->delete();$this->dispatchBrowserEvent('user-deleted', ['user_name' => $user->name]); // add this然后在客户端,通过标准浏览器事件监听器API监听此事件。在这里,我们仅显示一个警报,其中包含已删除用户的名称:// resources/js/users.jswindow.addEventListener("user-deleted", (event) => {    alert(`${event.detail.user_name} was deleted!`);});最后,不要忘记将users.js文件添加到webpack.mix.js:// webpack.mix.jsmix.js("resources/js/app.js", "public/js")    .sass("resources/sass/app.scss", "public/css")    .js("resources/js/users.js", "public/js") // add this    .sourceMaps();此时,如果您在浏览器上尝试删除用户,现在应该可以使用。创建新用户让我们继续创建新用户。打开resources/views/livewire/live-table.blade.php文件并添加用于创建新用户的按钮。再次,我们wire:click用来触发一个称为的事件triggerCreate:<div>    <div class="row mb-4">        <div class="col-md-12">            <div class="float-left mt-5">                <button class="btn btn-success" wire:click="$emit('triggerCreate')">Create New User</button>            </div>            <div class="float-right mt-5">                <input wire:model="search" class="form-control" type="text" placeholder="Search Users...">            </div>        </div>    </div>    <!-- code for the users table from earlier -->    <div class="row">        @if ($users->count())        @endif    </div></div>然后在您的resources/js/users.js文件中,侦听此事件并打开模式:Livewire.on("triggerCreate", () => {    $("#user-modal").modal("show");});注意:上面的代码并不是真正的“LivewirePurist”处理方式。通常,如果您使用Livewire,则希望所有交互都由Livewire处理,其中包括模态。在这种情况下,我们使用jQuery打开模式。这是可以原谅的,因为它只是一行代码。但是,如果您想使用Livewire,则正确的处理方式是对所有内容都使用Livewire。您无法将其与jQuery混合使用。当您以后需要添加前端测试时,这将使事情变得容易。我们实际上还没有创建Livewire组件,所以让我们继续进行操作:php artisan make:livewire UserForm就像LiveTable一样,这会创建组件类以及视图文件:app/Http/Livewire/UserForm.phpresources/views/livewire/user-form.blade.php首先,打开视图文件并添加以下内容。该代码看起来很熟悉,因为其中大多数只是用于呈现表单的标准Blade模板。唯一的区别是我们为它添加了一些Livewire属性,您已经熟悉了所有这些属性:<!-- resources/views/livewire/user-form.blade.php --><div>    <form wire:submit.prevent="save">        <div class="form-group">            <label for="name">Name</label>            <input type="text" class="form-control" id="name" name="name" wire:model="name">            @error('name') <span class="text-danger">{{ $message }}</span> @enderror        </div>        <div class="form-group">            <label for="exampleInputPassword1">Email</label>            <input type="email" class="form-control" id="email" name="email" wire:model="email">            @error('email') <span class="text-danger">{{ $message }}</span> @enderror        </div>        <div class="form-group">            <label for="age">Age</label>            <input type="number" class="form-control" id="age" name="age" wire:model="age">            @error('age') <span class="text-danger">{{ $message }}</span> @enderror        </div>        <div class="form-group">            <label for="address">Address</label>            <input type="text" class="form-control" id="address" name="address" wire:model="address">            @error('address') <span class="text-danger">{{ $message }}</span> @enderror        </div>        <button class="btn btn-primary" type="submit">Save</button>    </form></div>至此,您已经知道wire:submit.prevent="save"单击保存按钮时将触发该事件。.prevent阻止默认操作,即默认提交表单。然后,我们wire:model将每个字段绑定到组件类中的特定属性。此代码的另一件事:这是用于显示表单错误的标准Blade模板代码。您可能想知道当我们使用Livewire组件时,它在做什么。好吧,答案是Livewire也可以利用此代码向我们展示表单错误。因此,如果用户提交表单时未向特定字段提供值,则服务器将发出警告,并会触发以下错误以显示:@error('name') <span class="text-danger">{{ $message }}</span> @enderror接下来,打开resources/views/index.blade.php文件并添加模态的标记:<div class="container">    <div class="row justify-content-center">        <div class="col-md-9">            <livewire:live-table />        </div>    </div></div><!-- add this --><div class="modal" tabindex="-1" role="dialog" id="user-modal">    <div class="modal-dialog" role="document">        <div class="modal-content">            <div class="modal-header">                <h5 class="modal-title">User</h5>                <button type="button" class="close" data-dismiss="modal" aria-label="Close">                    <span aria-hidden="true">&times;</span>                </button>            </div>            <div class="modal-body">                <livewire:user-form>            </div>        </div>    </div></div>现在我们有了前端的代码,下面让我们看一下后端。打开app/Http/Livewire/UserForm.php并添加以下内容:<?phpnamespace App\Http\Livewire;use Livewire\Component;use App\Models\User; // add thisclass UserForm extends Component{    // add these    public $name;    public $email;    public $age;    public $address;    public function render()    {        return view('livewire.user-form');    }    // add this    public function save()    {        $validated = $this->validate([            'name' => 'required|min:10',            'email' => 'required|email|min:10',            'age' => 'required|integer',            'address' => 'required|min:10',        ]);        User::create(array_merge($validated, [            'user_type' => 'user',            'password' => bcrypt($this->email)        ]));        $this->resetForm();        $this->dispatchBrowserEvent('user-saved', ['action' => 'created', 'user_name' => $this->name]);        $this->emitTo('live-table', 'triggerRefresh');    }    public function resetForm()    {        $this->user_id = null;        $this->name = null;        $this->email = null;        $this->age = null;        $this->address = null;    }}数量很多,但是大多数内容对您来说已经很有意义,因为我们之前已经使用过它们。因此,我不会详细介绍它们。取而代之的是,让我们回顾一下为什么我以某种方式对其进行了编码。首先是我验证表格的方式。这里没有新内容。这只是标准的Laravel表单验证代码。但是为什么我用它代替Request类呢?这是因为Livewire的处理方式与Request类不兼容。基本上,LaravelRequest类仅适用于标准HTTP请求。这意味着如果出现验证错误,它将把用户重定向到上一页。我们真的不能在Livewire中拥有它,因为一切都是通过AJAX完成的:$validated = $this->validate([    'name' => 'required|min:10',    'email' => 'required|email|min:10',    'age' => 'required|integer',    'address' => 'required|min:10',]);接下来是这段代码。在这里,我们使用emitTo()而不是emit()。这使不同组件可以通过事件相互通信。它接受组件名称作为第一个参数,接受事件名称作为第二个参数:$this->dispatchBrowserEvent('user-saved', ['action' => 'created', 'user_name' => $this->name]);$this->emitTo('live-table', 'triggerRefresh');当只能使用一个事件时,为什么要使用两个单独的事件(一个浏览器事件和一个Livewire事件)?是的,确实可以只使用一个事件。问题在于,一旦创建用户,我们还需要刷新数据表。我目前不知道从客户端触发重新加载特定组件的方法,这就是为什么我使用了两个单独的事件的原因-一个用于隐藏模式并显示警报,另一个用于刷新数据表。现在您知道了代码背后的原因,让我们继续处理这些事件。将以下内容添加到resources/js/users.js文件中:window.addEventListener("user-saved", (event) => {    $("#user-modal").modal("hide");    alert(`User ${event.detail.user_name} was ${event.detail.action}!`);});然后在LiveTable组件类中,为添加侦听器triggerRefresh。这与delete侦听器有点不同,因为我们指向的$refresh是我们实际上不需要声明为类方法的函数。这是因为它是所有Livewire组件类的内置方法,可让我们重新加载整个组件:// app/Http/Livewire/LiveTable.php    protected $listeners = ['delete', 'triggerRefresh' => '$refresh'];现在,您可以在浏览器上尝试创建用户。更新现有用户我们将要实现的功能的最后一部分是更新用户。使用以下命令更新resources/views/livewire/live-table.blade.php文件中的编辑按钮。由于我们位于LiveTable组件中,而编辑功能应该位于UserForm组件中,因此我们必须用来$emitTo()将triggerEdit事件发送给UserForm组件。与之前不同,在这里我们只提供单个值,在这里我们提供整个user对象:<td>    <button class="btn btn-sm btn-dark" wire:click="$emitTo('user-form', 'triggerEdit', {{ $user }})">Edit</button></td>要监听triggerEdit事件,请打开app/Http/Livewire/UserForm.php文件并添加以下内容。单个user对象被传递给该函数,我们使用它用值填充表单字段。请注意,您可以像访问数组一样访问单个字段,而不是对象。完成后,发出dataFetched事件:protected $listeners = ['triggerEdit'];public function resetForm(){    //}public function triggerEdit($user){    $this->user_id = $user['id'];    $this->name = $user['name'];    $this->email = $user['email'];    $this->age = $user['age'];    $this->address = $user['address'];    $this->emit('dataFetched', $user);}dataFetched在客户端收听事件。由于我们已经在那一点上填入了各个字段,因此我们可以简单地打开模式:// resources/js/users.jsLivewire.on("dataFetched", (user) => {  $("#user-modal").modal("show");});最后,更新save()UserForm组件类中的方法以同样处理更新。为此,请检查该user_id字段的值。如果具有值,则表示当前正在更新用户。否则,我们创建它:// app/Http/Livewire/UserForm.phppublic function save(){    $validated = $this->validate([        'name' => 'required|min:10',        'email' => 'required|email|min:10',        'age' => 'required|integer',        'address' => 'required|min:10',    ]);    if ($this->user_id) {        User::find($this->user_id)            ->update([                'name' => $this->name,                'email' => $this->email,                'age' => $this->age,                'address' => $this->address,            ]);        $this->dispatchBrowserEvent('user-saved', ['action' => 'updated', 'user_name' => $this->name]);    } else {        User::create(array_merge($validated, [            'user_type' => 'user',            'password' => bcrypt($this->email)        ]));        $this->dispatchBrowserEvent('user-saved', ['action' => 'created', 'user_name' => $this->name]);    }    $this->resetForm();    $this->emitTo('live-table', 'triggerRefresh');}现在,您可以在浏览器中尝试编辑用户详细信息。最佳化在本节中,我们将介绍一些您可以做的优化,以确保您的应用不会使用超出其所需资源的服务器。搜索领域您可能已经注意到,在搜索字段中键入内容几乎会立即触发AJAX请求,该请求会从服务器中提取更新的数据。每次都会向数据库发送查询,因此并不是很理想。默认情况下,Livewire将150ms的反跳应用于输入。我们希望增加该延迟,以便Livewire在用户仍在键入时不会将请求发送到服务器。下面的代码添加了800ms的反跳,因此会有明显的延迟。试一试此值,以确保达到完美的平衡:<!-- resources/views/livewire/live-table.blade.php --><input wire:model.debounce.800ms="search">表格栏位我们可以做的另一个快速优化是更新用户表单中的表单字段。就像搜索字段一样,在您键入内容时,请求几乎立即发送到服务器。这次,我们使用延迟更新,而不是添加去抖动。仅当用户将焦点移到文本字段之外时,这才会向服务器发送请求:<!-- resources/views/livewire/user-form.blade.php --><input wire:model.lazy="name">结论而已!在本教程中,您学习了使用Livewire来使Laravel应用更具动态性的基础知识,而无需编写大量JavaScript来获取数据并将其提交给服务器。具体来说,我们构建了一个简单的CRUD应用程序,该应用程序使用Livewire消除了对诸如Datatables之类的前端程序包的实现表搜索和排序的需求。我们也消除了表单提交需要整页刷新的需要。最后,我们利用Livewire事件和浏览器事件来使前端和后端相互通信,而无需编写AJAX代码。...

前端开发:简单谈谈JavaScript执行模型

前端开发:简单谈谈JavaScript执行模型。JavaScript是一个单线程(Single-threaded)异步(Asynchronous)非阻塞(Non-blocking)并发(Concurrent)语言,这些语言效果通过一个调用栈(CallStack)、一个事件循环(EventLoop)、一个回调队列(CallbackQueue)有些时候也叫任务队列(TaskQueue)与跟运行环境相关的API组成。概念调用栈CallStack调用栈是一个LIFO后进先出数据结构的函数运行栈,它内部的数据结构为函数帧。当在JavaScript中调用一个函数时,它将被压入栈中,当这个函数内部还有另一个函数被调用时,另一个函数将会被压入栈顶,直到其内部没有更多调用,栈顶函数将会被以单线程方式执行并出栈,直到最后一个函数帧出栈。JavaScript语言特性中的单线程就是指的调用栈的单线程运行。function multiply(a, b) { return a * b;}function square(n) { return multiply(n, n)}function printSquare(n) { console.log(square(n));}printSquare(4);首先调用栈压入main(),扫描到printSquare()函数调用调用栈压入printSquare(4),printSquare函数内部调用square(n)该函数被压入栈,同理multiply(n,n)函数也被压入栈且没有更多调用,JavaScript引擎开始执行栈顶函数multiply(n,n)返回结果并出栈,以此类推直到main()函数出栈。调用栈有一个意外情况,当函数递归调用其自身时调用栈将溢出,执行环境将报错。function foo() { foo();}foo();任务队列TaskQueue任务队列是WebAPI的一部分,也就是说它本身并不是ECMAScript标准的一部分,而是运行环境自行实现的。任务队列是所有回调函数排队执行的FIFO先进先出队列,它的单位是任务(Task),每个任务都关联着一个用于处理这个任务的回调函数。在事件循环(EventLoop)中会将任务队列内的函数压入调用栈执行并出队列,直至为空。任务队列在浏览器的实现中被分为了宏任务队列(macrotaskqueue)和微任务队列(microtaskqueue),它们分别个自承载宏任务(macrotask)和微任务(microtask)的排队,其中宏任务队列与宏任务又被默认为常规的任务队列与任务。当调用栈内所有调用都完成执行后,事件轮询会在每次处理宏任务队列的一个宏任务后处理微任务队列的全部微任务,也就是微任务基本会在宏任务处理之前被处理。微任务处理中间不会被UI或网络事件处理被执行,微任务执行是连续的。会被添加到宏任务的方法的回调有:script:script标签中的代码解析运行setTimeoutsetIntervalsetImmediateI/OUIrendering:UI渲染,每16.6ms放到队列上一次,60fps,如果调用栈被占用则会被阻塞会被添加到微任务的WebAPI方法有:process.nextTick:Node提供的PromiseObject.observeMutationObserver微任务只会从我们编写的代码中产生,宏任务既可能从我们编写的代码中产生也可能从浏览器本身事件、渲染、IO产生。事件循环EventLoop事件循环是JavaScript的事件处理机制,它会一直轮询消息队列,当满足调用栈为空且消息队列不为空时,它将把消息队列队头的消息压入执行栈。这样的机制保证了函数不会被中断,不会有线程切换带来的数据不一致等情况事件循环在调用栈为空时轮询,顺序为1.找到任务队列(宏任务队列)的最早被添加的任务并将其添加到调用栈执行2.执行所有微任务队列内的任务当微任务队列不为空时找到微任务队列最早被添加的任务并将其添加到调用栈执行3.渲染所有变化4.如果宏任务队列为空等待宏任务出现5.返回步骤1JavaScript运行时Runtime浏览器的JavaScript代码执行也就是调用栈与堆(用于储存变量对象等)由JavaScript引擎提供,用的比较多的是谷歌的V8引擎,Chrome、Edge浏览器、Nodejs均使用该引擎。事件循环EventLoop、任务队列TaskQueue(回调队列CallbackQueue)、WebAPI或NodeAPI由运行环境提供。...

前端开发TIPS:websocket封装的应用

前端开发TIPS:websocket封装的应用。在一个应用中,websocket一般都是以单例形式存在的,即在整个应用中,websocket实例始终保持唯一。但有时我们要用到websocket实例的时候,可能websocket还没实例化,所以要做成异步的形式来获取实例。import EventEmitter from 'events'; // 这里用到了 events 包const ee = new EventEmitter();class Ws { private wsUrl: string = ''; private socket: WebSocket | undefined; // socket实例 private lockReconnect: boolean = false; // 重连锁 private timeout: NodeJS.Timeout | undefined; // 初始化socket,一般在应用启动时初始化一次就好了,或者需要更换wsUrl public init(wsUrl: string) {  this.wsUrl = wsUrl;  this.createWebSocket(); } // 获取socket实例 public getInstance(): Promise<WebSocket> {  return new Promise((resolve, reject) => {   if (this.socket) {    resolve(this.socket);   } else {    ee.on('socket', (state: string) => {     if (state === 'success') {      resolve(this.socket);     } else {      reject();     }    });   }  }); } // 创建socket private createWebSocket() {  try {   console.log('websocket 开始链接');   const socket = new WebSocket(this.wsUrl);   socket.addEventListener('close', () => {    console.log('websocket 链接关闭');    this.socket = undefined;    this.reconnect();   });   socket.addEventListener('error', () => {    console.log('websocket 发生异常了');    this.socket = undefined;    this.reconnect();   });   socket.addEventListener('open', () => {    // 可在此进行心跳检测    // this.heartCheck.start();    console.log('websocket open');    this.socket = socket;    ee.emit('socket', 'success');   });   socket.addEventListener('message', (event) => {    console.log('websocket 接收到消息', event);   });  } catch (e) {   console.log('socket catch error', e);   this.reconnect();  } } // 重连 private reconnect() {  if (this.lockReconnect) {   return;  }  console.log('websocket 正在重新连接');  this.lockReconnect = true;  //没连接上会一直重连,设置延迟避免请求过多  this.timeout && clearTimeout(this.timeout);  this.timeout = setTimeout(() => {   this.createWebSocket();   this.lockReconnect = false;  }, 5000); }}export default new Ws();封装完后,我们如何调用呢,看下代码:import socket from '@/utils/ws';socket .getInstance() .then((ws) => {   // 这里的 ws 就是实例化后的 websocket,可以直接使用 websocket 原生 api  console.log('getInstance ws', ws);  ws.addEventListener('message', (event) => {    console.log('ws 接收到消息', event);   }); }) .catch(() => {});大家去试试吧。...

前端开发报错信息解决方案:SyntaxError: Malformed formal parameter

前端开发报错信息解决方案:SyntaxError:Malformedformalparameter。哪里错了?在至少带有两个参数的 Function() 构造器中它的最后一个参数是创建的新函数的源代码。剩下的都是新函数的参数。构造器的参数有些情况下是无效的。也许是你不小心用了一个关键字 if 或 var 作为参数名称,或者在参数列表中有一些杂乱的标点符号。或者也许你不小心传递了一个无效的值,比如数字或对象。好吧,这解决了我的问题。但是为什么不开始就说明白呢?诚然,错误信息中的措辞稍微有些奇怪。"Formalparameter"是"functionargument"的另一种优美的同义。我们使用“malformed”(即畸形)这个词,因为所有的Firefox工程师都是19世纪哥特式恐怖小说的巨星。示例无效的var f = Function("x y", "return x + y;");// SyntaxError (missing a comma)var f = Function("x,", "return x;");// SyntaxError (extraneous comma)var f = Function(37, "alert('OK')");// SyntaxError (numbers can't be argument names)有效的var f = Function("x, y", "return x + y;");  // correctly punctuatedvar f = Function("x", "return x;");// if you can, avoid using Function - this is much fastervar f = function (x) { return x; };...

前端开发学习:contentType: “application/json” 的理解和应用

前端开发学习:contentType:“application/json”的理解和应用。$.ajax({  type: httpMethod,  cache:false,  async:false,  contentType: "application/json; charset=utf-8",  dataType: "json",//返回值类型  url: path+url,  data:jsonData,  success: function(data){    var resultData = '返回码='+data.status+',响应结果='+data.message+',耗时='+data.tcost;    layer.msg(resultData,{icon: 1});  },  error : function(xhr, ts, et) {    layer.msg('服务调用失败!', {icon: 2});  }});contentType:发送信息至服务器时内容编码类型,简单说告诉服务器请求类型的数据。 默认值:"application/x-www-form-urlencoded"。dataType:告诉服务器,我要想什么类型的数据,除了常见的json、XML,还可以指定html、jsonp、script或者text不使用contentType:“application/json”则data可以是对象。$.ajax({url: actionurl,type: "POST",datType: "JSON",data: { id: nodeId },async: false,success: function () {}});使用contentType:“application/json”则data只能是json字符串。$.ajax({url: actionurl,type: "POST",datType: "JSON",contentType: "application/json"data: "{'id': " + nodeId +"}",async: false,success: function () {}});...

前端开发TIPS:VUE实现数组应用

前端开发TIPS:VUE实现数组应用。提取name和callcount。getQueryCallStatistics("sesp1", this.provinceId).then((res) => {    let arr = [];    let arr1 = [];    let arr2 = [];    let arr3 = [];    let arr4 = [];    this.xunshiMap = res.data.callstatistics;    res.data.callstatistics.forEach((element) => {     // arr.push([     //  element.name,     //  element.callcount.patrol,     //  element.callcount.repair,     //  element.callcount.defect     // ]);     arr.push([element.name, 27, 38, 27]);     arr1.push(element.name);     arr2.push({ value: element.callcount.defect, name: element.name,type: element.name });     arr3.push({ value: element.callcount.patrol, name: element.name,type: element.name });     arr4.push({ value: element.callcount.repair, name: element.name,type: element.name });     // arr2.push({ value: 27, name: element.name });     // arr3.push({ value: 27, name: element.name });     // arr4.push({ value: 38, name: element.name });    });    this.xunshiMap = arr;    this.xunshiPer = arr1;    this.xunshiPer1 = arr2;    this.qxArr = arr4;    this.xqArr = arr2;    this.xsArr = arr3;    setTimeout(this.renderEachCity, 0);    this.echartCity();   });...

前端开发tips:SyntaxError: JSON.parse: bad parsing的解决方案

前端开发tips:SyntaxError:JSON.parse:badparsing的解决方案。大家先看下错误报错列表信息,如下:SyntaxError: JSON.parse: unterminated string literalSyntaxError: JSON.parse: bad control character in string literalSyntaxError: JSON.parse: bad character in string literalSyntaxError: JSON.parse: bad Unicode escapeSyntaxError: JSON.parse: bad escape characterSyntaxError: JSON.parse: unterminated stringSyntaxError: JSON.parse: no number after minus signSyntaxError: JSON.parse: unexpected non-digitSyntaxError: JSON.parse: missing digits after decimal pointSyntaxError: JSON.parse: unterminated fractional numberSyntaxError: JSON.parse: missing digits after exponent indicatorSyntaxError: JSON.parse: missing digits after exponent signSyntaxError: JSON.parse: exponent part is missing a numberSyntaxError: JSON.parse: unexpected end of dataSyntaxError: JSON.parse: unexpected keywordSyntaxError: JSON.parse: unexpected characterSyntaxError: JSON.parse: end of data while reading object contentsSyntaxError: JSON.parse: expected property name or '}'SyntaxError: JSON.parse: end of data when ',' or ']' was expectedSyntaxError: JSON.parse: expected ',' or ']' after array elementSyntaxError: JSON.parse: end of data when property name was expectedSyntaxError: JSON.parse: expected double-quoted property nameSyntaxError: JSON.parse: end of data after property name when ':' was expectedSyntaxError: JSON.parse: expected ':' after property name in objectSyntaxError: JSON.parse: end of data after property value in objectSyntaxError: JSON.parse: expected ',' or '}' after property value in objectSyntaxError: JSON.parse: expected ',' or '}' after property-value pair in object literalSyntaxError: JSON.parse: property names must be double-quoted stringsSyntaxError: JSON.parse: expected property name or '}'SyntaxError: JSON.parse: unexpected characterSyntaxError: JSON.parse: unexpected non-whitespace character after JSON data错误类型SyntaxError哪里出错了?JSON.parse() 会把一个字符串解析成 JSON对象。如果字符串书写正确,那么其将会被解析成一个有效的JSON,但是这个字符串被检测出错误语法的时候将会抛出错误。示例JSON.parse() 不允许在末尾添加多余的逗号下面两行代码都会抛出错误:JSON.parse('[1, 2, 3, 4, ]');JSON.parse('{"foo" : 1, }');// SyntaxError JSON.parse: unexpected character// at line 1 column 14 of the JSON data省略末尾多余的逗号解析JSON就是正确:JSON.parse('[1, 2, 3, 4 ]');JSON.parse('{"foo" : 1 }');JSON的属性名必须使用双引号属性名上不能使用单引号,例如: 'foo'。JSON.parse("{'foo' : 1 }");// SyntaxError: JSON.parse: expected property name or '}'// at line 1 column 2 of the JSON data取而代之,写成 "foo":JSON.parse('{"foo" : 1 }');前导0和小数点数字不能用0开头,比如01,并且你的小数点后面必须跟着至少一个数字。JSON.parse('{"foo" : 01 }');// SyntaxError: JSON.parse: expected ',' or '}' after property value// in object at line 1 column 2 of the JSON dataJSON.parse('{"foo" : 1. }');// SyntaxError: JSON.parse: unterminated fractional number// at line 1 column 2 of the JSON data正确的写法应该是只写一个1,不书写前面的0。在小数点的后面至少要跟上一个数字:JSON.parse('{"foo" : 1 }');JSON.parse('{"foo" : 1.0 }');...

Web前端开发:了解下微型前端架构的入门指南

现代Web开发可提供丰富的用户体验,涵盖了用户流和交互的范围。建立,维护,部署和交付这些体验需要大规模的开发团队和复杂的部署系统。Web应用程序的当前状态用于现代Web应用程序的最常见模式是单页应用程序(SPA)。SPA的核心原理是构建交付给用户的单个Web应用程序。SPA通过根据用户交互或数据更改来重写页面内容来工作。SPA通常将包含用于处理页面导航和深层链接的路由器,并且可以由多个组件(例如购物篮或产品列表)组成。典型的SPA应用流程遵循标准步骤:用户访问Web应用程序浏览器请求JavaScript和CSSJavaScript应用程序启动,并将初始内容添加到浏览器文档中用户与应用程序进行交互-例如单击导航链接或将产品添加到购物篮该应用程序重写了部分浏览器文档以反映更改在大多数情况下,使用JavaScript框架可实现上述目的。React,Vue或Angular等框架具有可帮助构建SPA的模式和最佳实践。作为示例,React是一个非常直观的框架,使用JSX来根据用户和数据更改呈现内容。让我们看下面的基本示例://App.jsimport React from "react";import "./styles.css";const App = () => { return (   <div className="App">     <h1>Hello I'm a SPA.</h1>   </div> );}export default App;这是我们的基本应用。它呈现了一个简单的视图:import React from "react";import ReactDOM from "react-dom";import App from "./App";const rootElement = document.getElementById("root");ReactDOM.render( <React.StrictMode>   <App /> </React.StrictMode>, rootElement);接下来,我们通过将React应用程序呈现到浏览器DOM中来启动应用程序。这只是SPA的基础。从这里,我们可以添加更多功能,例如路由和共享组件。SPA是现代开发的主要内容,但并不完美。SPA有很多缺点。其中之一是搜索引擎优化的损失,因为只有在用户在浏览器中查看应用程序后,才会呈现该应用程序。Google的网络爬虫将尝试呈现页面,但无法完全呈现应用程序,并且您将失去攀登搜索排名所需的许多关键字。框架复杂性是另一个缺点。如前所述,有许多框架可以提供SPA体验,并允许您构建可靠的SPA,但是每个框架都针对不同的需求,因此很难知道采用哪种框架。浏览器性能也可能是一个问题。因为SPA执行用户交互的所有呈现和处理,所以它可能具有连锁效应,具体取决于用户的配置。并非所有用户都会在现代浏览器上通过高速连接运行您的应用程序。为了保持流畅的用户体验,需要减小捆绑包的大小并尽可能减少客户端上的处理。以上所有这些导致最终的问题,即规模。试图构建一个可以满足用户所有需求的复杂应用程序需要多个开发人员。使用SPA可能会导致许多人使用相同的代码来尝试进行更改并引起冲突。那么所有这些问题的解决方案是什么?微型前端!什么是微型前端?微型前端是一种架构模式,用于构建可扩展的Web应用程序,该应用程序随您的开发团队一起增长,并允许您扩展用户交互。我们可以说这是我们SPA的简化版本,从而将其与现有SPA关联起来。对于用户而言,此版本仍然看起来像SPA,但在幕后,它会根据用户的流动态加载应用程序的某些部分。为了进一步说明这一点,让我们以比萨店应用程序为例。核心功能包括选择披萨,然后将其添加到您的购物篮中并签出。以下是该应用程序的SPA版本的模型。通过考虑可以拆分的应用程序的不同部分,让我们将其变成一个微型前端。我们可以按照分解创建应用程序所需的组件时的方式来考虑。所有微型前端都从宿主容器开始。这是将所有部分结合在一起的主要应用程序。这将是访问应用程序时发送给用户的主要JavaScript文件。然后,我们转到实际的微型前端-产品列表和购物篮前端。这些可以在本地与主要主机分离,并作为微型前端交付。让我们更深入地研究“与主机分离的本地”。当我们想到传统的SPA时,在大多数情况下,您将构建一个JavaScript文件并将其发送给用户。使用微型前端,我们仅将主机代码发送给用户,并且根据用户流,我们进行网络调用以获取应用程序其余部分的其他代码。该代码可以与启动主机存储在不同的服务器上,并且可以随时更新。这将导致更多的生产开发团队。如何建立一个微型前端?有多种构建微型前端的方法。对于此示例,我们将使用webpack。Webpack5发布了模块联合作为核心功能。这使您可以将远程Webpack构建导入到您的应用程序中,从而为微型前端提供易于构建和维护的模式。完整的webpack微型前端应用程序可以在这里[https://github.com/sitepoint-editors/webpack-federation-example]找到。HomeContainer首先,我们需要创建一个将成为应用程序宿主的容器。这可以是应用程序的非常基本的框架,也可以是在用户与产品进行交互之前具有菜单组件和一些基本UI的容器。使用webpack,我们可以导入ModuleFederation插件并配置容器和任何微型前端:// packages/home/webpack.config.jsconst ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");module.exports = {  ...  plugins: [    new ModuleFederationPlugin({      name: "home",      library: { type: "var", name: "home" },      filename: "remoteEntry.js",      remotes: {        "mf-products": "products",        "mf-basket": "basket",      },      exposes: {},      shared: require("./package.json").dependencies,    }),    new HtmlWebPackPlugin({      template: "./src/index.html",    }),  ],};注意:您可以查看webpack.config.js文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/webpack.config.js]。在这里,我们将模块命名为“home”,因为这是容纳所有前端的容器。然后,我们提供库详细信息,因为容器也可以是微型前端,因此我们声明有关它的详细信息,例如其类型,在这种情况下为var。类型定义了它是哪种webpack模块类型。var声明该模块是ES2015兼容模块。然后,我们将产品和购物篮模块设置为遥控器。这些将稍后在导入和使用组件时使用。将模块导入应用程序时,将使用我们提供的模块名称(“mf-products”和“mf-basket”)。配置模块后,可以将脚本标签添加到主index.html目录的主文件中,该文件将指向托管的模块。在我们的例子中,它们都在localhost上运行,但是在生产环境中,它可以在Web服务器或AmazonS3存储桶上。<!-- packages/home/src/index.html --><script src="http://localhost:8081/remoteEntry.js"></script> //product list<script src="http://localhost:8082/remoteEntry.js"></script> //basket注意:您可以查看index.html文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/src/index.html]。家用容器的最后一部分是导入和使用模块。在我们的示例中,模块是React组件,因此我们可以使用React.lazy导入它们,并像对待任何react组件一样使用它们。通过使用React.lazy我们可以导入组件,但是仅在呈现组件时才获取基础代码。这意味着即使用户不使用它们,我们也可以导入它们,并在有条件后有条件地渲染它们。让我们看一下如何使用组件:// packages/home/src/src/App.jsxconst Products = React.lazy(() => import("mf-nav/Products"));const Basket = React.lazy(() => import("mf-basket/Basket"));注意:您可以查看App.jsx文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/src/App.jsx#L4]。与标准组件用法的主要区别在于React.lazy。这是一个内置的React函数,用于处理代码的异步加载。正如我们过去在使用React.lazy代码时获取代码一样,我们需要将组件包装在Suspense组件中。这有两件事:触发组件代码的获取,并呈现正在加载的组件。除了Suspense组件和fallback组件,我们可以像其他任何React组件一样使用我们的微型前端模块。产品[ProductandBasket]配置家用容器后,我们需要设置产品和购物篮模块。这些遵循与家用容器类似的模式。首先,我们需要导入webpackModuleFederation插件,就像在home容器的webpack配置中所做的那样。然后我们配置模块设置:// packages/basket/webpack.config.jsconst ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");module.exports = {  ...  plugins: [      new ModuleFederationPlugin({        name: 'basket',        library: {          type: 'var', name: 'basket'        },        filename: 'remoteEntry.js',        exposes: {          './Basket': './src/Basket'        },        shared: require('./package.json').dependencies      })  ],};我们为模块提供一个名称,该名称将是产品或购物篮以及库的详细信息,然后是fileName-在这种情况下为远程输入。这是webpack的标准,但可以是您想要的任何东西-例如产品代码名称或模块名称。这将是webpack生成的文件,并将托管给宿主容器以供参考。使用fileNameremoteEntry,模块的完整URL将为http://myserver.com/remoteEntry.js。然后,我们定义暴露选项。这定义了模块导出的内容。在我们的例子中,这只是购物篮或产品文件,这是我们的组件。但是,这可能是多个组件或不同的资源。最后,回到家庭容器中,这就是使用这些组件的方式:// packages/home/src/src/App.jsx<div className="app-content">  <section>    <React.Suspense fallback={<div>....loading product list</div>}>      <ProductList        onBuyItem={onBuyItem}      />    </React.Suspense>  </section>  <section>    {      selected.length > 0 &&      <React.Suspense fallback={<div>....loading basket</div>}>        <Basket          items={selected}          onClear={() => setSelected([])}        />      </React.Suspense>    }  </section></div>注意:您可以查看ProductandBasketusage文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/src/App.jsx#L23]。依存关系[Dependencies]我们还没有谈论依赖性。如果您从上述代码示例中注意到,则每个webpack模块配置都有一个共享的配置选项。这告诉webpack在微型前端之间应该共享哪些Node模块。这对于减少最终应用程序上的重复非常有用。例如,如果basket和home容器都使用样式化的组件,则我们不想加载两个版本的样式化组件。您可以通过两种方式配置共享选项。第一种方法是作为您要共享的已知共享节点模块的列表。另一个选项是从其自己的程序包JSON文件中提供模块依赖项列表。这将共享所有依赖关系,并且在运行时webpack将确定所需的依赖项。例如,当导入购物篮时,webpack将能够检查其需求以及是否已共享其依赖项。如果购物篮使用Lodash,但家不使用,则它将从购物篮模块获取Lodash依赖项。如果房屋已经有Lodash,则不会加载。缺点所有这些听起来都很棒-太好了,难以置信。在某些情况下,这是完美的解决方案。在其他情况下,这可能会导致超出其价值的开销。尽管微型前端模式可以使团队更好地合作,并在应用程序的各个部分上快速前进,而不会因繁琐的部署管道,混乱的Git合并和代码审查而减慢速度,但仍有一些缺点:复制依赖逻辑。如依赖性部分所述,webpack可以为我们处理共享的Node模块。但是,当一个团队使用Lodash来实现其功能逻辑而另一个团队使用Ramda时会发生什么呢?现在,我们提供了两个功能编程库,以实现相同的结果。设计,部署和测试的复杂性。现在,我们的应用程序可以动态加载内容,因此很难完整了解整个应用程序。确保跟踪所有微型前端本身就是一项任务。部署可能会变得更加危险,因为您不确定100%在运行时将什么内容加载到应用程序中。这导致更难的测试。每个前端都可以单独进行测试,但是需要获得完整的,真实世界的用户测试,以确保该应用程序适合最终用户。标准。现在,应用程序被分解成较小的部分,很难使所有开发人员都遵循相同的标准。一些团队可能比其他团队进步更多,或者提高或降低代码质量。将所有人保持在同一页面上对于提供高质量的用户体验很重要。成熟:微前端不是一个新概念,并且在使用iframe和自定义框架之前已经实现。但是,webpack直到最近才将其作为webpack5的一部分引入。对于webpack捆绑,它仍然是新事物,并且有很多工作可以建立标准并使用这种模式发现错误。要使它成为一种强大的,可立即投入生产的模式,还有很多工作要做,可以由使用webpack的团队轻松使用。结论因此,我们学习了如何使用webpack模块联合来构建React应用程序以及如何在微前端之间共享依赖项。这种构建应用程序的模式非常适合团队将应用程序分解为较小的部分,从而与传统的SPA应用程序(部署和发布过程较慢)相比,可以更快地增长和升级。显然,这并不是可以应用于所有用例的灵丹妙药,但是在构建下一个应用程序时需要考虑这一点。由于一切仍然很新,我建议您尽早采用微型前端以进入地面,因为从微型前端模式转换为标准SPA比其他方法更容易。...

前端开发TIPS:实现box绝对居中

前端开发TIPS:实现box绝对居中<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /><title>实现box绝对居中</title><style>body * {outline: 1px #ff0000 solid;}.father {width: 500px;height: 300px;position: relative;}.father .son {width: 180px;height: 140px;/*  */position: absolute;right: 0;bottom: 0;top: 0;left: 0;margin: auto;}</style></head><body><div class="father"><div class="son">margin:auto + 上下左右:0</div></div></body></html>...

前端开发每日一学:正则表达式的使用规则

前端开发每日一学:正则表达式的使用规则。许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。下面给大家介绍正则表达式的使用规则,具体内容如下所示:\d   |匹配0-9中的任意一个数字,等效于[0-9]\D   |匹配非数字字符,等效于[^0-9]\w   |匹配任意一个字母、数字或下划线,等效于[^A-Za-z0-9_]\W  |与任何非字母、数字或下划线字符匹配,等效于[^A-Za-z0-9_]\s    |匹配任何空白字符,包括空格、制表符、换页符,等效于?[\f\n\r\t\v]\S   |匹配任何非空白字符,等效于[^\f\n\r\t\v]\n   |匹配换行符\r    |匹配一个回车符\t    |匹配制表符\v   |匹配垂直制表符\f    |匹配换页符这些字符在正则表达式中表示特殊的含义,比如:*,+,?,\,\     |转义字符,将下一个字符标记为一个特殊字符^    |匹配字符串开始的位置$    |匹配字符串结尾的位置*     |零次或多次匹配前面的字符或子表达式+    |一次或多次匹配前面的字符或子表达式?    |零次或一次匹配前面的字符或子表达式.     |“点”匹配除“\r\n”之外的任何单个字符|     |或[]   |字符集合()   |分组,要匹配圆括号字符,请使用“(”?或“)”限定字符又叫量词,是用于表示匹配的字符数量的。 *    |零次或多次匹配前面的字符或子表达式 +    |一次或多次匹配前面的字符或子表达式 ?    |零次或一次匹配前面的字符或子表达式{n}    |n是一个非负整数,匹配确定的n次{n,}   |n是非负整数,至少匹配n次{n,m}|n和m是非负整数,其中n<=m;匹配至少n次,至多m次定位字符也叫字符边界,标记匹配的不是字符而是符合某种条件的位置,所以定位字符是“零宽的”。^     |匹配字符串开始的位置,表示开始$     |匹配字符串结尾的位置,表示结尾\b   |匹配一个单词边界...

前端开发:用原生JS实现一个日历的效果

前端开发:用原生JS实现一个日历的效果 - Web前端之家www.jiangweishan.com</title><style> .week-item {  display: inline-block;  width: 80px;  height: 40px;  line-height: 40px;  border: 1px solid sandybrown;  text-align: center; } .date-item {  display: inline-block;  width: 80px;  height: 40px;  line-height: 40px;  border: 1px solid beige;  text-align: center; }</style></head><body> <div class="wrapper">  <div class="year-line">   <button id="preMonth" class="year-prev">上一月</button>   <button id="nowYear" class="year-now"></button>   <button id="nowMonth"></button>   <button id="nowDate"></button>   <button id="nextMonth" class="year-next">下一月</button>  </div>  <div id="weekLine" class="week-line"></div>  <div id="dateWrap" class="date-wrap"></div> </div></body><script> // 工具方法 - start // 1.为了获得每个月的日期有多少,我们需要判断 平年闰年[四年一闰,百年不闰,四百年再闰] const isLeapYear = (year) => {  return (year % 400 === 0) || (year % 100 !== 0 && year % 4 === 0); }; // 2.获得每个月的日期有多少,注意 month - [0-11] const getMonthCount = (year, month) => {  let arr = [   31, null, 31, 30,    31, 30, 31, 31,   30, 31, 30, 31  ];  let count = arr[month] || (isLeapYear(year) ? 29 : 28);  return Array.from(new Array(count), (item, value) => value + 1); }; // 3.获得某年某月的 1号 是星期几,这里要注意的是 JS 的 API-getDay() 是从 [日-六](0-6),返回 number const getWeekday = (year, month) => {  let date = new Date(year, month, 1);  return date.getDay(); }; // 4.获得上个月的天数 const getPreMonthCount = (year, month) => {  if (month === 0) {   return getMonthCount(year - 1, 11);  } else {   return getMonthCount(year, month - 1);  } }; // 5.获得下个月的天数 const getNextMonthCount = (year, month) => {  if (month === 11) {   return getMonthCount(year + 1, 0);  } else {   return getMonthCount(year, month + 1);  } }; // 工具方法 - end let weekStr = '日一二三四五六'; weekArr = weekStr.split('').map(item => '星期' + item); // 插入星期 dom let weekDomStr = ''; let oFragWeek = document.createDocumentFragment(); weekArr.forEach(item => {  let oSpan = document.createElement('span');  let oText = document.createTextNode(item);  oSpan.appendChild(oText);  oSpan.classList.add('week-item');  oFragWeek.appendChild(oSpan); }); let weekWrap = document.getElementById('weekLine'); weekWrap.appendChild(oFragWeek);  // 这里获得我们第一次的 数据 数组 const updateCalendar = (year, month, day) => {  if (typeof year === 'undefined' && typeof month === 'undefined' && typeof day === 'undefined') {   let nowDate = new Date();   year = nowDate.getFullYear();   month = nowDate.getMonth();   day = nowDate.getDate();  }  // 更新一下顶部的年月显示  document.getElementById('nowYear').innerHTML = year;  document.getElementById('nowMonth').innerHTML = month + 1;  document.getElementById('nowDate').innerHTML = day;  // 生成日历数据,上个月剩下的的 x 天 + 当月的 28(平年的2月)或者29(闰年的2月)或者30或者31天 + 下个月的 y 天 = 42  let res = [];  let currentMonth = getMonthCount(year, month);  let preMonth = getPreMonthCount(year, month);  let nextMonth = getNextMonthCount(year, month);  let whereMonday = getWeekday(year, month);  if (whereMonday === 0) {   whereMonday = 7  }  // 感谢网友 luoyiming 的测试(哈哈!谢谢!):这里当 whereMonday 为 0 的时候会截取上月的所有数据  let preArr = preMonth.slice(-1 * whereMonday)  let nextArr = nextMonth.slice(0, 42 - currentMonth.length - whereMonday);  res = [].concat(preArr, currentMonth, nextArr);  // 上面经过我本人的测试是没有什么问题,接下来就是更新 dom 的信息的问题  let hadDom = document.getElementsByClassName('date-item');  if (hadDom && hadDom.length) {   let domArr = document.getElementsByClassName('date-item');   for (let i = 0; i < domArr.length; i++) {    domArr[i].innerHTML = res.shift();   }  } else {   // 如果之前没有结构的话   let str = '';   for (let i = 0; i < 6; i++) {    str += '<div class="date-line">';    for (let j = 0; j < 7; j++) {     str += `<span class='date-item'>${res.shift()}</span>`;     if (j === 6) {      str += '</div>';     }    }   }   document.getElementById('dateWrap').innerHTML = str;  } };  updateCalendar(); // 添加上一月,下一月事件 let oPreButton = document.getElementById('preMonth'); let oNextButton = document.getElementById('nextMonth'); oPreButton.addEventListener('click', function () {  let currentYear = +document.getElementById('nowYear').textContent;  let currentMonth = +document.getElementById('nowMonth').textContent - 1;  let currentDate = +document.getElementById('nowDate').textContent;  if (currentMonth === 0) {   updateCalendar(currentYear - 1, 11, currentDate);  } else {   updateCalendar(currentYear, currentMonth - 1, currentDate);  } }); oNextButton.addEventListener('click', function () {  let currentYear = +document.getElementById('nowYear').textContent;  let currentMonth = +document.getElementById('nowMonth').textContent - 1;  let currentDate = +document.getElementById('nowDate').textContent;  if (currentMonth === 11) {   updateCalendar(currentYear + 1, 0, currentDate);  } else {   updateCalendar(currentYear, currentMonth + 1, currentDate);  } });</script></html>大家可以慢慢享用吧。...

聊聊前端开发中Vue.js组件间的循环引用方法

前端开发中Vue.js组件间的循环引用方法。组件是可以在它们自己的模板中调用自身的。不过它们只能通过name选项来做这件事:什么是组件:众所周知组件是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能。在有些情况下,组件也可以是原生HTML元素的形式,以is特性扩展。下面话不多说了,来一起看看本文的正文内容。引言写了大大小小不少基于vue的项目,但是基本没用到过组件循环引用的知识。为了查缺补漏,照着官方文档撸一个DEMO:https://cn.vuejs.org/v2/guide/components-edge-cases.html本人的运行版本为vue-cli@2.8.1,启用项目后,将以下js文件和vue文件放置在相应的目录中运行。main.jsimport Vue from 'vue'import App from './App'new Vue({ el: '#app', template: '<App/>', components: { App }})main.js导入App组件,并在components中注册App组件。App.vue<template> <div id="app"> <li v-for="folder in folders"> <tree-folder v-bind:folder="folder"></tree-folder> </li> </div></template><script> import TreeFolder from './components/tree-folder' export default { data: function () { return { folders: [  {  name: 'folder1',  children: [{  name: 'folder1 - folder1',  children: [{  name: 'folder1 - folder1 - folder1'  }]  }, {  name: 'folder1 - folder2',  children: [{  name: 'folder1 - folder2 - folder1'  }, {  name: 'folder1 - folder2 - folder2'  }]  }]  },  {  name: 'folder 2',  children: [{  name: 'folder2 - folder1',  children: [{  name: 'folder2 - folder1 - folder1'  }]  }, {  name: 'folder2 - folder2',  children: [{  name: 'folder2-content1'  }]  }]  },  {  name: 'folder 3',  children: [{  name: 'folder3 - folder1',  children: [{  name: 'folder3 - folder1 - folder1'  }]  }, {  name: 'folder3 - folder2',  children: [{  name: 'folder3-content1'  }]  }]  } ] } }, components: { TreeFolder } }</script>App组件导入TreeFolder组件,并在components中注册TreeFolder组件。components/tree-folder.vue<template> <p> <span>{{ folder.name }}</span> <tree-folder-contents :children="folder.children"></tree-folder-contents> </p></template><script> // 官方文档:「在我们的例子中,将 tree-folder 组件做为切入起点。我们知道制造矛盾的是 tree-folder-contents 子组件,所以我们在 tree-folder 组件的生命周期钩子函数 beforeCreate 中去注册 tree-folder-contents 组件」 export default { props: ['folder'], data: function () {  return {} }, beforeCreate: function () { // 官方文档给出的是require // this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue') // 在基于vue-cli@2.8.1按照上面的写法还是会报错 // Failed to mount component: template or render function not defined. // 所以我们应该改为基于es6的写法异步加载一个组件如下  this.$options.components.TreeFolderContents = () => import('./tree-folder-contents.vue') } }</script>TreeFolder组件导入TreeFolderContents组件,并在components中注册TreeFolderContents组件。components/tree-folder-contents.vue<template> <ul> <li v-for="child in children">  <tree-folder v-if="child.children" :folder="child"></tree-folder>  <span v-else>{{ child.name }}</span> </li> </ul></template><script> import TreeFolder from './tree-folder' export default { props: ['children'], components: {  TreeFolder } }</script>TreeFolderContents组件又导入TreeFolder组件,并在components中注册TreeFolder组件,产生了循环引用。组件之间的循环引用假设你需要构建一个文件目录树,像访达或资源管理器那样的。你可能有一个<tree-folder>组件,模板是这样的:<p>  <span>{{ folder.name }}</span>  <tree-folder-contents :children="folder.children"/></p>还有一个<tree-folder-contents>组件,模板是这样的:<ul>  <li v-for="child in children">    <tree-folder v-if="child.children" :folder="child"/>    <span v-else>{{ child.name }}</span>  </li></ul>当你仔细观察的时候,你会发现这些组件在渲染树中互为对方的后代和祖先——一个悖论!当通过Vue.component全局注册组件的时候,这个悖论会被自动解开。如果你是这样做的,那么你可以跳过这里。然而,如果你使用一个模块系统依赖/导入组件,例如通过webpack或Browserify,你会遇到一个错误:Failed to mount component: template or render function not defined.为了解释这里发生了什么,我们先把两个组件称为A和B。模块系统发现它需要A,但是首先A依赖B,但是B又依赖A,但是A又依赖B,如此往复。这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。为了解决这个问题,我们需要给模块系统一个点,在那里“A反正是需要B的,但是我们不需要先解析B。”在我们的例子中,把<tree-folder>组件设为了那个点。我们知道那个产生悖论的子组件是<tree-folder-contents>组件,所以我们会等到生命周期钩子beforeCreate时去注册它:beforeCreate: function () {      this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default}或者,在本地注册组件的时候,你可以使用webpack的异步import:components: {      TreeFolderContents: () => import('./tree-folder-contents.vue')}这样问题就解决了!...

前端开发tips:VUE中setTimeout和setInterval

前端开发tips:VUE中setTimeout和setInterval。在Vue的大型单页应用中,在某个路由下,经常会出现需要延迟执行(setTimeout)或者间隔之心(setInterval)的函数,但是每次在页面destroy之前,都必须手动清理掉。正常代码如下:beforeDestroy() {  this._timer && clearTimeout(this._timer);}但是如果一不小心,就会忘记,会造成意想不到的情况,那么有什么办法能避免这种情况吗?当然有,那就是重新写一个setTimeout的方法(或者干脆劫持window.setTimeout)。var _pageTimer = []; Vue.prototype.setTimeout = (fn, time) => {  let handler = window.setTimeout(fn, time);  _pageTimer.push(handler);     return handler;}在路由层面,当每次页面变更时,执行清理工作:router.beforeEach((to, from, next) => { _pageTimer.map(handler => { window.clearTimeout(handler); }) })再页面使用时,调用Vue的setTimeout,这样是不是方便多了呢?setInterval也是一样的。该方法还适用于对于页面异步请求的ajax处理,可以通过获取ajax的handler,在切面切换时,调用handler.abort()取消请求,避免对服务器资源的不必要的压力。补充知识:在vue中使用setTimeout,退出页面后,计时器没有销毁问题:页面在使用setTimeout定时循环某方法,或者在两个页面之间跳转时间小于定时器的时间间隔时,定时器还在运行。原因:当我们刷新页面时,会将当前页面之前创建的setTimeout以及其他定时器都清除掉,但是仅仅是路由切换是不会清除的。data (){ return{ clearTime: '' }},mounted () { randomGet () { // 在 1分钟到 2分钟之间 不定时执行   var r = Math.random() * (2 - 1) + 1   var t = Math.ceil(r * 60000)   // console.log(t)   this.clearTime = setTimeout(() => {    this.submit()    this.randomGet()   }, t)  },  submit () {   console.log('aaaa')  }},destroyed () { clearTimeout(this.clearTime) // 清除}...

分享前端开发表单选项:select应用

前端开发表单选项:select应用,直接上DEMO演示,大家预览看下效果吧。<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>列表选择 - Web前端之家www.jiangweishan.com</title></head><body>            <form name="selectform">        <select name="dropdownbox" size=1>        <option value="">请选择</option>        <option value="第一项">第一项</option>        <option value="第二项">第二项</option>        <option value="第三项">第三项</option>        <option value="第四项">第四项</option>        <option value="第五项">第五项</option>        <option value="第六项">第六项</option>        </select>        <input type=button value="添加到列表中" onClick="passText(this.form.dropdownbox.options[this.form.dropdownbox.selectedIndex].value);">        </form>        <form name="displayform" >        <span face="Arial, Helvetica, Sans Serif" size="3"><b>你可以自己选择一下:</b></span><br>        <textarea cols="30" rows="5" name="itemsbox" ></textarea>                <SCRIPT type="text/javaScript">            oldvalue = "";            function passText(passedvalue) {             if (passedvalue != "") {             var totalvalue = passedvalue+"\n"+oldvalue;             document.displayform.itemsbox.value = totalvalue;             oldvalue = document.displayform.itemsbox.value;             }            }            // End -->        </script></body></html>...

前端开发调试:分享console.log()的应用

前端开发调试应用:console.log()。对于JavaScript程序的调试,相比于alert(),使用console.log()是一种更好的方式,原因在于:alert()函数会阻断JavaScript程序的执行,从而造成副作用。一、什么是console.log()?除了一些很老版本的浏览器,现今大多数浏览器都自带调试功能;即使没有调试功能,也可以通过安装插件来进行补充。比如,老版本的Firefox没有自带调试工具,在这种情况下可以通过安装Firebug插件来添加调试功能。在具备调试功能的浏览器上,window对象中会注册一个名为console的成员变量,指代调试工具中的控制台。通过调用该console对象的log()函数,可以在控制台中打印信息。比如,以下代码将在控制台中打印”Samplelog”:window.console.log("Sample log");上述代码可以忽略window对象而直接简写为:console.log("Sample log");console.log()可以接受任何字符串、数字和JavaScript对象。与alert()函数类似,console.log()也可以接受换行符\n以及制表符\t。console.log()语句所打印的调试信息可以在浏览器的调试控制台中看到。不同的浏览器中console.log()行为可能会有所不同,本文主要探讨Firebug中console.log()的使用。二、兼容没有调试控制台的浏览器对于缺少调试控制台的老版本浏览器,window中的console对象并不存在,因此直接使用console.log()语句可能会在浏览器内部造成错误(空指针错误),并最终导致某些老版本浏览器的崩溃。为了解决这一问题,可以人为定义console对象,并声明该console对象的log函数为空函数;这样,当console.log()语句执行时,这些老版本的浏览器将不会做任何事情:if(!window.console){  window.console = {log : function(){}};}不过,在大多数情况下,没有必要去做这种兼容性工作—console.log()等调试代码应当从最终的产品代码中删除掉。三、使用参数与alert()函数类似,console.log()也可以接受变量并将其与别的字符串进行拼接://Use variablevar name = "Bob";console.log("The name is: " + name);与alert()函数不同的是,console.log()还可以接受变量作为参数传递到字符串中,其具体语法与C语言中的printf语法一致://Use parametervar people = "Alex";var years = 42;console.log("%s is %d years old.", people, years);上述代码的执行结果为:”Alexis42yearsold.”四、使用其它日志级别除了console.log(),Firebug还支持多种不同的日志级别:debug、info、warn、error。以下代码将在控制台中打印这些不同日志级别的信息://Use different logging levelconsole.log("Log level");console.debug("Debug level");console.info("Info level");console.warn("Warn level");console.error("Error level");从Firebug控制台中可以看到,不同日志级别的打印信息,其颜色和图标是不一样的;同时,可以在控制台中选择不同的日志级别来对这些信息进行过滤:...

前端开发:8个实用的JavaScript应用代码

前端开发TIPS:8个实用的JavaScript应用代码,拿走不谢。每种编程语言都有其独特的技巧。他们中的许多人是开发人员所熟知的,但是其中一些却有些骇人听闻。在本文中,我将向您展示一些我认为有用的技巧。我在实践中使用过的其中一些是解决旧问题的新方法。请享用!1.确保数组值是否曾经在需要重新创建原始数据的网格上工作,而每行的列长度可能不匹配?好吧,我有!为了确保不匹配的行之间的长度相等,可以使用Array.fill方法。let array = Array(5).fill('');console.log(array); // outputs (5) ["", "", "", "", ""]2.获取数组唯一值ES6提供了两种非常整洁的方法来从数组中提取唯一值。不幸的是,它们不能很好地填充非基本类型的数组。您可以稍后在此链接上阅读有关此内容的更多信息。处理数组重复可能很棘手。在本文中,我们将重点介绍原始数据类型。const cars = [    'Mazda',     'Ford',     'Renault',     'Opel',     'Mazda']const uniqueWithArrayFrom = Array.from(new Set(cars));console.log(uniqueWithArrayFrom); // outputs ["Mazda", "Ford", "Renault", "Opel"]const uniqueWithSpreadOperator = [...new Set(cars)];console.log(uniqueWithSpreadOperator);// outputs ["Mazda", "Ford", "Renault", "Opel"]3.使用扩展运算符合并对象和对象数组对象合并不是一件难事,过去您很有可能做到了,将来也有可能做到。不同之处在于,过去您是手动完成大部分工作的,但是现在和将来,您将使用新的ES6功能。// merging objectsconst product = { name: 'Milk', packaging: 'Plastic', price: '5$' }const manufacturer = { name: 'Company Name', address: 'The Company Address' }const productManufacturer = { ...product, ...manufacturer };console.log(productManufacturer); // outputs { name: "Company Name", packaging: "Plastic", price: "5$", address: "The Company Address" }// merging an array of objects into oneconst cities = [    { name: 'Paris', visited: 'no' },    { name: 'Lyon', visited: 'no' },    { name: 'Marseille', visited: 'yes' },    { name: 'Rome', visited: 'yes' },    { name: 'Milan', visited: 'no' },    { name: 'Palermo', visited: 'yes' },    { name: 'Genoa', visited: 'yes' },    { name: 'Berlin', visited: 'no' },    { name: 'Hamburg', visited: 'yes' },    { name: 'New York', visited: 'yes' }];const result = cities.reduce((accumulator, item) => {  return {    ...accumulator,    [item.name]: item.visited  }}, {});console.log(result);/* outputsBerlin: "no"Genoa: "yes"Hamburg: "yes"Lyon: "no"Marseille: "yes"Milan: "no"New York: "yes"Palermo: "yes"Paris: "no"Rome: "yes"*/4.映射数组(不带Array.map)您是否知道还有另一种不包含该Array.map方法的映射数组值的方法?如果没有,请在下面检查。const cities = [    { name: 'Paris', visited: 'no' },    { name: 'Lyon', visited: 'no' },    { name: 'Marseille', visited: 'yes' },    { name: 'Rome', visited: 'yes' },    { name: 'Milan', visited: 'no' },    { name: 'Palermo', visited: 'yes' },    { name: 'Genoa', visited: 'yes' },    { name: 'Berlin', visited: 'no' },    { name: 'Hamburg', visited: 'yes' },    { name: 'New York', visited: 'yes' }];const cityNames = Array.from(cities, ({ name}) => name);console.log(cityNames);// outputs ["Paris", "Lyon", "Marseille", "Rome", "Milan", "Palermo", "Genoa", "Berlin", "Hamburg", "New York"]5.条件对象属性不再需要根据条件创建两个不同的对象以使其具有特定的属性。为此,散布算子非常适合。const getUser = (emailIncluded) => {  return {    name: 'John',    surname: 'Doe',    ...emailIncluded && { email : 'john@doe.com' }    // or, if the && operator pattern is hard to read use ternary operator    // ...emailIncluded ? { email : 'john@doe.com' } : null  }}const user = getUser(true);console.log(user); // outputs { name: "John", surname: "Doe", email: "john@doe.com" }const userWithoutEmail = getUser(false);console.log(userWithoutEmail); // outputs { name: "John", surname: "Doe" }6.破坏原始数据您是否曾经处理过包含太多数据的对象?我很确定你有。可能最常见的情况是,当我们有一个包含整体数据以及详细信息的用户对象时。在这里,我们可以将新的ES破坏功能称为救援。让我们用一个示例对此进行备份。const rawUser = {   name: 'John',   surname: 'Doe',   email: 'john@doe.com',   displayName: 'SuperCoolJohn',   joined: '2020-05-05',   image: 'path-to-the-image',   followers: 45   ...}可以通过拆分为两个对象,以更上下文的方式表示上面的对象,如下所示:let user = {}, userDetails = {};({ name: user.name, surname: user.surname, ...userDetails } = rawUser);console.log(user); // outputs { name: "John", surname: "Doe" }console.log(userDetails); // outputs { email: "john@doe.com", displayName: "SuperCoolJohn", joined: "2016-05-05", image: "path-to-the-image", followers: 45 }7.动态属性名称在过去,我们首先必须声明一个对象,然后再分配一个属性(如果该属性名称需要动态)。这不可能以声明的方式实现。这些天已经过去,有了ES6功能,我们可以做到这一点。const dynamic = 'email';let user = {    name: 'John',    [dynamic]: 'john@doe.com'}console.log(user); // outputs { name: "John", email: "john@doe.com" }8.字符串插值最后但并非最不重要的是串联字符串的新方法。如果您要构建基于模板的帮助程序组件,那么可以真正发挥作用的用例。它使动态模板串联变得容易得多。const user = {  name: 'John',  surname: 'Doe',  details: {    email: 'john@doe.com',    displayName: 'SuperCoolJohn',    joined: '2016-05-05',    image: 'path-to-the-image',    followers: 45  }}const printUserInfo = (user) => {   const text = `The user is ${user.name} ${user.surname}. Email: ${user.details.email}. Display Name: ${user.details.displayName}. ${user.name} has ${user.details.followers} followers.`  console.log(text);}printUserInfo(user);// outputs 'The user is John Doe. Email: john@doe.com. Display Name: SuperCoolJohn. John has 45 followers.'结论JavaScript世界正在迅速扩展。这里有很多很酷的功能,随时可以使用。棘手且耗时的问题逐渐淡出了过去,并且借助ES6的新功能,可以立即使用解决方案。...

前端开发笔记:了解window.open的参数设置

前端开发笔记:了解window.open的参数设置。最近在使用window.open时忽略了一个细节问题:window.open新打开一个窗口,但是有时却是新打开一个窗口有时打开一个新标签页。虽然对一般的需求来说,这个两种情况都无所谓,但是对于那种有强烈区分的需求来说还是要注意的。那么怎么会出现这种不同的打开情况呢,这要从window.open方法的用法和不同浏览器来区分。 1、window.open的用法容易忽视的细节 window.open方法有三个参数:  window.open(url, [name], [configuration])其中:url,为要新打开页面的urlname,为新打开窗口的名字,可以通过此名字获取该窗口对象configuration,为新打开窗口的一些配置项,比如是否有菜单栏、滚动条、长高等等信息例如,新打开一个没有菜单栏、标题栏、工具栏,但是有滚动条、状态栏、地址栏且可伸缩窗口的方法调用如下:window.open("index.html","newWindow","menubar=0,scrollbars=1, resizable=1,status=1,titlebar=0,toolbar=0,location=1");以上只是简要描述了window.open的方法,但是这个方法容易忽略的地方就是:新打开窗口名字可以是自定义的值,此外还可以是以下几个值,与超链接a的target属性值相同窗口name值描述_blank默认的,在新窗口打开链接的url_self在当前窗口打开链接url_parent在父窗口打开链接url_top在顶级窗口打开urlframename在指定的框架中打开链接url2、window.open打开新窗口还是打开新标签页 调用window.open是打开新窗口,还是打开新标签页,其实没有什么要紧关系,但是有些需求在这方面有很强的意愿时,那就得区分一下了。具体的打开什么还是根据具体情况来定的,以下结论是经过本人测试得出的,若有不正确的地方,请大家批评指正。1)、window.open(url)或者window.open(url,name),其中name为_blank标准浏览器、IE9+是新标签打开链接urlie6-8是新窗口打开链接url2)、window.open(url,name),其中name为非_blank的其他4个值  此时会会在指定窗口或者frame打开链接url3)、window.open(url,name,configration)  只要配置了configration,所有浏览器都是新窗口打开链接url3、使用window.open方法打开的窗口可能被拦截的替代方案 现在有些浏览器为了安全起见,可能会阻止window.open打开的链接url,不管是以标签还是以窗口打开。这时可能需要用户进行浏览器设置允许弹新页,让用户设置浏览器是极不可取的做法,尤其像电商类网站,那么有其他替代方案呢?答案当然是有的,利用超链接打开的url是不会被拦截的可以实现这一点。具体做法是结合事件手动触发机制。...

Web前端开发每日TIPS:Javascript节流和防抖函数

前端开发TIPS:Javascript节流和防抖函数。这个功能我们经常会用到,尤其是在面试过程中,考官一般会问的。接下来,我们一起来了解下。1.节流函数throttle// 节流方案1,每delay的时间执行一次,通过开关控制function throttle(fn, delay, ctx) {  let isAvail = true  return function () {    let args = arguments // 开关打开时,执行任务     if (isAvail) {      fn.apply(ctx, args)      isAvail = false // delay时间之后,任务开关打开       setTimeout(function () { isAvail = true }, delay)    }  }}// 节流方案2,通过计算开始和结束时间function throttle(fn,delay){      // 记录上一次函数出发的时间      var lastTime = 0      return function(){      // 记录当前函数触发的时间      var nowTime = new Date().getTime()      // 当当前时间减去上一次执行时间大于这个指定间隔时间才让他触发这个函数      if(nowTime - lastTime > delay){        // 绑定this指向        fn.call(this)        //同步时间        lastTime = nowTime      }      } }2.防抖debounceTail2.1)只执行首次// 防抖 且首次执行// 采用原理:第一操作触发,连续操作时,最后一次操作打开任务开关(并非执行任务),任务将在下一次操作时触发)function debounceStart(fn, delay, ctx) {  let immediate = true  let movement = null  return function() {    let args = arguments         // 开关打开时,执行任务    if (immediate) {      fn.apply(ctx, args)      immediate = false    }    // 清空上一次操作    clearTimeout(movement)         // 任务开关打开    movement = setTimeout(function() {      immediate = true    }, delay)  }}2.2)只执行最后一次// 防抖 尾部执行// 采用原理:连续操作时,上次设置的setTimeout被clear掉function debounceTail(fn, delay, ctx) {  let movement = null  return function() {    let args = arguments         // 清空上一次操作    clearTimeout(movement)         // delay时间之后,任务执行    movement = setTimeout(function() {      fn.apply(ctx, args)    }, delay)  }}...

Web前端开发:聊聊input输入框全角和半角应用

前端开发:聊聊input输入框全角和半角应用 - Web前端之家https://www.jiangweishan.com</title></head> <body>        <input  type="text" onkeyup="checkBanj(this);">    <script>        function checkBanj(obj){            var str=obj.value;            var result="";            for (var i = 0; i < str.length; i++){                if (str.charCodeAt(i)==12288){                    result+= String.fromCharCode(str.charCodeAt(i)-12256);                    continue;                }                if (str.charCodeAt(i)>65280 && str.charCodeAt(i)<65375)                result+= String.fromCharCode(str.charCodeAt(i)-65248);                else result+= String.fromCharCode(str.charCodeAt(i));            }            obj.value=result;        }    </script></body> </html>试试吧!!!!...

每日一学每天一小步 成功一大步!

最新留言

  • 访客

    sddddddddddddddVS的vVSVS但是v方法v方法v发v方法v发v发v方不方便德文法文...

  • 没人发言

    鸡肋的功能...

  • 23b

    这个你应该加群找群主才对吧...

  • 访客

    大佬,能提供能一下提取图片中的文字工具的源码吗,感谢...

  • qdxx

    SEO原创还可以的,新手学习下。...

  • Web前端之家

    已处理!...

  • 程序员路灯

    贵站友链已添加名称:程序员路灯地址:http://www.eqishare.com...

  • Web前端之家

    可以加群讨论...

首页|JavaScript|HTML|HTML4|HTML5|CSS3|开发工具|性能优化|移动开发|前端教程|性能优化|开发工具|酷站欣赏|UI设计|前端教程

Copyright © 2022 Web前端之家(www.jiangweishan.com) 版权所有 All Rights Reserved.
粤ICP备12067512号-1

Copyright Your WebSite.Some Rights Reserved.

Powered By Z-BlogPHP 1.7.2