<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<div id="app">
<!--简单表达式 [类型一样直接加]=25-->
<h1>{{5+5}}</h1>
<!-- +:运算,字符串连接 【类型不一样就是拼接】=5v5,55-->
<h1>{{5+"v5"}}</h1>
<h1>{{5+"5"}}</h1>
<!-- -:减法 "5"-"5" 两个双引号 自动解析【类型一样直接算】=0,25-->
<h1>{{"5"-"5"}}</h1>
<h1>{{5*5}}</h1>
<!-- *:乘 【一样类型一样直接乘】=25-->
<h1>{{"5"*"5"}}</h1>
<!-- / 除 【不说了一样】=1,1-->
<h1>{{5/5}}</h1>
<h1>{{"5"/"5"}}</h1>
</div>
</body>
<script>
var app=new Vue({
el:"#app"//挂载到id
});
</script>
<script src="../node_modules/vue/dist/vue.min.js"></script>
<body>
<div class="app">
{{show?"GG":"MM"}}
</div>
</body>
<script>
var app=new Vue({
el:".app",
data:{
show:true//true就是MM,false就是GG
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
{{message}}<br>
<!--长度-->
{{message.length}}<br>
<!--截取根据下标-->
{{message.substring(0,3)}}
<!--根据下标从哪里开始 【3456】-->
{{message.substring(2).toUpperCase()}}<br>
<!--获取到下标 【3】-->
{{message.charAt(2)}}
</div>
</body>
<script>
var app=new Vue({
el:"#app",
data:{
message:"123456"
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--123456-->
{{message}}<br>
<!--{ "name": "华雄", "age": 69 }重写toString,就变了-->
{{user}}<br>
<!--华雄-->
{{user.name}}<br>
<!--getName(){return this.name}-->
{{user.getName}}<br>
<!--toString(){return this.name}-->
{{user.toString}}<br>
<!--{"name":"华雄","age":69} -->
{{JSON.stringify(user)}}
<!--22 json转成字符串了-->
{{JSON.stringify(user).length}}
</div>
</body>
<script>
var sss={
name:"华雄",
age:69,
getName(){return this.name},//{ "name": "华雄", "age": 69 }
//原toString---function toString() { [native code] }
toString(){return this.name}//重写toString,这样获取到就是华雄
}
var app=new Vue({
el:"#app",
data:{
message:"123456",
user:sss
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
{{woman}}<br>
{{woman[0]}}<br>
{{woman.length}}<br>
{{woman.toString()}}<br>
{{woman.join(" + ")}}
</div>
</body>
<script>
var app=new Vue({
el:"#app",
data:{
woman:["黄月英","蔡文姬","孙尚香","甄宓"]
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--纯文本 是什么样就展示什么-->
<span v-text="msg"></span><br>
<!--解析标签 会自动解析标签-->
<span v-html="msg"></span>
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
msg:"<h3>你好!中国</h3>"
}
})
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--就是直接循环-->
<ul>
<li v-for="a in woman">{{a}}</li>
</ul>
<!--循环a和下标index-->
<ul>
<li v-for="(a,index) in woman">{{a}}---{{index}}</li>
</ul>
<!--搞一个表-->
<table border="1px black">
<!--表头-->
<tr>
<th>名字</th>
<th>年龄</th>
</tr>
<!--循环里面的东西-->
<tr v-for="key in users">
<!--
aa in key aa:value值
aa,bb in key aa:value值 bb:属性名
aa,bb,index,index aa:value值 bb:属性名 index:下标
-->
<td v-for="(aa,bb,index) in key">
{{aa}}----{{bb}}---{{index+1}}
</td>
</tr>
</table>
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
woman:["黄月英","蔡文姬","孙尚香","甄宓"],
<!--List<user>-->
users:[{
name:"张三",
age:10
},{
name:"李四",
age:20
}]
}
})
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--原版以前这么写-->
<img src="123456.JPG" title="">
<!--新版 可以实现绑定,这样就能写活了-->
<img v-bind:src="src" v-bind:title="sss">
<!--title就是鼠标提示-->
<img :src="src" v-bind:title="sss">
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
//下面的值现在是写死,以后从后台获取。
src:"123456.JPG",
sss:"手放哪呢?"
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<!--这个都是一些双向绑定的案例,不好解释,太麻烦了!需要的时候代码考过去自己一看就明白了了-->
<body>
<div id="app">
<h3>绑定到type=text的input表单元素</h3>
姓名:<input type="text" v-model="inputValue"><br/>
data中的值:{{inputValue}}
<h3>绑定到type=checkbox的input表单元素</h3>
<!--v-model="checkboxValue" checkboxValue数组包含了当前value值 就会默认选中-->
打篮球:<input type="checkbox" v-model="checkboxValue" value="打篮球"><br/>
踢足球:<input type="checkbox" v-model="checkboxValue" value="踢足球"><br/>
data中的值:{{checkboxValue}}
<h3>绑定到type=radio的input表单元素</h3>
男:<input type="radio" v-model="radioValue" value="男"><br/>
女:<input type="radio" v-model="radioValue" value="女"><br/>
data中的值:{{radioValue}}
<h3>绑定到textarea的元素</h3>
个人简介:<textarea v-model="textareaValue"></textarea><br/>
data中的值:{{textareaValue}}
<h3>绑定到单选的select的元素</h3>
技能:<select v-model="skills">
<option value="java">java</option>
<option value="php">php</option>
<option value=".net">.net</option>
</select><br/>
data中的值:{{skills}}
</div>
</body>
<script>
var vue=new Vue({
el:"#app",
data:{
inputValue:"输入框的值",
checkboxValue:["打篮球"],
radioValue:"女",
textareaValue:"文本域的值",
skills:"php"
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<span v-show="show">显示</span><br>
<span v-show="hidden">不显示</span><br>
<span v-show="score<60">小于60分显示</span><br>
<span v-show="score>60">大于60分显示</span>
</div>
</body>
<script>
var app=new Vue({
el:"#app",
data:{
show:true,
hidden:false,
score:59
}
});
</script>
</html>
内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。
为了便于操作基本类型值,ECMAScript还提供了3个特殊的引用类型:Boolean、Number和String;这些类型与以上所说的引用类型相似,但同时也具有与各自的基本类型相应的特殊行为;实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够用一些方法来操作这些数据,如:
var s1="zero network";
var s2=s1.substring(4);
alert(s2);
基本数据类型不是对象,所以从逻辑上它们不应该有方法(实际上有)(原理:为了能够实现这种直观的操作,后台已经自动完成了一系列的处理:创建String对象实例,同样适用于Boolean和Number类型对应的布尔值和数字值。
基本数据类型与基本包装类型有本质上的不同,产生从类型的角度来讲,它们就是不同的;其保存的位置也不同。
引用类型与基本包装类型的区别就是对象的生存期:使用new操作符创建的引用类型对象,在执行流离开当前作用域之前都一直保存在内存中,而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁;这意味着不能在运行时为基本类型值添加属性和方法,如:
var s1="zero network";
s1.color="red";
alert(s1.color); // undefined
当然可以使用Boolean, Number和String显示的来创建基本包装类型的对象,但绝大部分情况下不需要,除非在绝对必要的情况下,因为这种做法很容易让开发者分不清是在处理基本类型还是引用类型的值;
对基本包装类型的实例调用typeof会返回object,而且所有基本包装类型的对象在转换为布尔类型时值都是true;如:
var s1=new Number(0);
// var s1=0;
alert(typeof s1);
alert(Boolean(s1));
Object构造函数,仅接受一个参数,其会根据传入值的类型返回相应基本包装类型的实例,如:
var obj=new Object("zeronetwork");
alert(obj instanceof String); // true
var n=new Object(5);
alert(n instanceof Number); // true
console.log(n.constructor);
说明:传递的值是动态的,直到运行时才确定其类型,Object()可能会调用另一个内置函数来创建对象,并且返回了一个以不同构造函数所创建的对象。
ES在必要的时会将包装对象转换成原始值,但不总是这样;如:
var s1="zeronetwork";
var s2=new String("zeronetwork");
console.log(s1==s2); // true
console.log(s1===s2); // false
var n1=18;
var n2=new Number(18);
console.log(n1==n2); // true
console.log(n1===n2); // false
说明:“==”运算符会将原始值与包装对象视为相等,但“===”全等会将它们视为不等,因为它们的类型不同,可以使用typeof查看。
注意:使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的,如:
var value="25";
var number=Number(value); // 转型函数
alert(typeof number); // number
var obj=new Number(value); // 构造函数
alert(typeof obj); // object
注:null与undefined没有包装对象,访问它们的属性会造成一个类型错误。
尽管在实际场景中不太使用基本包装类型,并且也不建议使用,但它们操作基本类型值的能力还是相当重要的,而每个基本包装类型都提供了操作相应值的便捷方法。
Boolean类型:
Boolean类型是与布尔值对应的引用类型,Boolean 对象表示两个值:"true" 或 "false"。
创建Boolean对象:var oBool=new Boolean(true); // 传入true或false值
注:如果逻辑对象无初始值或者其值为 0、-0、null、""、false、undefined 或者 NaN,那么对象的值为 false,否则,其值为 true;
在实际场景中,Boolean对象的用处不大,因为经常会造成一些误解,其中最常见的问题就是在布尔表达式中使用Boolean对象,如:
var bObj=new Boolean(false);
alert(bObj && true); // true
var bValue=false;
alert(bValue && true); // false
基本类型与引用类型还有两个区别,首先,typeof操作符对基本类型返回boolean,而对引用类型返回object;其次,由于Boolean类型的实例,所以使用instanceof操作符测试Boolean对象会返回true,而测试基本类型的布尔值则返回false,如:
var falseObject=new Boolean(false);
var falseValue=false;
alert(typeof falseObject); // object
alert(typeof falseValue); // boolean
alert(falseObject instanceof Boolean); // true
alert(falseValue instanceof Boolean); // false
总结:理解基本类型的布尔值与Boolean对象之间的区别非常重要,建议不要使用,最好使用Boolean原始值;
可以使用Boolean(参数)进行数据类型转换;
Number对象:
Number 对象,是原始数值的包装对象。在必要时,JavaScript 会自动地在原始数据和对象之间转换;
可以用构造函数 Number() 明确地创建一个 Number 对象:
var numberObject=new Number(10);
Number对象属性:
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
var x=-Number.MAX_VALUE * 2;
if(x==Number.NEGATIVE_INFINITY)
console.log("X的值:" + x);
var y=Number.MAX_VALUE * 2;
if(y==Number.POSITIVE_INFINITY)
console.log("Y的值:" + y);
注: Number.NaN 是一个特殊值,说明某些算术运算(如求负数的平方根)的结果不是数字。方法 parseInt() 和 parseFloat() 在不能解析指定的字符串时就返回这个值。
Number类或对象方法:
var number=new Number(1337);
console.log(number.toString(8));
var num=new Number(12.345678);
console.log(num.toFixed(2)); // 12.35
var num=1200.00;
console.log(num.toExponential(2));
var num=99;
console.log(num.toPrecision(1)); // 1e+2
console.log(num.toPrecision(2)); // 99
console.log(num.toPrecision(3)); // 99.0
num=10000;
console.log(num.toPrecision(1)); // 1e+4
console.log(num.toPrecision(4)); // 1.000e+4
注意:与Boolean对象类似,Number对象也以后台方式为数值提供了重要的功能;不建议直接实例化Number类型,原因与Boolean一样;一般情况下是使用数字的原始表示法;
var numberObject=new Number(10);
var numberValue=10;
alert(typeof numberObject); // object
alert(typeof numberValue); // number
alert(numberObject instanceof Number); // true
alert(numberValue instanceof Number); // false
String对象:
String类型是字符串对象包装类型,用于处理文本(字符串)。语法:var str=new String(str); 如:
var oStr=new String();
var oStr=new String("零点程序员");
var oStr=String("525");
length属性:返回字符串中的字符个数 如: str.length ;
注:即使字符串包含双字节字符(不是占一个字节的ASCII字符),每个字符也仍然算一个字符;
String 类定义了大量操作字符串的方法,例如从字符串中提取字符或子串,或者检索字符或子串。
需要注意的是,JavaScript 的字符串是不可变的(immutable),String 类定义的方法都不能改变字符串的内容。像 String.toUpperCase() 这样的方法,返回的是全新的字符串,而不是修改原始字符串。
1)字符方法:
用于访问字符串中特定字符的方法:charAt()和charCodeAt();这两个方法都接收一个参数,即基于0的字符位置;其中,charAt()方法以单字符字符串的形式返回给定位置的那个字符(ECMAScript中没有字符类型),如:
var str="zero network";
alert(str.charAt(2));
如果想得到字符编码而不是字符时,使用charCodeAt(),如:
var str="zero network";
alert(str.charCodeAt(2)); // 114
ECMAScript还定义了另一个访问字符的方法,即使用括号(类似于访问数组元素)语法,使用数字索引来访问字符串中的特定字符,如:
var str="zero network";
alert(str[2]); // 2
2)字符串操作方法:
concat()方法:用于将一或多个字符串拼接起来,返回拼接得到的新字符串,如:
var str = "zero";
var result = str.concat("network");
alert(str);
alert(result);
concat()方法可以接受多个任意参数,即可以通过它可以拼接任意多个字符串,如:
var str = "zero";
var result = str.concat("network","!");
alert(result);
说明:虽然concat()是专门用来拼接字符串的,但在实际场景中使用更多的还是加号操作符(+);而且,使用加号操作符在大多数情况下都比使用concat方法要简便易行,特别是在拼接多个字符串的情况下;
ECMAScript提供了三个基于字符串创建新字符串的方法:slice()、substr()和substring();这三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数;第一个参数指定子字符串的开始位置,第二个参数表示子字符串到哪里结束;具体来说,slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置;而substr()的第二个参数指定的则是返回的字符个数;如果没有给这些方法传递第二个参数,则将字符串的末尾作为结束位置,如:
var str = "zero network";
alert(str.slice(3));
alert(str.substring(3));
alert(str.substr(3));
alert(str.slice(3,6));
alert(str.substring(3,6));
alert(str.substr(3,6));
同时这些方法的值可以是负值;其中,slice()会将传入的负值与字符串的长度相加,substr()将负的第一个参数加上字符串的长度,而将负数的第二个参数转换为0,substring()方法会把所有负值参数都转换为0,如:
var str = "zero network";
alert(str.slice(-3));
alert(str.substring(-3));
alert(str.substr(-3));
alert(str.slice(3,-4));
alert(str.substring(3,-4));
alert(str.substr(3,-4));
一个小示例:
<style>
#mydiv{width: 100px; height: 1.5em; border: 1px solid; overflow: hidden;}
</style>
<div id="mydiv"></div>
<script>
var msg="北京零点网络科技有限公司";
var spacer="...";
var pos=0;
var mydiv = document.getElementById("mydiv");
function ScrollMsg(){
mydiv.innerHTML = msg.substring(pos,msg.length) + spacer + msg.substring(0, pos);
pos++;
if(pos>msg.length) pos=0;
window.setTimeout("ScrollMsg()",200);
}
ScrollMsg();
</script>
3)字符串位置方法:
有两个可以从字符串中查找子字符串的方法:indexOf()和lastIndexOf();这两个方法都是从一个字符串中搜索给定的子字符串,然后返回子字符串的位置,如果没有找到子字符串,则返回-1;两个方法的区别在于,一个从开头向后搜索子字符串,而lastIndexOf是从末尾向前搜索,如:
var str = "zero network";
alert(str.indexOf("o"));
alert(str.lastIndexOf("o"));
说明:如果o在字符串只出现了一次,则两个方法会返回相同的位置值;
这两个方法都可以接受可选的第二个参数,表示从字符串中的哪个位置开始搜索;如:
var str = "zero network";
alert(str.indexOf("o",6));
alert(str.lastIndexOf("o",6));
在使用第二个参数的情况下,可以循环调用这两个方法来找到所有匹配的子字符串,如:
var str = "lorem ipsum dolor sit amet, consectetur adipisicing elit";
var positions = new Array();
var pos = str.indexOf("e");
while(pos>-1){
positions.push(pos);
pos = str.indexOf("e",pos + 1);
}
alert(positions); // 3,24,32,35,52
4)trim()方法:
ECMAScript5为所有字符串定义了trim()方法;该方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果,如:
var str = " zero network ";
var trimStr = str.trim();
alert(str);
alert(trimStr);
此外,还有两个非标准的trimLeft()和trimRight()方法,分别用于删除字符串开头和末尾的空格;
var trimStr = str.trimLeft();
var trimStr = str.trimRight();
5)字符串大小写转换方法:
涉及字符串大小写转换的方法有4个:
其中toLowerCase()和toUpperCase()是最常用的方法;而toLocaleLowerCase()和toLocaleUpperCase()则是针对特定地区的实现,对有些地区来说,针对地区的方法与其通用方法得到的结果相同,但少数语言(如土耳其语)会为Unicode大小写转换应用特殊的规则,这时候就必须使用针对地区的方法来保证实现正确的转换,如:
var str = "ZERO network";
alert(str.toLocaleLowerCase());
alert(str.toLocaleUpperCase());
alert(str.toLowerCase());
alert(str.toUpperCase());
6)字符串的模式匹配方法:
String类型定义了几个用于在字符串中匹配模式的方法;
match()方法:本质上与RegExp的exec()方法相同;其只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象,如:
var text = "cat, bat, sat, fat";
var pattern = /.at/;
var matches = text.match(pattern);
alert(matches);
alert(matches.index);
alert(matches[0]);
alert(pattern.lastIndex);
说明:match()返回了一个数组,其第一项是与整个模式匹配的字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串;
search()方法:接受的参数与match()一样;该方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1;如:
var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
alert(pos); // 1
replace()方法:此方法的目的是为了简化替换子字符串的操作;该方法接受两个参数,第一个参数可以是一个RegExp对象或一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或一个函数;如果第一个参数是字符串,那么只会替换第一个子字符串,要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局g标志,如:
var text = "cat, bat, sat, fat";
var result = text.replace("at","ond");
alert(result); // cond, bat, sat, fat
result = text.replace(/at/g,"ond");
alert(result); // cond, bond, sond, fond
split()方法:
可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中;分隔符可以是字符串,也可以是RegExp对象;其可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小,如:
var colorText = "red,blue,green,yellow";
var colors1 = colorText.split(",");
var colors2 = colorText.split(",",2);
var colors3 = colorText.split(/[^\,]+/);
alert(colors1); // red,blue,green,yellow
alert(colors2); // red,blue
alert(colors3); // ,,,,,,,
7)localeCompare()方法:
localeCompare()用本地特定的顺序来比较两个字符串,默认返回下列值中的一个:-1、0、1,如:
var str = "yellow";
alert(str.localeCompare("brick")); // 1
alert(str.localeCompare("yellow")); // 0
alert(str.localeCompare("zoo")); // -1
注:利用localeCompare()可以自定义返回的值,如:
function determineOrder(value){
var result = stringValue.localeCompare(value);
if(result < 0){
alert("yellow在'"+value+"'之前");
}else if(result > 0){
alert("yellow在'"+value+"'之后");
}else{
alert("yellow与'"+value+"'相等");
}
}
determineOrder("brick");
determineOrder("yellow");
determineOrder("zoo");
localeCompare()方法比较与众不同的地方,就是实现所支持的地区(国家和语言)决定了这个方法的行为;如,美国以英语作为ECMAScript实现的标准语言,因此localeCompare()就是区分大小写的,于是大写字母在字母表中排在小写字母前头就成为了一项决定性的比较规则;在其他地区有可能就不是这种情况了。
8)fromCharCode()方法:
String构造函数本身还有一个静态方法:fromCharCode(),这个方法的任务是接受一或多个字符编码,然后将它们转换成一个字符串;从本质上看,这个方法与实例方法charCodeAt()执行的是相反的操作,如:
alert(String.fromCharCode(104,101,108,108,111)); // hello
9)HTML方法:
早期的Web浏览器可以使用Javascript动态格式化HTML,其扩展了字符串的标准,实现了一些专门用于简化常见HTML格式化任务的方法;但是,尽量不要使用这些方法,因为它们创建的标记通常无法表达语义;
示例:检测上传文件后缀名:
<input type="file" id="myFile"/>
<input type="button" value="上传" onclick="upFile()" />
<script>
function upFile(){
var fileName = document.getElementById('myFile').value;
if(fileName){
// console.log(fileName);
var pos = fileName.lastIndexOf('.'); // 找到路径中最后出现“.”的位置
// console.log(pos);
var suffix = fileName.substr(pos + 1); // 找到后缀名
// console.log(suffix);
var suffixArr = ['jpg','png','gif']; // 图片格式数组
// console.log((suffixArr.indexOf(suffix)));
if(suffixArr.indexOf(suffix) >= 0){ // 后缀名与格式数组作比较
// 上传图片的操作处理
console.log("上传成功");
}else{
console.log("图片格式不正确");
return;
}
}
}
</script>
示例:过滤脏话:
// 简单过滤
var str = "不要相信女人,女人太坏了";
var arr = ["坏","笨","傻"]; // 敏感字
for(var i = 0; i<arr.length; i++){
str = str.replace(arr[i],"*");
}
console.log(str);
// 复杂的过滤
var arrStr = [
"傻子,你的脑子是不是有病?你就是傻子一个!",
"你的眼睛是不是瞎了?",
"你是一个大坏蛋!"
];
var arr = ["病","傻子","瞎了","坏蛋"];
for(var i =0;i<arrStr.length; i++){
for(var j=0;j<arr.length; j++){
arrStr[i] = arrStr[i].replace(arr[j],"**");
// var reg = new RegExp(arr[j],"img");
// arrStr[i] = arrStr[i].replace(reg,"**");
}
}
console.log(arrStr);
Web前端开发之Javascript-零点程序员-王唯
文源代码获取:文末
启动一个新项目并从头开始处理身份验证和授权可能会筋疲力尽。它通常涉及创建登录和注册功能,这可能很耗时。管理刷新令牌和实现双因素身份验证等挑战增加了复杂性。
值得庆幸的是,随着 .NET 8 的到来,这些任务大大简化,只需要最少的配置。
在本文中,我将指导你完成各种标识配置方案,包括:
基本注册和登录。
使用持有者令牌保护终结点。
检索用户信息。
电子邮件确认。
重新发送电子邮件确认。
更改默认设置并添加自定义用户属性。
配置双因素身份验证 (MVC)。
先决条件:.NET 8 或更高版本。
在继续之前,让我概述一下我当前的环境设置:
.NET Web API 应用程序。
Visual Studio 2022 年。
利用SQLite模拟真实数据库,而不是内存数据库。
接下来,让我们安装必要的 NuGet 包。
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
首先,首先创建所有身份生成的表的存储位置。
// IdentityUser is the Microsoft base identity class.
// creates empty scheme with just all the identity tables.
class AppDbContext : IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
}
接下来,我们将注册身份验证和授权中间件,从而灵活地在持有者令牌或 Cookie 之间进行选择。在此示例中,我们将选择持有者令牌,以便于在登录时检索持有者令牌。
// Program.cs
builder.Services.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();
之后,我们将注册我们的 ,在此示例中,它是一个 SQLite 数据库。
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options=>
{
options.UseSqlite("DataSource=app.db");
});
之后,我们需要将 作为身份存储连接,以便所有操作都指向 SQLite 数据库。
// Program.cs
builder.Services.AddIdentityCore\<IdentityUser>()
.AddEntityFrameworkStores\<AppDbContext>()
.AddApiEndpoints();
最后,我们需要将所有端点添加为应用程序端点的一部分。这可以通过在构建生成器后映射标识终结点来实现。
app.MapIdentityApi<IdentityUser>();
您的Program.cs文件现在应类似于此结构。
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder=WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();
builder.Services.AddDbContext<AppDbContext>(options=>
{
options.UseSqlite("DataSource=app.db");
});
builder.Services.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<AppDbContext>()
.AddApiEndpoints();
var app=builder.Build();
app.MapIdentityApi<IdentityUser>();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
在运行应用程序之前,请不要忘记添加新的迁移,并通过从包管理器控制台执行以下命令来更新数据库。
Add-Migration initial
Update-Database
现在,运行应用程序并打开 Swagger。您应该会看到自动生成的终结点集合。
生成的身份终结点。
尝试注册新用户。如果尝试使用无效的电子邮件地址或不符合条件的密码创建新用户,则标识将返回错误以及发生的具体详细信息。
现在,让我们继续登录过程。如果提供的电子邮件或密码无效,则身份将返回 401 未授权状态。
将身份验证配置为返回持有者令牌后,我们需要将此持有者令牌用于任何需要授权的受保护终结点。
必须注意的是,生成的令牌不是标准的 JSON Web 令牌 (JWT)。此决定是有意为之的,因为内置标识主要用于简单方案。令牌选项并不意味着充当功能齐全的身份服务提供商或令牌服务器,而是无法使用 cookie 的客户端的 cookie 选项的替代方案。
现在,让我们尝试访问授权端点,特别是 /manage/info 端点。如果您尝试从 Swagger 访问它,它将显示未经授权的错误。接下来,使用 Postman 登录,保存 accessToken,并在调用 /manage/info 端点时包含它。这一次,它应该返回所需的结果。
要随时检索当前登录用户的信息,请使用存储当前登录用户信息的类。ClaimsPrincipal
要使用最小 API 方法实现此目的,请执行以下操作:
app.Map("/", (ClaimsPrincipal user)=> $"Hello {user.Identity!.Name}")
.RequireAuthorization();
对于控制器方法:
[ApiController]
[Route("[controller]")]
public class TestController: ControllerBase
{
[HttpGet]
[Authorize]
public string Get()
{
return User.Identity!.Name;
}
}
请务必注意,上述示例使用“**!”**运算符,这意味着标识应始终存在,而不可能为 。此假设成立,因为两者都是需要事先登录的授权端点。但是,建议验证标识是否存在,以防止潜在的 引用异常。
Microsoft建议使用SendGrid或其他电子邮件服务发送电子邮件,而不是SMTP。SMTP 很难保护和正确设置。
在本教程中,SendGrid 用于发送电子邮件。发送电子邮件需要 SendGrid 帐户和密钥。请参阅免费开始使用 SendGrid,注册免费的 SendGrid 帐户。
1- 在文件中配置 SendGrid 所需信息。避免将这些设置直接添加到appsettings.json文件中,以降低安全风险。secrets.json
{
"SendGridKey": "your send-grid key",
"From": "your registered email",
"Name": "your registered name"
}
注册电子邮件和姓名
SendGrid API 密钥
2- 安装必要的 NuGet 包。
dotnet add package SendGrid
dotnet add package SendGrid.Extensions.DependencyInjection
3-实现IEmailSender
要实现,请使用类似于以下内容的代码进行创建:IEmailSenderEmailSender.cs
using Microsoft.AspNetCore.Identity.UI.Services;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace Identity;
public class EmailSender : IEmailSender
{
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
public EmailSender(IConfiguration configuration, ILogger<EmailSender> logger)
{
_configuration=configuration;
_logger=logger;
}
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
var sendGridKey=_configuration["SendGridKey"];
ArgumentException.ThrowIfOrEmpty(sendGridKey, nameof(sendGridKey));
await Execute(sendGridKey, subject, message, toEmail);
}
public async Task Execute(string apiKey, string subject, string message, string toEmail)
{
var client=new SendGridClient(apiKey);
var msg=new SendGridMessage()
{
From=new EmailAddress(_configuration["From"], _configuration["Name"]),
Subject=subject,
PlainTextContent=message,
HtmlContent=message
};
msg.AddTo(new EmailAddress(toEmail));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
var response=await client.SendEmailAsync(msg);
_logger.LogInformation(response.IsSuccessStatusCode
? $"Email to {toEmail} queued successfully!"
: $"Failure Email to {toEmail}");
}
}
4- 将 EmailService 注入其中以启用其使用。Program.cs
// Program.cs
builder.Services.AddTransient<IEmailSender, EmailSender>();
5- 强制登录确认并集成 SendGrid 服务。
现在,如果您尝试再次注册,它将起作用,但您会注意到没有发送电子邮件确认。此外,用户无需任何确认即可登录。
为了解决这个问题并确保正确的流程,我们需要首先在签名过程中要求电子邮件确认。然后,我们需要集成 SendGrid 服务,以便标识框架可以利用它。
// Program.cs
builder.Services.AddIdentityCore<IdentityUser>(options=>
{
options.SignIn.RequireConfirmedEmail=true;
})
builder.Services.AddSendGrid(options=>
options.ApiKey=builder.Configuration["SendGridKey"]!
);
您的文件现在应类似于此结构。Program.cs
using Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using SendGrid.Extensions.DependencyInjection;
using System.Security.Claims;
var builder=WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddSendGrid(options=>
options.ApiKey=builder.Configuration["SendGridKey"]!
);
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddAuthorizationBuilder();
builder.Services.AddDbContext<AppDbContext>(options=>
{
options.UseSqlite("DataSource=app.db");
});
builder.Services.AddIdentityCore<IdentityUser>(options=>
{
options.SignIn.RequireConfirmedEmail=true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddApiEndpoints();
var app=builder.Build();
app.MapIdentityApi<IdentityUser>();
app.Map("/", (ClaimsPrincipal user)=> $"Hello {user.Identity!.Name} from minimal apis.")
.RequireAuthorization();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
现在,是时候测试我们的应用程序了。尝试注册一个新用户,并检查您的电子邮件以获取确认链接。单击该链接应将数据库中的确认状态更改为 TRUE。
请记住检查您的垃圾邮件文件夹,因为电子邮件通常最终会在那里。此外,请注意,电子邮件在发送之前会排队,因此预计会有几秒钟的轻微延迟。
注册新用户
Visual Studio 日志
在数据库中创建的用户
确认链接电子邮件
通过单击链接“单击此处”,它应该将您重定向回 localhost,并将用户在数据库中的确认状态更新为 TRUE。
确认消息
确认状态更改为 TRUE
最后,通过注入 SendGrid 服务,我们可以简化 EmailService,从而减少代码行数。
这是该版本的更新版本,可以有效处理所有测试方案。EmailService
using Microsoft.AspNetCore.Identity.UI.Services;
using SendGrid;
using SendGrid.Helpers.Mail;
public class EmailSender : IEmailSender
{
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
private readonly ISendGridClient _sendGridClient;
public EmailSender(IConfiguration configuration, ILogger<EmailSender> logger, ISendGridClient sendGridClient)
{
_configuration=configuration;
_logger=logger;
_sendGridClient=sendGridClient;
}
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
var msg=new SendGridMessage()
{
From=new EmailAddress(_configuration["From"], _configuration["Name"\]),
Subject=subject,
PlainTextContent=message,
HtmlContent=message
};
msg.AddTo(new EmailAddress(toEmail));
var response=await _sendGridClient.SendEmailAsync(msg);
_logger.LogInformation(response.IsSuccessStatusCode
? $"Email to {toEmail} queued successfully!"
: $"Failure Email to {toEmail}");
}
}
您当然需要重新发送确认电子邮件的选项,这对任何应用程序都至关重要。.NET 8 Identity 提供重新发送确认终结点:只需调用并设置正文,如下所示:.NET 8 Identity provides a resend confirmation endpoint: just call and set the body as follows:/resendConfirmationEmail
{
"Email": "user email"
}
Microsoft 标识还提供修改默认设置或引入新的自定义属性的功能。
调整默认设置。
builder.Services.Configure<IdentityOptions>(options=>
{
// Password settings.
options.Password.RequireDigit=true;
options.Password.RequireLowercase=true;
options.Password.RequireNonAlphanumeric=true;
options.Password.RequireUppercase=true;
options.Password.RequiredLength=6;
options.Password.RequiredUniqueChars=1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan=TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts=5;
options.Lockout.AllowedForNewUsers=true;
// User settings.
options.User.AllowedUserNameCharacters="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.\_@+";
options.User.RequireUniqueEmail=false;
});
将新的自定义属性添加到用户属性中。这可以通过创建一个新的用户类来实现,该类继承自 。
class AppUser : IdentityUser
{
public int MyProperty { get; set; }
}
随后,需要添加新的迁移,并且应将新属性集成到表中。
请记住将应用程序中出现的每个项更新为IdentityUserAppUser
在我们开始之前,这里有一些概念,在我们开始之前最好知道。
什么是多重身份验证 (MFA)?
多重身份验证 (MFA) 通过要求用户在登录过程中提供其他形式的身份标识来增强安全性。这可能包括从手机输入代码、使用 FIDO2 密钥或提供指纹扫描。通过要求第二种形式的身份验证,MFA 使攻击者更难获得未经授权的访问,因为附加因素不容易获得或复制。
什么是双因素身份验证 (2FA)?
双因素身份验证 (2FA) 类似于 MFA 的子集,但不同之处在于 MFA 可能需要两个或多个因素来证明身份。
什么是TOTP(基于时间的一次性密码算法)?
使用 ASP.NET Core Identity 时,默认支持使用 TOTP 的 MFA。此方法可与任何合规的身份验证器应用一起使用,包括:
Microsoft 身份验证器
谷歌身份验证器
什么是 MFA 短信?
与密码身份验证(单因素)相比,带有 SMS 的 MFA 大大提高了安全性。但是,不再建议使用 SMS 作为第二个因素。对于此类实现,存在太多已知的攻击媒介。
什么是在线快速身份识别 (FIDO)?
FIDO 是无密码身份验证的开放标准,允许用户在没有密码的情况下登录。FIDO2 密钥(通常是 USB,但也包括蓝牙或 NFC)通过消除密码风险来增强安全性。
它们目前被认为是最安全的 MFA 方法。
现在,让我们开始配置 2FA。
我们将利用 2FA 与 MVC 项目的集成。首次创建新的 MVC 项目时,可以选择从一开始就生成标识系统,包括“注册”、“登录”和“2FA”。我们的第一步是创建一个新的 MVC 项目。
在 Visual Studio 2022 中,可以完成以下操作:
选择个人帐户标识选项。
或者,您可以使用命令行。
dotnet new mvc -n "2FA" -au individual
创建的项目应如下所示。
现在,运行创建的项目。应显示一个带有登录和注册按钮的欢迎页面。
单击“注册”以创建新用户。
单击“单击此处确认您的帐户”以确认您的电子邮件。
接下来,注销,然后再次登录。点击右上角的电子邮件。
导航到“双因素身份验证”部分。
单击“添加身份验证器应用程序” 应显示双因素身份验证设置屏幕。
如您所见,它是开箱即用的,但没有扫描二维码的选项;它仅适用于代码。
现在,让我们添加二维码功能,允许用户只需扫描二维码即可自动添加密钥。
我们需要访问为我们创建的生成的 cshtml 页面。默认情况下,这些页面位于目录中。但是,如果尚未展开目录,则 dotnet 不会显示文件,除非生成了文件。Areas/Identity/Pages
为此,请运行该命令。
dotnet tool install -g dotnet-aspnet-codegenerator
接下来,我们需要安装所有必需的 NuGet 包。
dotnet add pacakge Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
然后,运行代码生成器并指定要检索的页面。默认情况下,如果将其留空,它将生成许多页面。但是,为了避免不必要的页面使您的项目混乱,我们将指定我们需要的特定页面。
cd "your project directory"
dotnet aspnet-codegenerator identity -dc _2FA.Data.ApplicationDbContext --files "Account.Manage.EnableAuthenticator"
若要查看可生成的所有文件,请运行该命令。
dotnet aspnet-codegenerator identity -dc _2FA.Data.ApplicationDbContext -lf
以下是可以生成的所有文件的完整列表。
File List:
Account.\_StatusMessage
Account.AccessDenied
Account.ConfirmEmail
Account.ConfirmEmailChange
Account.ExternalLogin
Account.ForgotPassword
Account.ForgotPasswordConfirmation
Account.Lockout
Account.Login
Account.LoginWith2fa
Account.LoginWithRecoveryCode
Account.Logout
Account.Manage.\_Layout
Account.Manage.\_ManageNav
Account.Manage.\_StatusMessage
Account.Manage.ChangePassword
Account.Manage.DeletePersonalData
Account.Manage.Disable2fa
Account.Manage.DownloadPersonalData
Account.Manage.Email
Account.Manage.EnableAuthenticator
Account.Manage.ExternalLogins
Account.Manage.GenerateRecoveryCodes
Account.Manage.Index
Account.Manage.PersonalData
Account.Manage.ResetAuthenticator
Account.Manage.SetPassword
Account.Manage.ShowRecoveryCodes
Account.Manage.TwoFactorAuthentication
Account.Register
Account.RegisterConfirmation
Account.ResendEmailConfirmation
Account.ResetPassword
Account.ResetPasswordConfirmation
返回到 Visual Studio,现在应该会看到已添加的文件。EnableAuthenticator.cshtml
在编辑文件之前,我们需要下载qrcode.js JavaScript库,它将为我们渲染并生成QR码。将其保存在文件夹中。EnableAuthenticator.cshtmlwwwroot\lib
在这里,我将其重命名为qrcodejs
接下来,创建一个新文件,使用以下代码在 中调用它。qr.jswwwroot\js
window.addEventListener("load", ()=> {
const uri=document.getElementById("qrCodeData").getAttribute('data-url');
new QRCode(document.getElementById("qrCode"),
{
text: uri,
width: 150,
height: 150
});
});
最后,打开文件,然后:EnableAuthenticator.cshtml
更新该部分以添加对以前下载的库的引用。Scriptsqrcode.js
添加带有调用的文件以生成二维码。qr.js
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<script type="text/javascript" src="~/lib/qrcodejs/qrcode.js"></script>
<script type="text/javascript" src="~/js/qr.js"></script>
}
再次运行应用程序,QR 码现在应该出现。
使用Authenticator应用程序扫描它,它应该可以完美运行。
通过将其添加到身份验证器应用,可以从应用中生成的代码中验证代码。它应该显示 2FA 确认。
现在,尝试注销,然后重新登录。将出现 2FA 表单,提示您输入 2FA 代码。
恭喜,您的 2FA 设置完美。
源代码获取:公众号回复消息【code:19129
】
*请认真填写需求信息,我们会在24小时内与您取得联系。