-
localStorage
永久存储
本地永久存储,就算你关闭了浏览器,数据依然存在,除非你手动清空掉它
-
localStorage.setItem(键名,键值)
设置存储 -
localStorage.getItem(键名)
获取存储值 -
localStorage.removeItem(键名)
移除存储值 -
localStorage.clear()
清空所有会话存储
<button onclick="saveData()">存储</button>
<button onclick="getData()">获取</button>
<button onclick="removeData()">移除name</button>
<button onclick="clearData()">清空</button>
<script>
function saveData(){
localStorage.setItem('name','张三');
localStorage.setItem('age','24');
localStorage.setItem('sex','24');
alert('存储成功!');
}
function getData(){
alert( localStorage.getItem('name') );
}
function removeData(){
alert( localStorage.removeItem('name') );
}
function clearData(){
localStorage.clear();
alert('清空了!');
}
</script>
在早期浏览器不支持html5
本地存储,那么使用cooike
存储,cookie
存储大小有限制,大小4KB
,存储数量也有限制,一个网站大概50
多个,cookie
它一个时效性,我们可以给cookie
设置一个保存时间,怎么删除cookie
呢?设置cookie
的时间为过去时间。
- 创建
cookie
不加过期时间的话,属于会话存储,关闭浏览器数据消失
cookie
是明文存储,不可以直接把重要信息(密码)存入cookie
//关闭浏览器,会被自动回收
document.cookie="username=龚贤";
//加上过期时间
document.cookie="username=龚贤;expires=Thu, 18 Dec 2043 12:00:00 GMT;";
//加上使用路径权限,/代表当前根目录
document.cookie="username=龚贤;expires=Thu, 18 Dec 2043 12:00:00 GMT;path=/";
- 获取
cookie
设置cookie
和获取cookie
是同一个方法
console.log( document.cookie );//username=龚贤
- 删除
cookie
删除cookie
,只需要设置一个过期的时间,删除cookie
一般是需要关闭整个浏览器之后生效。
注意:
chrome
需要以服务器的方式访问,cookie
才能生效,本地静态方式访问无效(file://
)
注意:有时候设置了关闭浏览器后
cookie
才能清除
示例
<button onclick="setItem()">存储cookie</button>
<button onclick="getItem()">读取cookie</button>
<button onclick="delItem()">删除cookie</button>
<script>
function setItem(){
document.cookie="username=老三&age=33&sex=男;expires=Thu, 18 Dec 2043 12:00:00 GMT;path=/";
alert('存储成功!');
}
function getItem(){
var str = document.cookie; // username=老三&age=33&sex=男
var params = new URLSearchParams( str );
console.log( params.get('username') );//老三
console.log( params.get('age') ); //33
console.log( params.get('sex') ); // 男
}
function delItem(){
//注意chrome浏览器
document.cookie="; expires=Thu, 01 Jan 1970 00:00:00 GMT";
alert('删除成功!');
}
</script>
cookie
、sessionStroage
和 localStroage
之间的区别
存储大小:
-
cookie
数据大小不能超过4k。 - 本地存储有存储大小的限制,可以达到
5M
或更大。
生命周期:
-
cookie
设置过期时间之前一直有效,即使窗口或浏览器关闭 -
localStorage
存储持久数据,浏览器关闭后数据不丢失除非主动删除数据 -
sessionStorage
数据在当前浏览器窗口关闭后自动删除
some()
只要一个成员的返回值是true
,那么结果就是true
。
该方法返回布尔类型(true
/false
)
var data = [
{ id:1,name:'张三'},
{ id:2,name:'李四'}
]
var result = data.some(function( item ){
return item.name == '张三';
})
console.log( result );//true
every()
每一个成员都返回值是true
,那么结果就是true
。
该方法返回布尔类型(true
/false
)
//示例一
var data = [
{ id:1,name:'张三',score: 70},
{ id:2,name:'李四',score: 56}
]
var result1 = data.every(function( item ){
return item.score >= 60 ;
})
console.log( result1 ); //false,因为李四未及格,所以返回假
//示例二
var data = [
{ id:1,name:'张三',score: 70},
{ id:2,name:'李四',score: 66}
]
var result2 = data.every(function( item ){
return item.score >= 60 ;
})
console.log( result2 );// true,每个成员都及格了,所以返回真
reduce() 和 reduceRight()
reduce
方法和reduceRight
方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce
是从左到右处理(从第一个成员到最后一个成员),reduceRight
则是从右到左(从最后一个成员到第一个成员),其他完全一样。
语法
数组.reduce( function(数组上一个成员,数组下一个成员){
},初始值默认为0)
//累积求和
var arr = [1, 2, 3, 4, 5];
var sum = arr.reduce(function (a, b) {
return a + b;
})
console.log( sum );//15
//数组对象形式的求和
var data = [
{ id:1,name:'张三',score: 10},
{ id:2,name:'李四',score: 20},
{ id:3,name:'王二',score: 30}
]
var score = data.reduce( function( prev, next ) {
return prev + next.score;
},0);
console.log( score ); //60
indexOf() 和 lastIndexOf()
数组也有indexOf
方法,注意indexOf
方法执行严格匹配,没有找到返回-1
,返回数组成员的索引值
var arr = [ 1,'2','A','b'];
console.log( arr.indexOf('1') );// -1 执行===操作
console.log( arr.indexOf('2') );// 1 返回数组成员索引值
console.log( arr.indexOf('a') );// -1 区分大小写
console.log( arr.indexOf('b') );// 3 返回索引值
includes
es6 返回数组成员是否包含某个字符,若包含返回true
,否则返回false
var arr = [ 1,'2','A','b'];
console.log( arr.includes('1') );// false
console.log( arr.includes('2') );// true
console.log( arr.includes('a') );// false
console.log( arr.includes('b') );// true
Array.from
将类数组转换为真正的数组
var str = '今天好今热今!';
var arr1 = Array.prototype.slice.call( str );
console.log( arr1 );
var arr2 = str.split('');
console.log( arr2 );
var arr3 = Array.from( str );
console.log( arr3 );
new Set()
实例化一个类数组对象
//将字符串中重复的字符抛弃,最后返回一个类数组对象
var obj = new Set( str );
console.log( obj );
findIndex
查找数组的索引值
var data = [
{ id:1, name: '网易', star:'★★★' },
{ id:2, name: '搜狐', star:'★' },
{ id:3, name: '凤凰网', star:'★★★' }
];
var index = data.findIndex( function( item ){
return item.id == 2;
})
console.log( index );//1
find
查找数组的对应成员
var data = [
{ id:1, name: '网易', star:'★★★' },
{ id:2, name: '搜狐', star:'★' },
{ id:3, name: '凤凰网', star:'★★★' }
];
var item = data.find( function( item ){
return item.id == 2;
})
console.log( item );//{id: 2, name: "搜狐", star: "★"}
函数
函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。
函数是第一等公民
函数是本身也是一种值,类似字符串、数值、布尔。
函数的声明
- 语句式声明
//函数语句式声明
function add( a, b ){
return a+b;
}
- 表达式声明
//函数表达式声明
var add = function( a, b ){
return a+b;
}
- 构造函数声明
var add = new Function( 'a', 'b','return a + b' );
示例
//语句式声明
function add( a, b ){
return a+b;
}
console.log( add(1,5) );//6
//表达式声明
var add = function( a, b ){
return a+b;
}
console.log( add(1,5) );//6
//构造函数声明
var add = new Function( 'a', 'b','return a + b' );
console.log( add(1,5) );//6
语句式声明和表达式声明有什么区别?
- 语句式声明会自动提升
//函数会自动提升,不会报错
console.log( add(1,5) ); //6
//语句式声明
function add( a, b ){
return a+b;
}
console.log( add(1,5) ); //6
- 表达式声明不提升
//不会提升,致命错误
console.log( add(1,5) ); //Uncaught TypeError: add is not a function
//表达式声明
var add = function( a, b ){
return a+b;
}
console.log( add(1,5) ); //6
构造函数声明可以用来解析字符串表达式,类似eval
console.log( eval('5+6*2') );//17
var fn = new Function( 'return 5+6*2' );
console.log( fn() );//17
函数的重复声明
代码是从上到下执行,后面的函数覆盖前面的函数,注意语句式声明函数会提升到代码顶部
//var 定义 会覆盖
var a = '111';
var a = '222';
console.log( a ); //会覆盖 222
//let 定义 不会覆盖
let b = 'aaa';
let b = 'bbb';
console.log( b );//报错,重定义错误
//const 定义 不会覆盖
const c = 'true';
const c = 'false';//报错,重定义错误
//函数 重定义 会覆盖
function add(){
console.log('hello');
}
var add = function(){
console.log('您好');
}
function add(){
console.log('fine');
}
add(); //因为语句式声明会提升,表达式不提升,所以输出您好
圆括号运算符,return 语句和递归
- 圆括号
调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。
function add(x, y) {
return x + y;
}
//调用函数加圆括号,在括号之中加入参数
add(1, 1) // 2
-
return
语句
return
返回并且跳出函数,return
是函数内部的语句。
return
和 break
的区别
function echo(){
for(var i=0;i<10;i++){
if(i>5){
return i; //跳出函数
break; //跳出循环
}
}
return 'hello';
}
console.log( echo() ); //6
return
是函数内部语句,必须有函数包裹
for( var i=0; i<10; i++ ){
console.log( i );
if(i>5){
return;
}
}
//报错,Uncaught SyntaxError: Illegal return statement
- 函数递归
函数可以调用自身,这就是递归,递归一定要有个结束值(return
),不然会形成死循环。
//因为没有结束条件,所以这是一个死循环
function fn( n ){
console.log( n );
fn( n+1 );
}
fn( 0 );//0 1 2 3 .....
//降序
function desc( n ){
console.log( n );
if( n == 0 ) return;
desc( n-1 );
}
desc( 5 );//5 4 3 2 1 0
//升序
function asc( n ){
console.log( n );
if( n == 5 ) return;
asc( n + 1 );
}
asc( 0 );//0 1 2 3 4 5
//阶乘
function fn( n ){
// console.log( n );
if( n == 0 ) {
return 1;
}else{
return n * fn( n-1 );
}
}
//斐波那契数列
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
函数是第一等公民
JavaScript
语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。
- 可以把函数赋值给变量
var add = function( a, b){
return a+b;
}
- 可以把函数赋值给对象的属性
var obj = {
name: '张三',
getAge: function(){
return 30;
}
}
- 可以当作参数传入其他函数(作为参数时不能加括号)
//1. 将函数作为参数,并提取出来声明
function add( a, b ){
console.log( a ); // function(){ }
console.log( b ); // 5
console.log( a() + b );//要加括号 需要执行
}
function fn(){
return 10;
}
add( fn, 5 );//15
//2. 将函数作为参数声明
function add( a, b ){
console.log( a() + b );
}
add( function(){
return 10;
}, 5 );// 15
函数作为参数传入到函数内部,我们也叫这个函数(参数)回调函数
//仿照数组实例forEach
function forEach( arr, fn ){
for(var i=0;i < arr.length;i++){
fn( arr[i], i, arr );
}
}
//调用
var arr = ['a','b','c','d'];
forEach( arr, function( item, index, arr){
console.log( item, index, arr );
});
//仿照数组实例fliter
function filter( arr, fn ){
var newArr = [];
for(var i=0; i < arr.length; i++ ){
var result = fn( arr[i], i, arr );
if( result ){
newArr.push( arr[i] );
}
}
return newArr;
}
var newData = filter( data, function( item, index, arr ){
return item.name=='张三';
});
console.log( newData );
函数名的提升
JavaScript
引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。
f();
function f() {}
变量提升
//1
console.log( a ); // 报错,未定义
//2
console.log( a ); // undefined
var a;
//3
console.log( a ); // undefined
var a = 10;
//4
console.log( a ); // undefined
var a = 10;
console.log( a ); // 10
变量提升面试题
function foo( x ){
if( x > 100 ) {
var tmp = x - 100;
}
console.log( tmp );
}
foo( 50 ); // undefined
//以上代码变量会提升,等同于
function foo( x ){
var tmp;
if( x > 100 ) {
tmp = x - 100;
}
console.log( tmp );
}
不能在条件语句中声明函数
不要在条件语句是声明函数,以下写法不推荐
var foo = false;
if ( foo ) {
function fn() {
console.log('hello');
}
}
fn();//报错 fn is not a function
函数的属性和方法
name
属性
//语句式声明
function f1() {
}
f1.name // "f1"
//表达式声明
var f1 = function add(){
}
f1.name // "add"
递归函数解耦
//递归
function desc( n ){
console.log( n );
if(n==0)return;
arguments.callee( n-1 ); // arguments.callee 存在兼容性问题
}
desc( 10 );
//替换上面的方法,兼容性更好
var desc = function fn( n ){
console.log( n );
if( n == 0 ) return;
fn( n-1 );
}
desc( 5 );
length
属性
函数的长度指的是形参的个数
function fn( a, b, c, d ){
console.log( fn.length );
}
console.log( fn.length ); // 4
fn(); // 4
toString()
函数的toString
方法返回一个字符串,内容是函数的源码。
function add( a, b ){
/*
这是一个
多行注释
*/
return a+b;
}
console.log( add.toString() );
函数作用域
作用域(scope)指的是变量存在的范围
js两种作用域:
- 全局作用域(全局变量):
变量在整个程序中一直存在,所有地方都可以读取,关闭当前窗口,才能释放内存。
<script>
var str = '张三';//全局变量
</script>
- 函数作用域(局部变量):
变量只在函数内部存在
注意:在函数内部使用
var
关键字声明的变量是局部变量
function fn(){
var str = '李四';
}
fn();
console.log( str ); //报错 str is not defined
注意:在函数内部省略
var
关键字,会自动提升为全局变量。
function fn(){
str = '李四';
}
fn();
console.log( str ); // 李四
<script>
var str = '张三';//全局变量
function fn(){
var age = 20; //局部变量
}
</script>
变量提升面试题 1
fn(); // undefined 20
var str = '张三';
function fn(){
var age = 20;
console.log( str, age);
}
变量提升面试题 2
function fn(){
if ( false ) {
var x = 5;
}
console.log(x);
}
fn(); // undefined
变量提升面试题 3
//代码从上到下执行
function x(){
console.log(a);
a = 10;
}
x();
console.log( a ); // Uncaught ReferenceError: a is not defined
变量提升面试题 4
function x(){
console.log(a);
var a = 10;
}
x(); // undefined
console.log( a ); // Uncaught ReferenceError: a is not defined
函数作用域面试题 1
var str = '张三';
function fn(){
var age = 20;
var str = '李四';
console.log( str, age);
}
fn(); // 李四 20
console.log( str ); // 张三
函数作用域面试题 2
var str = '张三';
function fn(){
var age = 20;
str = '李四';
console.log( str, age);
}
fn(); // 李四 20
console.log( str ); // 李四
函数作用域面试题 3
function one( ){
var str = '张三';
function two(){
var age = 20;
console.log( str );
}
two();
console.log( age );
}
one(); // 报错
函数本身的作用域
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时(调用时)所在的作用域无关。
情况 1
var a = 1;
//声明时作用域
var x = function(){
console.log(a);
}
function f() {
var a = 2;
//运行时(调用时)作用域
x();
}
f();//1
情况 2
var a = 1;
//声明时作用域
var x = function(){
var a = 5;
console.log(a);
}
function f() {
var a = 2;
//运行时(调用时)作用域
x();
}
f();//5
情况 3
//声明时作用域
var x = function(){
console.log(a);
}
function f() {
var a = 2;
//运行时(调用时)作用域
x();
}
f(); // a is not defined
情况 4
//声明时作用域
var x = function(){
console.log(a);
a = 10;
//注意没有使用var关键字 则没有优先提升权限
}
function f() {
var a = 2;
//运行时(调用时)作用域
x();
}
f(); // Uncaught ReferenceError: a is not defined
函数闭包
在函数内部返回函数,返回的函数调用了父函数的局部变量,这样形成了闭包。
- 闭包可以将函数内部的局部变量暴露给外部,是函数内部和外部的桥梁
- 闭包会导致函数内部的局部变量无法释放,不被垃圾回收站回收。
闭包的缺点:闭包因为会使局部变量内存无法及时释放,如果使用太多会增加内存开销,带来性能隐患。
function one(){
var name = '张三';
function two(){
console.log( name );
}
return two;
}
var fn = one();
fn();
闭包经典面试题 1
function bb(){
var num = 999;
return function(){
num++;
console.log( num );
}
}
var fun = bb();// function(){}
fun(); // 1000
fun(); // 1001
参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
function square(x) {
return x * x;
}
参数可以省略,从后向前省略,形参和实际参数一一对应。
运行时无论提供多少个参数,或者不提供参数都不会报错,省略的参数值是undefined
函数的length
只和形式参数个数有关,和实际参数无关
没有办法只省略靠前的参数,而保留靠后的参数,从后向前省略。
形参的作用域是函数作用域,也就是局部变量
//1 使用条件语句
function add( a, b, c){
if(a==undefined) a =0;
if(b==undefined) b =0;
if(c==undefined) c =0;
return a + b + c;
}
console.log( add( 1, 2 ) );
//2 使得短路法则
function add( a, b, c){
a = a || 0;
b = b || 0;
c = c || 0;
return a + b + c;
}
console.log( add( 1, 2 ) );
//3 默认参数 es6
function add( a = 0, b = 0, c = 0){
return a + b + c;
}
console.log( add( 1, 2 ) );
函数参数传递方式
- 参数为原始类型(数值、字符串、布尔、undefined、null)传递方式是传值传递。
- 参数为引用类型(对象、数组)传递方式是传址传递。
比较下面两道题,注意区分:
//函数内部没有加var关键字,认为全局变量
var p = 2;
function f() {
p = 3;
}
f();
console.log( p );//3
//因为p是形参,而它是直接拷贝 2,内部覆盖它的值为3 不会影响到全局变量p
var p = 2;
function f( p ) {
p = 3;
}
f( p );
console.log( p );//2
数组是传址传递,所以在数组内部会影响到外面传入的数组
var arr = ['苹果','桔子'];
function f( arr ) {
arr[1] = '香蕉';
}
f( arr );
console.log( arr );//['苹果','香蕉']
解决办法,就是复制一个新数组
//使用es6扩展运算符生成新数组
var arr = ['苹果','桔子'];
function f( [...arr] ) {
arr[1] = '香蕉';
}
f( arr );
console.log( arr );//['苹果','桔子']
//使用concat生成新数组
var arr = ['苹果','桔子'];
function f( arr ) {
arr = [].concat( arr );
arr[1] = '香蕉';
}
f( arr );
console.log( arr );//['苹果','桔子']
对象因为也是引用类型,所有在函数内部修改对象时会改变外面的对象
var obj = {
name: '张三',
info: {
age: 20
}
};
function f( obj ) {
obj.info.age = 30;
}
f( obj );
console.log( obj );//年龄会被该成30
同名参数
如果有同名的参数,则取最后出现的那个值。
即使后面的a没有值或被省略,也是以其为准。
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
arguments
参数集合对象
由于 JavaScript
允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments
对象的由来。
arguments
是一个类数组对像,它包含了数组所有参数。
arguments
对象包含了函数运行时的所有参数(实际参数)
arguments.callee
指向的是函数本身
function max(){
var args = arguments;
var arr = [];
if( args.length > 1 ){
arr = Array.from( args );
}else{
if( typeof args[0] =='string' ){
arr = args[0].split('');
}else{
arr = args[0];
}
}
arr = arr.filter(function(item){
return isNaN(item) == false;
})
var max = arr[0];
for(var i=0;i<arr.length;i++){
if ( max < arr[i] ) max = arr[i];
}
console.log( max );
}
max('230a1');
max(2,'a',30,1);
max([2,'a',13,1]);
经典面试题:多种办法单击获取索引值
<ul>
<li>首页</li>
<li>关于我们</li>
<li>产品中心</li>
</ul>
<script>
var lis = document.querySelectorAll('li');
//1. es6 块级作用域实现
for(let i = 0; i < lis.length; i++ ){
lis[i].onclick = function(){
console.log( i );
}
}
//2.将索引值缓存到对象上,然后再到对象自身去取
for(var i = 0; i < lis.length; i++ ){
lis[i].index = i;
lis[i].onclick = function(){
console.log( this.index );
}
}
//3. 使用闭包来缓存索引值
for(var i = 0; i < lis.length; i++ ){
function fn( j ){
lis[j].onclick = function(){
console.log( j );
}
}
fn( i );
}
//4. 使用数组的indexOf方法获取索引值
var arr = Array.from( lis );
for(var i = 0; i < lis.length; i++ ){
lis[i].onclick = function(){
console.log( arr.indexOf( this ) );
}
}
</script>
立即调用的函数表达式 IIFE
圆括号()是一种运算符,跟在函数名之后,表示调用该函数。
比如,print()
就表示调用print
函数。
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
//官方建议
( function(){ /* code */ } )();
( function(){ /* code */ }() );
示例
+function( a, b ){
console.log(a+b);
}(2,3);
(function( a, b ){
console.log(a+b);
})(2,3);
(function( a, b ){
console.log(a+b);
}(2,3));
立即执行函数用途:
- 不必为函数命名,避免了污染全局变量
- IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
用js写原生插件或者组件,我们习惯用IIFE封装它,避免污染全局变量。
(function(){
var iAlert = function(){
console.log('我是警告框插件!');
}
//将私有的变量注册为全局变量
window.iAlert = iAlert;
}());
//调用插件
iAlert();
定义组件时,组件默认参数通常采用覆盖替换法实现
//默认参数
var oldObj = {
page: 1,
perpage: 5
}
//用户参数
var newObj = {
page: 3,
perpage: 10
}
//合并默认参数和用户参数
var obj = Object.assign( {}, oldObj, newObj);
// var obj = { ...oldObj, ...newObj };
console.log( obj );
js 严格模式
默认是比较宽松的模式
如何设置严格模式?
全局设置
<script>
//设置严格模式
'use strict'
</script>
函数内设置
<script>
//函数内设置严格模式
function fn(){
'use strict'
}
</script>
严格模式有那些要求(部分)?
- 声明变量需要使用关键字
var
、let
、const
,否则报错
'use strict'
str = '张三';
console.log(str); //报错
- 不允许函数的形参重名
'use strict'
function x(p1, p1) {
} //报错
- 不允许删除一个不允许删除的属性
'use strict';
delete Object.prototype; //错误
- 变量名不能使用
arguments
字符串
'use strict';
var arguments = 10;
console.log( arguments );
- 由于一些安全原因,在作用域
eval()
创建的变量不能被调用
'use strict';
eval ('var x = 2');
alert (x);
- 禁止this关键字指向全局对象。
'use strict';
function fn(){
console.log(this);
}
fn();//本来是指向windows,但在严格模式下不允许,返回undefined
- 不允许删除变量或对象。
'use strict';
var x = 3.14;
delete x;
使用new Function
将字符串当作命令执行。
语法
new Function('参数1','参数2','...','函数内部返回');
最后一个参数始终是函数内部返回代码
var jsonp = 'foo({"id": 42})';
// foo是函数的参数,它是一个函数(回调函数)。
var f = new Function( 'foo', jsonp );
// 相当于定义了如下函数
// function f(foo) {
// foo({"id":42}); //回调函数
// }
//调用
f(function (json) {
console.log( json.id ); // 42
})
jsonp原理
为了便于客户端使用数据(比如远程数据),逐渐形成了一种非正式传输协议,进行数据传送,这种方式我们叫JSONP
跨域,该协议的一个要点就是允许服务端返回一个回调函数,这个回调函数包裹这数据,这样客户端就可以方便的取到服务器返回的数据。
jsonp
主要原理是利用js回调函数机制实现一种非正式跨域。
- 远程服务端返回一个函数,该函数包裹数据
//服务端
//假设以下 http://www.jx-tongan.com/vueapi/hello.js 的内容
fn({
name:'张三',
age: 20,
habby: ['钓鱼','游泳']
});
- 客户端通过回调函数的形参获取到服务端返回的数据
<script>
//客户端
//服务端的数据在形参obj上
function city( obj ){
console.log( obj );
}
</script>
<script src="js/city.js"></script>
运算符
算术运算符
- 指数运算符(注意兼容性)
console.log( 2 ** 2 );// 4
console.log( 2 ** 3 );// 8
- 取余
console.log( 10 % 3); // 1
console.log( 13 % 6); // 1
- 自增运算符
注意下面代码的区别
//示例 1
var a = 0;
var b = 0;
a++;
++b;
console.log( a, b ); //1 1
//示例 2
var x = 0;
var y = 0;
console.log( x++, ++y ); //0 1
赋值运算符
最常见的赋值运算符,当然就是等号(=)。
var x = 1;
var x = y;
赋值运算符还可以与其他运算符结合,形成变体
// 等同于 x = x + y
x += y
// 等同于 x = x - y
x -= y
// 等同于 x = x * y
x *= y
// 等同于 x = x / y
x /= y
// 等同于 x = x % y
x %= y
// 等同于 x = x ** y
x **= y
比较运算符
提供了8个比较运算符:
-
<
小于运算符 -
>
大于运算符 -
<=
小于或等于运算符 -
>=
大于或等于运算符 -
==
相等运算符 -
===
严格相等运算符 -
!=
不相等运算符 -
!==
严格不相等运算符
字符串按照字典顺序进行比较。
'cat' > 'dog' // false
'cat' > 'catalog' // false
'大' > '小' // false
上面代码中,“大”的 Unicode 码点是22823,“小”是23567,因此返回false。
两个原始类型的值的比较,除了相等运算符(==)和严格相等运算符(===),其他比较运算符都是先转成数值再比较。
+0 === -0 // true
{} === {} // false
[] === [] // false
(function () {} === function () {}) // false
new Date() > new Date() // false
new Date() < new Date() // false
new Date() === new Date() // false
NaN === NaN // false
undefined === undefined // true
null === null // true
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
undefined == null // true
相等运算符隐藏的类型转换,会带来一些违反直觉的结果。
0 == '' // true
0 == '0' // true
2 == true // false
2 == false // false
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
上面这些表达式都很容易出错,因此不要使用相等运算符(==
),最好只使用严格相等运算符(===
)。
布尔运算符
-
!
:取反运算符 -
&&
:且运算符 -
||
:或运算符 -
?:
:三元运算符
取反运算符是一个感叹号,用于将布尔值变为相反值,即true
变成false
,false
变成true
。
用取反来写开关效果
<button id="btn">按钮</button>
<script>
var flag = false;
document.getElementById('btn').onclick = function(){
flag = !flag;
console.log(flag);
}
</script>
!!
相当于使用Boolean
转换为布尔类型
!!x
// 等同于
Boolean(x)
示例
var str = '111';
console.log( Boolean( str ) );//true
console.log( !!str );//true
且运算符(&&)
它的运算规则是:
- 如果第一个运算符的布尔值为
true
,那么返回第二个运算符的值。 - 如果第一个运算符的布尔值为
false
,那么返回第一个运算符的值。
true && 'hello'; // hello
false && 'hello'; // false
'张三' && 20; // 20
'0' && 'ab'; // ab
0 && 'ab'; // 0
1>2 && ''; // false
'' && 'ab'; // ''
1 && 0 && 'ab'; // 0
'a' && 'null' && 0; // 0
var age = 20;
if( age > 18 ) console.log('成年!');
console.log( age > 18 && '成年!' );
或运算符(||)
它的运算规则是:
- 如果第一个运算符的布尔值为
true
,那么返回第一个运算符的值。 - 如果第一个运算符的布尔值为
false
,那么返回第二个运算符的值。
true || 'ab'; // true
false || 'ab'; // ab
'false' || 'ab'; // false
0 || null; // null
'' || 0 || null || 'ab'; // ab
'ab' || null || 0; // ab
且运算:遇真返回第2个值,遇假直接输出假。
或运算:遇假返回第2个值,遇真直接输出真。
//且运算:遇真返回第2个值,遇假直接输出假。
//或运算:遇假返回第2个值,遇真直接输出真。
var str1 = 'he' && '0' && [] || 20 || {};
console.log( str1 );// []
var str2 = 'he' && '0' && '' || 0 || {};
console.log( str2 );// {}
用且或来替代条件语句
if (i) {
doSomething();
}
// 等价于
i && doSomething();
三元条件运算符
语法
console.log( true ? '真' : '假');
't' ? 'hello' : 'world' // "hello"
0 ? 'hello' : 'world' // "world"
void
运算符
void
运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
。
console.log( 3*5 ); // 15
console.log( void(3*5) ); // undefined
阻止超级链接默认事件
<div style="height:2000px;"></div>
<a href="javascript:void(0)">连接1</a>
<a href="javascript:;">连接2</a>
<a>连接3</a>
逗号运算符
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
注意要加上括号,不然会报错
var str1 = ( 2+4, 3*5 );
console.log( str ); //15
var str2 = ( a = 2+4, a + 3*5 );
console.log( str );//21
var str3 = ( a = 2+4, 3*5 );
console.log( a, str ); //6 15
错误处理
JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象,JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。
创建错误对象实例
var err = new Error('出错了');
err.message // "出错了"
try catch
错误捕获语句,它将语句在沙箱中运行,在语句中可以抛出错误。避免发送致命错误,导致语句中断无法继续。
//错误捕获
try {
if(!document.getElementById('box1')){
throw new Error('错误:不存在的对象!');
}
document.getElementById('box1').innerText='错误代码演示';
} catch( e ) {
//e 是错误对象
console.dir(e);
} finally {
console.log('无论对错都会执行');
}
console.log('代码继续执行');
js错误类型
-
SyntaxError:
语法错误
// 1. Syntax Error: 语法错误
// 1.1 变量名不符合规范
var 1 // Uncaught SyntaxError: Unexpected number
var 1a // Uncaught SyntaxError: Invalid or unexpected token
// 1.2 给关键字赋值
function = 5 // Uncaught SyntaxError: Unexpected token =
-
Uncaught ReferenceError:
引用错误
引用一个不存在的变量时发生的错误
将一个值分配给无法分配的对象,比如对函数的运行结果或者函数赋值。
// 2.1 引用了不存在的变量
a() // Uncaught ReferenceError: a is not defined
console.log(b) // Uncaught ReferenceError: b is not defined
// 2.2 给一个无法被赋值的对象赋值
console.log("abc") = 1 // Uncaught ReferenceError: Invalid left-hand side in assignment
-
RangeError:
范围错误
// 3.1 数组长度为负数
[].length = -5 // Uncaught RangeError: Invalid array length
// 3.2 Number对象的方法参数超出范围
var num = new Number(12.34)
console.log(num.toFixed(-1)) // Uncaught RangeError: toFixed() digits argument must be between 0 and 20 at Number.toFixed
// 说明: toFixed方法的作用是将数字四舍五入为指定小数位数的数字,参数是小数点后的位数,范围为0-20.
-
TypeError
类型错误
变量或参数不是预期类型时发生的错误。比如使用new字符串、布尔值等原始类型和调用对象不存在的方法就会抛出这种错误,因为new命令的参数应该是一个构造函数。
// 4.1 调用不存在的方法
123() // Uncaught TypeError: 123 is not a function
var o = {}
o.run() // Uncaught TypeError: o.run is not a function
// 4.2 new关键字后接基本类型
var p = new 456 // Uncaught TypeError: 456 is not a constructor
-
URIError,URL
错误
主要是相关函数的参数不正确。
decodeURI("%") // Uncaught URIError: URI malformed at decodeURI
URI相关参数不正确时抛出的错误,主要涉及encodeURI、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()六个函数。
-
EvalError eval()
函数执行错误
在ES5以下的JavaScript中,当eval()函数没有被正确执行时,会抛出evalError错误。
var myEval = eval;
myEval("alert('call eval')");
编程风格
缩进
缩进建议统一使用空格(1个tab=2个空格,1个tab=4个空格),因为不同操作系统对tab解析不不太一样。
//bad
function fn(){
if(1==1){
console.log('aaa');
}else{
console.log('bbb');
}
}
//good
function fn(){
if(1==1){
console.log('aaa');
}else{
console.log('bbb');
}
}
区块
建议总是使用大括号表示区块
function fn()
{
}
//well
function fn() {
}
圆括号
- 函数的调用
// 表示函数定义时,函数名与左括号之间没有空格。
//bad
function add () {
}
//good
function add() {
}
//表示函数调用时,函数名与左括号之间没有空格。
//bad
add()
//good
add();
// 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。
//bad
foo (bar);
return(a+b);
if(a === 0) {...}
function foo (b) {...}
function(x) {...}
//good
foo(bar);
return (a+b);
if (a === 0) {...}
function foo(b) {...}
function (x) {...}
不使用分号的情况
以下三种情况不需要添加分号
for 和 while 循环
for ( ; ; ) {
} // 没有分号
while (true) {
} // 没有分号
注意,do...while
循环是有分号的。
do {
a--;
} while(a > 0); // 分号不能省略
分支语句:if,switch,try
if (true) {
} // 没有分号
switch () {
} // 没有分号
try {
} catch {
} // 没有分号
函数的声明语句
function f() {
} // 没有分号
注意,函数表达式仍然要使用分号。s
var f = function f() {
};
自动添加分号
虽然大部分语法会自动加上分号,但是也存在一些解析歧义,比如分号不能正确的加入合适的位置,建议手动加上分号。
全局变量
JavaScript 最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写。这对代码的模块化和重复使用,非常不利。
因此,建议避免使用全局变量。
如果不得不使用,可以考虑用大写字母表示变量名
var CPAGE = 5;
相等和严格相等
推荐使用 ===
代替 ==
自增和自减运算符
//bad
++x
//good
x += 1;
改用+= 1
,代码变得更清晰了。
建议自增(++
)和自减(--
)运算符尽量使用+=
和-=
代替。
switch...case 结构
function doAction( action ) {
switch (action) {
case 'hack':
return 'hack';
break;
case 'slash':
return 'slash';
break;
case 'run':
return 'run';
break;
default:
throw new Error('Invalid action.');
}
}
上面的代码建议改写成对象结构。
function doAction(action) {
var actions = {
'hack': function () {
return 'hack';
},
'slash': function () {
return 'slash';
},
'run': function () {
return 'run';
}
};
if (typeof actions[action] !== 'function') {
throw new Error('Invalid action.');
}
return actions['run']();
}
因此,建议switch...case
结构可以用对象结构代替。
Math对象
该对象不是构造函数,不能生成实例。
var a = 2.67;
//报错,Math对象不是构造函数
var str = new Math();
str.round( a );
// 应该这样写
var str = Math.round( a );//3
静态属性
-
Math.PI
:圆周率 -
Math.SQRT2
:2 的平方根
这些属性都是只读的,不能修改。
静态方法
-
Math.abs()
:绝对值
console.log( Math.abs(-1) );//1
console.log( Math.abs(1) );//1
-
Math.ceil()
:上舍入
console.log( Math.ceil(3.00001) ); //4
console.log( Math.ceil(3.99999) ); //4
console.log( Math.ceil(3.33333) ); //4
-
Math.floor()
:下舍入
console.log( Math.floor(3.99999) ); //3
console.log( Math.floor(3.00001) ); //3
console.log( Math.floor(3.33333) ); //3
-
Math.round()
:四舍五入
console.log( Math.round(3.99999) ); //4
console.log( Math.round(3.00001) ); //3
console.log( Math.round(3.33333) ); //3
-
Math.max()
:最大值
console.log( Math.max(2,1,5,6,7) );// 7
console.log( Math.max.apply( null, [2,1,5,6,7] ) ); // 7
-
Math.min()
:最小值
console.log( Math.min(2,1,5,6,7) );// 1
console.log( Math.min.apply( null, [2,1,5,6,7] ) ); // 1
-
Math.pow()
:指数运算
语法:
Math.pow(底数,指数);
console.log( 2 ** 3 ); //8
console.log( Math.pow(2,3 ) );//8
-
Math.sqrt()
:平方根
// 2的平方根
console.log( Math.SQRT2 );//1.4142135623730951
console.log( Math.sqrt(2) );//1.4142135623730951
console.log( Math.sqrt(4) ); //2
-
Math.random()
:随机数
返回0~1之间,可能等于0,但是一定小于1。
console.log( Math.random() );
//随机 0 - 10 之间的整数
console.log( Math.round( Math.random() * 10 ) );
//随机 5 - 15 之间的整数
console.log( 5 + Math.round( Math.random() * 10 ) );
//随机取出数组成员(数组可增可减)
var arr = ['苹果','桔子','菠萝'];
var index = Math.floor( Math.random() * arr.length );//0 - 2
console.log( arr[index] );
//随机打乱一个数组
var arr = [1,2,3,4,5,6,7,8,9];
arr.sort(function(){
return 0.5 - Math.random();
})
console.log( arr );
setTimeout 单次定时执行函数
语法:
- fn:函数
- str:字符串
- time:时间毫秒(1000毫秒=1秒)
setTimeout( fn|str,time);
示例
//写法一
setTimeout(function(){
console.log('hello');
},1000);
//写法二
function echo(){
console.log('hello');
}
setTimeout( echo, 1000);
setTimeout('echo()',2000);
- 有时候可以用
setTimeout
模拟异步操作
//打开文件
console.log('正在打开文件...');
setTimeout(function(){
console.log('文件已打开!');
},3000);
console.log('hello');
// 执行先后顺序如下:
//1.正在打开文件...
//2.hello
//3.文件已打开!
- 延时操作
setTimeout(function(){
console.log('暂停3秒');
},3000);
setTimeout对象
setTimeout(function(){
console.log('ok');
},0);
console.log('hello');
//输出顺序是 hello > ok
经典面试题1
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i);
},0)
}
//输出10次 10
经典面试题2
for(let i=0;i<10;i++){
console.log('a:',i);
setTimeout(function(){
console.log('b:',i);
},0)
}
//输出 a: 0 ~ 9
//输出 b: 0 ~ 9
- 如果需要手动写动画,你认为最小时间间隔是多久,为什么?
答:多数显示器默认频率是60Hz
,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms
= 16.7ms
clearTimeout 清除单次操作定时器
<button id="btn">清除定时</button>
<script>
var time = setTimeout(function(){
console.log('hello');
},5000);
document.getElementById('btn').onclick = function(){
clearTimeout( time );
alert('清除完毕!');
}
</script>
setInterval 多次定时执行函数
语法:
- fn:函数
- str:字符串
- time:时间毫秒(1000毫秒=1秒)
setInterval( fn|str,time);
示例
setInterval(function(){
console.log('hello');
},1000)
clearInterval 清除多次定时执行函数
简单的随机点名效果
<h1 id="user"></h1>
<button id="addbtn">添加</button>
<button id="btn">启动</button>
<script>
var list = [];
if( localStorage.getItem('list') ){
list = localStorage.getItem('list').split(',');
}
function play(){
var num = 3;
var user = document.getElementById('user');
if(list.length == 1){
user.innerHTML = list[0];
return;
}
var int = setInterval(function(){
user.innerText = num;
num -= 1;
if(num < 0) {
clearInterval( int );
var index = Math.floor(Math.random() * list.length);
user.innerText = list[index];
list.splice(index,1);
console.log(list);
}
},1000);
}
document.getElementById('addbtn').onclick = function(){
var str = prompt('请输入姓名!');
list.push( str );
//本地存储
localStorage.setItem('list', list.toString() );
}
document.getElementById('btn').onclick = function(){
//启动随机点名
play();
}
</script>
Date对象
时间起点
它以1970
年1
月1
日00:00:00
作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。
时间最小单位
毫秒,换算 1000
毫秒 = 1
秒
普通函数的用法
console.log( Date() );
//Tue Jul 30 2019 14:17:35 GMT+0800 (中国标准时间)
console.log( Date(2000, 1, 1) );
//Tue Jul 30 2019 14:17:35 GMT+0800 (中国标准时间)
上面代码说明,无论有没有参数,直接调用Date
总是返回当前时间。
构造函数的用法
var d1 = new Date();
//d1 是构造函数Date的实例
var d2 = new Date();
Date
不加参数,实例代表的就是当前时间。
//不写参数默认是当前时间
var d = new Date();
console.log(d);
//Tue Jul 30 2019 14:22:45 GMT+0800 (中国标准时间)
var d = new Date(2000,10,20,15,30,56);
console.log(d);
//Mon Nov 20 2000 15:30:56 GMT+0800 (中国标准时间)
时间戳
至时间零点(1970-1-1 0:0:0
)到当前时间所经历的毫秒数
var d = new Date(0);
console.log(d);
//Thu Jan 01 1970 08:00:00 GMT+0800 (中国标准时间)
//因为我们是东八区,比如GMT快8小时
只要是能被Date.parse()方法解析的字符串,都可以当作参数。
new Date('2013-2-15')
new Date('2013/2/15')
new Date('02/15/2013')
new Date('2013-FEB-15')
new Date('FEB, 15, 2013')
new Date('FEB 15, 2013')
new Date('Feberuary, 15, 2013')
new Date('Feberuary 15, 2013')
new Date('15 Feb 2013')
new Date('15, Feberuary, 2013')
// Fri Feb 15 2013 00:00:00 GMT+0800 (CST)
getYear 获取总年数
var d = new Date();
//1900 ~ 至今经历的总年数
console.log( d.getYear() );//119
getFullYear 获取四位年份
var d = new Date();
console.log( d.getFullYear() ); // 2019
getMonth 获取月份
注意:月份是从0开始计算,0表示一月
范围:0 ~ 11
var d = new Date();
console.log( d.getMonth() + 1 ); // 7
getDate 获取月份
范围:1 ~ 31
var d = new Date();
console.log( d.getDate() ); // 30
getDay 获取星期
注意:0
表示星期日,其余依次类推
范围:0 ~ 6
var d = new Date();
console.log( d.getDay() ); // 2
getHours 获取小时
范围:0 ~ 23
var d = new Date();
console.log( d.getHours() );//14
getMinutes 获取分钟
范围:0 ~ 59
var d = new Date();
console.log( d.getMinutes() ); // 50
getSeconds 获取秒钟
范围:0 ~ 59
var d = new Date();
console.log( d.getSeconds() ); //29
getMilliseconds 获取毫秒
范围:0 ~ 999
var d = new Date();
console.log( d.getMilliseconds() ); //555
getTime 获取指定日期的毫秒
var d1 = new Date();
var times = d1.getTime();//获取1970到当前所经历的毫秒数
//以上代码也可以改下如下
var times = new Date().getTime();
//获取指定日期的毫秒数
var times = new Date('2019-7-31 10:12').getTime();
正则RegExp对象
正则表达式表达文本模式的方法,常常用来按照“给定模式”匹配文本。
什么是正则对象?
正则对象通常使用斜杠包裹起来的一段字符
正则对象定义
//构造函数定义
var reg = new RegExp('hello');
//缩写定义
var reg = /hello/;
什么是正则对象的修饰符?
-
i
:加i
,表示不区分大小写 -
g
: 加g
,表示全局匹配 -
m
:加m
,表示多行匹配
//1. 表示不区分字母a大小写
var r1 = /a/i;
var r1 = new RegExp('a','i');
//2. 表示匹配字母a
var r2 = /a/g;
var r2 = new RegExp('a','g');
//3. 表示全局匹配字母a,字母a不区分大小。
var r3 = /a/gi;
var r3 = new RegExp('a','gi');
正则对象实例方法
test
用于正则模式去匹配给定的文本,返回布尔值。
语法:
返回布尔类型
正则对象.test('搜索的文本');
示例
var str = 'hello';
var reg1 = /e/;
var reg2 = new RegExp('e');
console.log( reg1.test( str ) ); //true
console.log( reg2.test( str ) ); //true
console.log( /E/i.test( 'hello' ) );//true
console.log( new RegExp('E','i').test( 'hello' ) );//true
exec
正则实例对象的exec
方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null
。
当有重复匹配,返回最初匹配的索引值
console.log( /e/.exec('hello') );
// ["e", index: 1, input: "hello", groups: undefined]
console.log( /l/.exec('hello') );
// ["l", index: 2, input: "hello", groups: undefined]
console.log( /w/.exec('hello') );
// null
//组匹配
console.log( /_(x)/.exec('_x_x') );
//["_x", "x", index: 0, input: "_x_x", groups: undefined]
var reg = /a/g;
var result = reg.exec('abc_abc_abc');
console.log( reg.lastIndex );//1
var result = reg.exec('abc_abc_abc');
console.log( reg.lastIndex );//5
var result = reg.exec('abc_abc_abc');
console.log( reg.lastIndex );//9
正则对象规则
字面量字符
没有代表意义的字符,也就是普通字符,我们叫字面量字符
/dog/.test('old dog') // true
/张三/.test('名称') // false
/111/.test('名称') // false
元字符
有代表意义的字符,特殊字符
- 点字符(
.
):表示任意字符,只匹配一个字符
console.log( /./.test('dsfdsf232131') ); //true
console.log( /./.test('一二三四') ); //true
console.log( /./.test('1122') ); //true
console.log( /./.test(' ') ); //true
console.log( /./.test('') ); //false
如果要匹配的是点?
console.log( /\./.test('jpg') );//false
console.log( /\./.test('.jpg') );//true
注意:如果想要元字符表达字面量字符,可以使用转义符
\
console.log( /:\/\//.test('http://') ); //true
- 位置字符(
^$
):
-
^
以什么开头 -
$
以什么结尾
console.log( /^\d/.test('2abc') ); //以数字开头
console.log( /\d$/.test('abc2') ); //以数字结尾
console.log( /^\d$/.test('abc2') ); //以数字开头并且以数字结尾,\d只能匹配单个字符
- 数字符
\d
: 匹配数字,只匹配一个字符
console.log( /\d/.test('d') ); //false
console.log( /\d/.test('3423') ); //true
console.log( /\d/.test('abb1a中') ); //true
console.log( /^\d/.test('1abba中') ); //true
console.log( /^\d$/.test('1abba中1') ); //false
console.log( /^\d$/.test('1a') ); //false
console.log( /^\d$/.test('1') ); //true
console.log( /^\d$/.test('123') ); //false,只匹配一个
- 数字符
\D
: 匹配非数字,只匹配一个字符
console.log( /^\D$/.test('张') ); //true
console.log( /^\D$/.test('1') ); //false
- 字母数字下划线符
\w
:匹配大小字母(a-z
A-Z
)、数字(0-9
)、下划线_
console.log( /^\w$/.test('张') ); //false
console.log( /^\w$/.test('1') ); //true
console.log( /^\w$/.test('a') ); //true
console.log( /^\w$/.test('A') ); //true
console.log( /^\w$/.test('_') ); //true
console.log( /^\w$/.test('-') ); //false
console.log( /^\w$/.test('a1') ); //false
- 非字母数字下划线符
\W
: 不匹配非数字,只匹配一个字符
console.log( /^\W$/.test('张') ); //true
console.log( /^\W$/.test('@') ); //true
console.log( /^\W$/.test('%') ); //true
- 选择符(
|
):类似于或的意思
console.log( /^(\d|a)/.test('f') ); //false
console.log( /^(\d|a)/.test('7') ); //true
console.log( /^(\d|a)/.test('7adfg') ); //true
console.log( /^(\d|a)/.test('a1212') ); //true
- 特殊字符
-
\n
:匹配换行
console.log( /\n/.test(`111 333 222 444`) );//false
console.log( /\n/.test(`111
333`) );//true
-
\s
:匹配空格、换行\n
、回车\r
、制表符\t
console.log( /\s/.test(`111 333 222 444`) );//true
console.log( /\s/.test(`111
333`) );//true
如果我只想匹配纯空格呢?
console.log( / /.test(`111 333 222 444`) );//true
-
\S
: 匹配非空格、换行\n
、回车\r
、制表符\t
-
\uhhhh
:匹配一个以四位十六进制数\u0000-\uFFFF
表示的Unicode
字符
注意:[\u4e00-\u9fa5]
:表示中文字符串集范围,表示单个汉字
console.log( /[\u4e00-\u9fa5]/.test(`2`) );//false
console.log( /[\u4e00-\u9fa5]/.test(`a`) );//false
console.log( /[\u4e00-\u9fa5]/.test(`$`) );//false
console.log( /[\u4e00-\u9fa5]/.test(`张`) );//true
console.log( /[\u4e00-\u9fa5]/.test(`张1`) );//true
console.log( /^[\u4e00-\u9fa5]$/.test(`张1`) );//false
console.log( /^[\u4e00-\u9fa5]$/.test(`张`) );//true
- 白名单符
[]
:可选范围
//允许输入数字或者下划线
console.log( /^[0-9_]$/.test(`2`) );//true
console.log( /^[0-9_]$/.test(`a`) );//false
console.log( /^[0-9_]$/.test(`_`) );//true
- 黑名单符
[^]
:除可选范围以为
//不允许输入数字或者下划线
console.log( /^[^0-9_]$/.test(`2`) );//false
console.log( /^[^0-9_]$/.test(`a`) );//true
console.log( /^[^0-9_]$/.test(`_`) );//false
- 连字符
-
:某些情况下,对于连续序列的字符,连字符(-)用来提供简写形式
console.log( /^[1-5]$/.test(`2`) );//
console.log( /^[c-f]$/.test(`C`) );//false
console.log( /^[c-f]$/.test(`d`) );//true
console.log( /^[c-f]$/.test(`j`) );//false
console.log( /^[三-五]$/.test(`四`) );//false
console.log( /^[三-五]$/.test(`三`) );//true
console.log( /^[三-五]$/.test(`五`) );//true
- 分组
()
:将一组字符串分组匹配
console.log(/(abc){2}/.test('abcabc123')); //true
console.log(/(abc){2}/.test('abc123')); //false
- 匹配边界
\b
:
var str = 'a|b|c';
var str = '1|2|3|4';
console.log( /^(.+\b\|)+[^\|]$/.test( str ) );
- 匹配非边界
\B
:
量词
-
*
:代表 0个、1个、n个情况,可有可无或者很多个。
console.log( /^\d*$/.test('') ); //true 表示0个
console.log( /^\d*$/.test('9') ); //true 表示1个
console.log( /^\d*$/.test('999') ); //true 表示n个
console.log( /^\d*$/.test(' ') );//false 空格不是数字
-
+
:代表 1个、n个情况,至少1个。
console.log( /^\d+$/.test('') ); //false 至少1个
console.log( /^\d*$/.test('9') ); //true 表示1个
console.log( /^\d*$/.test('999') ); //true 表示n个
console.log( /^\d*$/.test(' ') );//false 空格不是数字
-
?
:代表 0个、1个情况,要么没有要么有一个。
console.log( /^@?$/.test('') ); //true
console.log( /^@?$/.test('@') ); //true
console.log( /^@?$/.test('@@') ); //false
-
{n}
:代表允许字符的固定位数
//必须输入6位数字,这很符合邮政编码规则
console.log( /^\d{6}$/.test('12345') ); //false
console.log( /^\d{6}$/.test('123456') ); //true
-
{n,}
:代表至少允许n个字符
console.log( /^\d{6,}$/.test('12345') );//false
console.log( /^\d{6,}$/.test('123456') );//true
console.log( /^\d{6,}$/.test('123456789') );//true
-
{n,m}
:代表至少允许n个字符,最多m个字符
console.log( /^\d{6,10}$/.test('12345') );//false
console.log( /^\d{6,10}$/.test('123456') );//true
console.log( /^\d{6,10}$/.test('123456789') );//true
console.log( /^\d{6,10}$/.test('12345678921311') );//false
贪婪和非贪婪模式
- 贪婪模式,默认贪婪模式
var s = 'aaa';
//贪婪模式
console.log( s.match(/a+/) );//aaa
//非贪婪模式
console.log( s.match(/a+?/) );
//查找 <字母>任意字符</字母>
var str = '<div><h2>标题2</h2></div>';
//贪婪模式
console.log( str.match(/^<(.+)>(.*)<\/(.+)>$/) );
// <div><h2>标题2</h2></div>
//非贪婪模式
console.log( str.match(/<(\w+)[^>]+>.*?<\/(\w+)>/) );
// <div><h2>标题2</h2>
- 非贪婪模式
如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。
var s = 'aaa';
s.match(/a+?/) // ["a"]
m 修饰符
/world$/.test('hello world\n') // false
/world$/m.test('hello world\n') // true
上面的代码中,字符串结尾处有一个换行符。如果不加m
修饰符,匹配不成功,因为字符串的结尾不是world
;加上以后,$
可以匹配行尾。
/^b/.test('a\nb') // false
/^b/m.test('a\nb') // true
字符串对象实例使用正则的方法
match
返回一个数组,成员是所有匹配的子字符串
replace
按照给定的正则表达式进行替换,返回替换后的字符串。
语法:
字符串.replace(/正则对象/修饰符,用来替换的字符);
示例
`1aa23A45`.replace(/a/,'*');//1*a23A45
`1aa23A45`.replace(/a/g,'*');//1**23A45
`1aa23A45`.replace(/a/gi,'*');//1**23*45
replace
方法的第二个参数可以使用美元符号$
,用来指代所替换的内容。
var str1 = 'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1');
console.log( str1 );// world hello
var str2 = '3 and 5'.replace(/(\d+)\sand\s(\d+)/,'$2 and $1');
console.log( str2 );// 5 and 3
-
$&
: 匹配的子字符串 -
$
: 匹配结果前面的文本。 -
$'
: 匹配结果后面的文本。 -
$n
:匹配成功的第n组内容,n是从1开始的自然数。 -
$$
:指代美元符号$
'abc'.replace('b', '[$`-$&-$\']');//a[a-b-c]c
'abc'.replace('b', '[$`-$&-$\']');
注意:用来替换的字符也可以是一个函数
var str = '3 and 5'.replace(/[0-9]+/g, function (match) {
return 2 * match;
})
console.log( str ); // 6 and 10
经典面试题
替换为驼峰法命名
var str1 = 'first-name'; //firstName
var str2 = 'border-top-left-radius';//borderTopLeftRadius
var str3 = 'FiRSt-Name';//firstName
var result = str3.toLowerCase().replace(/-[a-z]/g, function( match ){
return match.slice(1).toUpperCase();
});
console.log( result );
正则如何使用变量替换
//定义敏感字
var badwords = ['shit','fuck','tmd'];
var str = '今天shit的天tmd气fuck不错';
//批量替换敏感字
for(var i=0;i<badwords.length;i++){
var reg = new RegExp( badwords[i] , 'gi' );
str = str.replace( reg, '*' );
}
document.write( str );
正则替换html标签
清理html
标签
//html文本
var str = `<div style="color:red;">
<span class="red">红色</span>
<h1>标题1</h1>
<span>文本</ span>
<p>段落</p>
</div>`;
//1. 清理html标签
str = str.replace(/<.*?>/g,'');
console.log( str );
2. 清理span标签
str = str.replace(/<\/?(\s*)span(.*?)>/gi,'');
console.log(str);
//3. 清理自定义标签
var lists = ['span','p','h1'];
for( var i=0;i<lists.length;i++){
var reg = new RegExp('<\/?(\\s*)'+lists[i]+'(.*?)>','gi');
str = str.replace( reg, '' );
}
console.log( str );
//4. 给字符串对象扩展过滤标签的方法
String.prototype.reTags = function(){
var str = this;
var args = arguments;
for(var key in args){
var reg = new RegExp(`<\/?(\\s*)${args[key]}(.*?)>`,'gi');
str = str.replace( reg,'');
}
return str;
}
var result = str.reTags('span','p','h1');
console.log( result );
先行断言
x(?=y)
称为先行断言,x
只有在y
前面才匹配,y
不会被计入返回结果。
示例
//60只有在%前面才匹配,%不会被计入返回结果。
console.log( '60%'.match(/\d+(?=%)/) );//60
console.log( '%60'.match(/\d+(?=%)/) );//null
console.log( '60'.match(/\d+(?=%)/) );//null
先行否定断言
//先行断言
//60只有在%前面才匹配,%不会被计入返回结果。
console.log( '60%'.match(/\d+(?=%)/) );//60
console.log( '%60'.match(/\d+(?=%)/) );//null
console.log( '60'.match(/\d+(?=%)/) );//null
console.log( '80%12%34903%33'.match(/\d+(?=%)/g) );
// ['80','12','34903']
console.log('a.jpg,b.gif,c.png'.match(/\w+(?=,)|\w+(?=$)/g));
//['a.jpg','b.gif','c.png']
var str = 'a.jpg,b.gif,c.png';
console.log( str.match(/(\w+)(?=(,|$))/g) );
//['a.jpg','b.gif','c.png']
先行否定断言
x(?!y)
:x
只有不在y
前面才匹配,y
不会被计入返回结果。
//先行否定断言
//60只有不在%前面才匹配,%不会被计入返回结果。
console.log( '60%'.match(/\d+(?!%)/) );//6
console.log( '%60'.match(/\d+(?!%)/) );//60
JSON对象
JSON对象本质其实就是一段字符串,只是这个字符串它有一定规则。
目的是取代繁琐笨重的 XML
格式
json文本键名是用双引号,键值如果是字符串,需要双引号,数字可以不需要
数组或对象最后一个成员的后面,不能加逗号。
什么xml
文本?
<?xml version="1.0" encoding="utf-8"?>
<students>
<student>
<sn>201709000123</sn>
<name>张嘉佳</name>
<sex>女</sex>
<nation>汉族</nation>
<address>湖北武汉</address>
<profession>英语</profession>
</student>
<student>
<sn>201709000124</sn>
<name>李文斌</name>
<sex>男</sex>
<nation>傣族</nation>
<address>云南昆明</address>
<profession>建筑</profession>
</student>
</students>
什么json
文本?
[
{
"sn": "201709000123",
"name": "张嘉佳",
"sex": "女",
"nation":"汉族",
"address":"湖北武汉",
"profession":"语言"
},
{
"sn": "201709000123",
"name": "张嘉佳",
"sex": "女",
"nation":"汉族",
"address":"湖北武汉",
"profession":"语言"
}
]
json
与xml
比较:
- 可读性:
xml
好 - 生成数据方面:
json
好 - 传输速度方面:
json
好 -
xml
诞生很早,可以支持一些很老的设备
json文本中复合类型中不能有函数
以下写法错误
{
"getname": function(){
}
}
以下写法正确
{
"name": "张三",
"info": [
{
"id": 1,
"title": "标题1",
"check": "true"
}
]
}
以下都是合法的 JSON。
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ]
以下都是不合法的 JSON。
{ name: "张三", 'age': 32 } // 属性名必须使用双引号
[32, 64, 128, 0xFFF] // 不能使用十六进制值
{ "name": "张三", "age": undefined } // 不能使用 undefined
{ "name": "张三",
"birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
"getName": function () {
return this.name;
}
} // 属性值不能使用函数和日期对象
注意,null
、空数组和空对象都是合法的 JSON
值。
JSON对象全局方法
-
JSON.stringify()
:
返回经过转换过后的字符串
语法:
JSON.stringify(需要转换的对象或数组,用于过滤指定属性,可读性);
示例
JSON.stringify('abc') // ""abc""
JSON.stringify(1) // "1"
JSON.stringify(false) // "false"
JSON.stringify([]) // "[]"
JSON.stringify({}) // "{}"
JSON.stringify([1, "false", false])
// '[1,"false",false]'
JSON.stringify({ name: "张三" })
// '{"name":"张三"}'
第二参数示例: 过滤指定的属性
var obj = {
'name': '张三',
'age': '20',
'sex': '男'
};
var selectedProperties = ['name', 'sex'];
var str = JSON.stringify(obj, selectedProperties);
console.log( str );//{"name":"张三","sex":"男"}
第三参数示例:
var obj = {
'name': '张三',
'age': '20',
'sex': '男',
info: {
'a': 20
}
};
var str = JSON.stringify(obj, null, 2);
//可读性就比较好
// {
// "name": "张三",
// "age": "20",
// "sex": "男",
// "info": {
// "a": 20
// }
// }
JSON.parse()
JSON.parse
方法用于将 JSON
字符串转换成对应的值。
JSON.parse('{}') // {}
JSON.parse('true') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null
var o = JSON.parse('{"name": "张三"}');
o.name // 张三
DOM模型
DOM:文档对象模型( Document Object Model ),它的作用是将网页转为一个 js
对象
节点 Node
DOM
的最小组成单位叫做节点
节点的类型有七种:
-
Document
:整个文档节点(包含整个节点树) -
DocumentType
:文档声明类型<!DOCTYPE html>
-
Element
:元素节点,网页的各种HTML标签(<body>
、div
、img
等等) -
Attribute
:网页元素的属性(比如class="right"
) -
Text
:标签之间或标签包含的文本 -
Comment
:注释 -
DocumentFragment
:文档的片段
根节点
document
根节点,文档的第一层只有一个节点
节点的关系
除了根节点,其他节点都有三种层级关系。
- 父节点关系
<!-- button的直接父节点是div -->
<div>
<button>按钮</button>
</div>
- 子节点关系
<!-- div的直接子节点是button -->
<div>
<button>按钮</button>
</div>
- 同级节点关系
<!-- button的同级节点是h2 -->
<div>
<button>按钮</button>
<h2>标题2</h2>
</div>
Node 接口的属性
所有 DOM
节点都继承了 Node
接口
-
Node.nodeType
:
返回一个整数值,表示节点的类型,有以下类型:
- document:9 表示 文档
- element: 1 表示 元素
- attr: 2 表示 属性
- text: 3 表示 文本
- DocumentFragment: 11 表示 文档片段
- DocumentType: 10 表示 文档类型
- Comment: 8 表示 注释
<div id="box">你好</div>
<script>
var box = document.getElementById('box');
console.log( box.nodeType ); //1 元素
console.log( box.childNodes[0].nodeType );//3 文本
</script>
-
Node.nodeName
:
<div id="box">你好</div>
<script>
var box = document.getElementById('box');
console.log( box.nodeName ); //DIV 默认是大写
console.log( box.childNodes[0].nodeName );//#text
</script>
-
Node.nodeValue
:
nodeValue
属性返回一个字符串,表示当前节点本身的文本值
注意:该属性主要用于文本节点
var box = document.getElementById('box');
console.log( box.nodeValue );//null
console.log( box.childNodes[0].nodeValue );//您好
-
Node.textContent
:
textContent
属性返回当前节点和它的所有后代节点的文本内容。
自动忽略当前节点内部的HTML标签,提取文本内容,它和innerText功能一样
该属性可读可写
<div id="box">
您好
<p>
张三
</p>
</div>
<script>
var box = document.getElementById('box');
console.log( box.textContent );// 您好 张三
</script>
-
Node.baseURI
:
返回一个字符串,表示当前网页的绝对路径
会受<base href="#">
基础地址影响,而location.href
不会。
console.log( document.baseURI );
//file:///F:/studyspace/web1905/javascript/dom.html
console.log( window.location.href );
//file:///F:/studyspace/web1905/javascript/dom.html
-
Node.ownerDocument
:
返回当前节点所在的顶层文档对象,即document
对象。
<div id="box">
</div>
<script>
var box = document.getElementById('box');
console.log( box.ownerDocument == document );//true
</script>
节点关系查找
父节点关系
-
Node.childNodes
:父节点下所有的子节点,注意包含文本、注释、元素节点
-
Node.children
:父节点下所有的子元素节点,只是元素节点
<ul id="box">
<li>一分</li>
<li>二分</li>
<li>三分</li>
<li>四分</li>
<li>五分</li>
</ul>
<script>
var box = document.getElementById('box');
var lis1 = box.childNodes;
var lis2 = box.children;
console.log( lis1, lis2);
</script>
子节点关系
-
parentNode
:当前节点的直接的父节点
-
parentElement
:当前节点的直接的父元素节点
<div>
<span>
<button id="btn">按钮</button>
</span>
</div>
<script>
document.getElementById('btn').onclick = function(){
var THIS = this;
while( THIS.parentElement ){
THIS = THIS.parentElement;
if(THIS.nodeName == 'BODY'){
THIS.style.backgroundColor='red';
}
}
}
</script>
同级节点关系
-
Node.nextSibling
:下一个节点,注意包含文本、注释、元素节点
-
Node.nextElementSibling
:下一个元素节点,只是元素节点。
<button onclick="setBox(this)">按钮</button>
<div></div>
<script>
//单击按钮,给div设置宽200 高200 背景颜色红色
function setBox( btn ){
btn.nextElementSibling.style.cssText = `width:200px;height:200px;background-color:red`;
}
</script>
-
Node.previousSibling
:上一个节点,注意包含文本、注释、元素节点
-
Node.previousElementSibling
:上一个元素节点,只是元素节点。
综合的例子
<ul> <li>一分</li> <li>二分</li> <li>三分</li> <li>四分</li> <li>五分</li> </ul>
var lis = document.querySelectorAll('li');
lis.forEach( function( item ){
item.onclick = function(){
var THIS1 = this;
var THIS2 = this;
//查找当前元素往上的所有元素
while( THIS1.previousElementSibling ){
THIS1 = THIS1.previousElementSibling;
THIS1.className = 'active'
}
//查找当前元素往下的所有元素
while( THIS2.nextElementSibling ){
THIS2 = THIS2.nextElementSibling;
THIS2.className = 'active'
}