组件化网页开发——让页面动起来的JavaScript深入讲解

320 阅读7分钟

JS变量、作用域

查漏补缺

JS中的变量名由$ _ 数字和字母组成,且不能以数组开头

jQuery和undercode都是JS函数库,调用jQuery中的函数可以用.funcName(),其中.funcName(),其中==jQuery,调用undercode中的函数可以用_.funcName(),其中_==jQuery

数据类型和堆栈

基本类型:4 'str' true/false underfined null

引用类型:[ ](数组) { } (对象)

区别:基本类型不可修改,引用类型可以修改

举例:

 var str='string';
var anotherStr=str.replace('s','');
console.log(str+'|'+anotherStr);
 
//打印 string|tring
//说明repalce函数不会改变str的值,只会生成一个它的副本并对这个副本进行修改

引用对象可以看做是一个分子,由许多原子组成 ,可以对引用对象进行增删改查。

基本对象本身没有属性和方法,但它们可以调用方法,原因是调用方法时,基本对象会去找它的包装对象,如数字的包装对象是Number,字符串的包装对象是String,这些包装对象中的属性和方法借给基本类型用,用完它们自己就会消失。

栈内存是固定大小的空间,堆内存空间大小不固定。由于基本对象大小不能修改,所以它们保存在栈内存,而引用对象由于可修改,大小不固定,所以就保存在堆内存。由于基本对象保存在栈内存中,是有序的,访问时容易找到,而引用对象保存在堆内存,是无序的,很难直接定位,所以一定要知道它们的地址才能访问到,由于地址大小是固定的,所以地址可以保存在栈内存中,到栈内存读出地址再去访问堆内存中的引用对象。

image.png

如图所示,堆内存中存储的变量对应的地址放在栈内存中,如果在栈内存中读取到一个基本类型的数据,就直接取出,如果读取到一个地址,就通过这个地址到堆内存中找到相对应的变量,这也是为什么叫做引用变量,因为这个地址就是引用地址。

对于引用类型的比较,只有当它们指向同一个引用时才会相等,即当它们共享一个地址时,才会相等,否则就算引用对象中的值都一模一样,也是不全等的。如果只要比较引用类型的值是否相等, 则需要通过遍历来把每个属性取出来比较,只要有一个属性不相等,这两个引用类型的值就不相等。

基本类型的值复制,无论对这些变量进行何种操作都不会影响其他变量。而引用类型的复制,则是把引用地址复制给了另一个引用对象,它们拥有同一个地址,指向同一个堆内存中的对象,因此对指向同一个地址的任意一个引用元素进行修改都会对其他引用元素产生影响,它们也发生了相同的变化。

因此,如果只想得到一个引用类型的副本,而且对副本的操作不影响到原对象,最好用遍历的方法,将原对象中的所有属性赋给副本的所有对应的属性。

/* 浅拷贝,只有基本类型的赋值 */
function copyObj(obj){
    var newOjb={};
    for(var p in obj){
        newObj[p]=obj[p];
    }
    return newObj;
}

参数传递和类型检测

参数传递

基本类型的参数传递传的是值,在函数中对形参作和操作对于实参的值都不会有任何影响。

引用类型的参数传递传的是地址,指向堆内存中的同一块内存,因此在函数中对形参的操作等同于对实参的操作。

但无论基本类型还是引用类型,实参到形参的传递都是按值传递,只不过前者传的是数值,后者传的是地址值。

function setName(obj){
    obj.name='xm';
    obj={};
    obj.name='xh';
}
var person={};
setName(person);
console.log(person.name);    //打印结果仍然是xm

上述例子中,person.name仍然是xm,因为调用函数传参时,传的是地址值,此时相当于persno=obj,形参obj与person拥有相同的地址,指向堆内存中同一块内存,obj.name='xm'相当于person.name='xm'。 当obj={}时,表明新申请了一块内存,并把这块内存的地址赋给了形参obj,这时候obj与person不再指向同一块内存单元,再对obj进行赋值,就不会对person产生影响了。

类型检测

typeof可以作为运算符也可以作为函数来检测一个数据的类型,但对于数组和对象这样的数据,返回类型都是Object。

为了知道Object具体是什么类型,可以用instanceof来判断。instanceof的用法是:a instanceof b,语义是:a是否为b的实例,返回值为boolean型。

[] instanceof Array;    //true
[] instanceof Object;    //true
{} instanceof Object;    //true
{} instanceof Array;    //true

注意:instanceof只能用于判断引用类型的具体类型,不能用于判断基本类型,否则都会返回false。 

全局作用域和局部作用域

变量的作用域即变量起作用的区域和范围,其中包含两个概念:①变量的生命周期②在哪里能访问到变量。

作用域又分全局作用域和局部作用域,局部作用域就是函数作用域,变量出了函数生命周期就结束

注意:由于JS中没有块级作用域,所以局部作用域指的就是函数作用域

变量对象和作用域链

所有全局的变量和函数都是window对象的属性和方法。因此全局变量var i=window i,全局函数function fn=window.fn。

注意:引用没有定义的变量时,脚本不会报错,只会返回undefined

当我们在某个作用域内查找一个变量时,会沿着作用域链来查找,即如果在本层作用域查找不到该变量,会到本层作用域的上一级作用域中去查找,以此类推。 用作用域链来查找变量的代价是很大的,所以局部变量一定优于全局变量,内层变量查找速度一定快于外层变量。因此在jQuery中,window这个全局变量常作为参数传递到函数中,将全局变量转化成局部变量,因为局部变量的访问速度快,否则要经过多层作用域链才能访问到全局变量window。

延长作用域链

var person={};
person.name='xm';
person.sex='male';
var score=4;
 
 
/* 不推荐使用with */
with(person){
    name='xh';    //这里对name修改相当于对person.name修改
    sex='female';
    score=44;    //person.score没有找到,沿着作用域链到外层去找score变量
}
console.log(person.name);
console.log(person.sex);
console.log(score);

JS的解析机制-预解析 

JS的解析过程:预解析→逐行解读代码

var name='xm';
var age=18;
function fn(argument){
    console.log(name);
    var name='xh';
    var age=10;
}
fn();
 
//预解析,先解析全局作用域
//解析全局作用域时,先用undefined给每个变量赋值,然后查找全局范围内的函数,把函数整个照搬过来,即解析时函数就已经声明
 
window
    name=undefined;
    age=undefined;
    function fn(argument){
    console.log(name);
    var name='xh';
    var age=10;
}
//再来解析局部,先用undefined给每个变量赋值,然后查找局部范围内的函数
fn
    name=undefined;
    age=undefined;
    argument=undefined;
//预解析完成,再来逐行解读代码,把赋值语句的右值赋给相应的变量,fn由于在预解析已经声明过了,此时就跳过,直接执行fn()
window
    name='xm';
    age=18;
//执行fn()时,先打印name,由于此时在fn作用域中,name的值还是undefined,因此打印出来的结果也是undefined
fn
    name=undefined;
    age=undefined;
    argument=undefined;

预解析的冲突问题

当处于同一作用域的两个变量同名(冲突),它们都是undefined。

当处于同一作用域的变量和函数同名(冲突),只解析函数。

当处于同一作用域的两个函数同名(冲突),解析后面的函数。

注意:IE游览器无法解析在if或for代码块中定义的函数,所以最好不要这样做。

JS解析机制详解

console.log(a);
var a=1;

JS解析时,都是从var关键字后面去找要解析的变量。因此,如果不写var就直接定义一个变量,JS是不会进行预解析的,也就不会提前知道有这么一个变量,如果这时候我们要访问这个变量,运行脚本就会报错,而不是访问到undefined。

console.log(a);
var a=1;
function a(){
    console.log(2);
}
console.log(a);
var a=3;
console.log(a);
function a(){
    console.log(4);
}
console.log(a);
a();
 
//预解析时,先解析var关键字后面的变量,则有
//a=undefined
//解析完变量,再解析函数
//由于function a与变量a同名,根据冲突原则,只解析函数,又由于有两个同名的functiona,只解析最后那一个,则有
//function a(){...}
//预解析完毕,逐行解读代码,此时a就是一个函数
//console.log(a); 打印一个函数
//变量a的值由一个函数变为1
//函数a已经在预解析时解析完毕,跳过
//console.log(a); 打印1
//变量a的值由1变为3
//console.log(a); 打印3
函数a已经在预解析时解析完毕,跳过
//console.log(a); 打印3
//调用a();,由于此时a是一个变量,不能调用,会报错,提示a不是一个函数

JS解析时,会按script标签的顺序进行解析,即script标签排在前面的脚本先解析,排在后面的后解析。

<script>
    console.log(a);
</script>
<script>
    var a=1;
</script>
/*以上代码会报错,因为a未定义*/
 
<script>
    var a=1;
</script>
<script>
    console.log(a);
</script>
/*以上代码打印结果为1*/
var a=1;
function fn(){
    console.log(a);
    var a=2;
}
fn();
console.log(a);
 
//控制台上打印结果为undefined和1
//预解析时,全局变量a=undefined,function fn为它本身,function fn的局部变量a=undefined
//预解析完毕,逐行执行代码,全局变量a=1,调用函数fn,打印函数fn中的变量a,由于函数体内就已经定义变量a,故访问的是函数体内的局部变量,不会到外层去寻找,又因为先前预解析时局部变量a为undefined,所以打印出来的内容为undefined,然后再为局部变量a赋值为2
//打印变量a,由于最后一条语句在全局范围内,访问的是全局变量a,全局变量a在先前已被赋值为1,所以打印出来的内容为1
var a=1;
function fn(){
    console.log(a);
    a=2;
}
fn();
console.log(a);
 
//预解析时,先在全局作用域中找到var关键字后面的变量,此时a=undefined
//预解析时,全局作用域中的function fn为它本身
//function fn中的a由于没有var关键字,因此不会被预解析,它与全局变量a是同一个东西
//预解析完毕,逐行执行代码
//全局变量a被赋值为1
//调用fn()函数,打印a,a的值为1
//a的值变为2
//打印a,a的值为2
var a=1;
function fn(a){
    console.log(a);
    a=2;
}
fn();
console.log(a);
//预解析时,全局变量a=undefined,function是它本身
//function fn中的形参a也相当于是var a,
//逐行执行代码,全局变量a的值被赋为1
//调用fn(),由于没有给实参,传入函数的形参a=undefined
//打印a,a的值为undefined
//函数体内的a前面没有var,不会被解析,会被看作和形参a是同一个局部变量,因此形参a的值变为2
//打印全局变量a,a的值为1
var a=1;
function fn(a){
    console.log(a);
    a=2;
}
fn(a);
console.log(a);
 
//预解析时,全局变量a=undefined,function fn是它本身
//解析function fn,形参a相当于var a,是一个局部变量,a=undefined
//逐行执行代码
//全局变量a的值被赋为1
//调用fn(a);实参是全局变量a,值为1,因此fn中的形参a也被赋值为1
//打印局部变量a,a的值为1
//没有var修饰的a=2代表的是函数体内的局部变量a,局部变量a的值被赋为2
//打印全局变量a,a的值为1

垃圾收集机制

概念:释放无用的数据,回收内存

原理:找出没用的数据,打上标记,释放其内存;周期性执行

标识无用数据的策略

标记清除

先给所有变量加上标记,再去除环境中的变量以及被环境中的变量引用的变量的标记

何为环境中的变量?

在这个时候,变量还没有离开它的执行环境,对于局部变量来说,当它的函数执行完毕,这个变量就离开了它所在的环境。

引用计数

当我们声明一个变量并将一个引用类型的值赋值给这个变量,这个数据就被变量引用了一次,这个值的引用次数就为1,以此类推,如果这个值又被赋值给了另外一个变量,此时就有两个变量引用这个值,引用次数为2。相反的,如果引用这个值的变量去引用其他的值了,这个值的引用次数就-1,当一个值的引用次数变为0时,就说明已 经没有变量可以访问到这个值,那么这个值就可以被回收了。

注意:引用计数算法可能因为循环的问题而得不到释放,所以已经被摒弃了

var xm={
    name:'xm';
    age:18;
};    //1
var xh=xm;    //2
xh={};    //1
xm={};    //0

内存管理

为了防止web浏览器占用太多内存,系统分配给web浏览器的内存往往小于分给桌面应用程序的内存

优化内存最好的方式就是及时释放不用的数据,只保留有用的数据

当一个变量不使用时,可以手动用null来解除它的引用。如a=null;就把变量a的引用解除了

JS函数深入讲解

什么是对象

对象就是值的集合,在JS中,一个对象中可以有许多不同类型的值,每个值都有属于它们自己的名字,这个名字用字符串来表示,名字和值中间用 : 隔开,形成一个名值对或者叫键值对,一个名值对或键值对就是对象的一个属性,对象就是由一个个属性构成的。

<script>
    var cat={
        'name':'Tom',              //对象的属性可以是一个字符串
        'age':4,                   //对象的属性可以是一个数字
        'family':['dad','mom'],    //对象的属性可以是一个数组
        'speak':function(){        //对象的属性可以是一个函数
            console.log('meow~');
        },
        'friend':{                //对象的属性可以是一个对象
            'name':'Jerry',
            'age':4
        }
    };
</script>

如何使用对象 

创建对象

//方法一:字面量形式(最常用)
var cat={
    'name'='Tom';
    'age'=4;
}
//方法二:new一个Object对象
var cat=new Object();    //相当于用字面量方式创建一个空对象var cat={};
//方法三:
Object.create();    //老版本浏览器存在兼容性问题

对象属性写入与读出

//对已有属性进行写入
cat.name='Tim';
cat['name']='Tim';
//对不存在的属性进行写入
cat.type='加菲猫';
//读取已有属性
console.log(cat.name);
console.log(cat.['name']);
//删除属性
delete cat.type;
console.log(cat.type);    //undefined
//检测对象是否拥有某个属性
console.log('name' in cat);    //返回true
console.log('r' in cat);    //返回false
//遍历对象所有属性
for(var p in cat){    //每次循环都会把属性名赋给p
    console.log(p);    //打印属性名
    console.log(cat.p);    //这样是错的
    console.log(cat['p']);    //√,打印p属性的值
    console.log(cat[p]);    //√,打印p属性的值
}

说明:. 语法后面只能跟表示字符串的, [ ]功能则强大得多,可以是字符串,可以是非字符串,还可以是运算表达式。

什么是函数

一次封装(定义),四处使用(调用)

命名函数

function XXX(arguments){ statements; return ; }

匿名函数

没有名字的函数时匿名函数

函数定义和调用时发生了什么?

当我们在JS中定义一个函数时,该函数就成为一个全局作用域下的函数,用函数名来标识它。函数体看作是一个局部作用域,浏览器解析时,作用域内所有变量初始值为undefined,所有函数照搬,函数内的变量再进行解析,初始值为undefined。解析完毕,逐行执行代码,给变量赋值或进行一些运算,直到遇到return语句。遇到return语句代表一个函数的结束,函数体内所有局部变量被销毁,但这个函数却不会被销毁,仍在全局作用域内,因此我们可以多次调用函数,但每次调用,都会重新开辟一个局部作用域。

为什么要使用函数

函数可以帮助我们复用代码,提高效率。我们不仅可以复用自己的代码,还可以复用别人的代码(如引用jQeury中的函数)

函数可以统一修改和维护

函数可以增加程序的可读性,避免暴露太多细节

函数的本质

二象性:函数是函数,它可以调用。函数同时也是对象

函数是对象,它具有属性。

函数是对象,可以用字面量的方式去定义:function fn(){...},也可以用构造函数的方式去定义。

函数是对象,可以作为数据值使用(所以对象的属性可以是函数)

var add = function (){...} 
add();    //返回的是函数返回值
add;    //返回的是函数本体,也就是说add就是函数本身,add()就是函数()
//这里是匿名函数,add保存了函数的地址,可以用add来调用这个函数,就不需要再给函数起名字了。
//调用方法:add();

函数是对象, 可以作为参数使用

setTimeout(function (){
    console.log(1);
},1000);
 
setTimeout(fn,1000);
function fn(){
    console.log(1);
}

函数是对象,可以作为返回值使用

function fn(){
    return function(){
        console.log(1);
    };
}
var newFn=fn();
newFn();

函数的三种定义方式及其区别

欲用函数,必先定义,这里的先不是指定义必须写在调用之前,而是JS解析器在调用函数之前就要先知道这个函数的存在。

字面量方式

//function声明
function add(){
    statements
}
add();
//var赋值表达式
var add=function add(){
    statements
};    //注意这里有分号,因为是赋值表达式
add();

构造函数

var add=new Function('num1','num2','return num1+num2;')    //参数一定要是字符串形式
add();

区别 

字面量定义方式直观、简洁,执行速度也更快。function声明法在预解析时,无论声明位于定义的之前或之后,都不会报错。而var赋值表达式法则不允许先调用再声明,因为预解析时,要先解析变量和函数再逐行执行代码,赋值表达式中function关键字不打头,解析器解析不到这个函数,在执行代码时调用它就会报错。而用构造函数的方式去定义,也只能允许调用放在声明之后。因此实际开发中更多的使用字面量定义方式。

函数定义的位置

全局作用域

在哪里都可调用

定义在函数内部

在函数外面无法访问,只能在定义它的函数内部调用或者与自己同一级别的函数(包括自己)内部中去调用。

总结:函数定义之内的都可以找到该函数,函数定义之外的都无法找到该函数,越在外层定义的函数越能被内层函数访问到。函数的访问遵循作用域链的原则。

function add(){
    fn();
    function fn(){
        fn();
        function fn3(argument){
            fn();    //???
        }
        //body
    }
    function fn2(){
        fn();
        //body
    }
}

注意:JS中没有块作用域,所以不能在if、for语句块中直接用function声明法定义函数,但是可以用var赋值声明法。不过还是不推荐在if、for语句块中定义函数。

定义在对象内部

var person={
    name:'xm',
    //对象内字面量定义
    setSex:function(sex){
        this.sex=sex;
    }
};
//作为给对象新增的属性,调用对象中的函数要用对象名.方法名();
person.setName=function(name){
    this.name=name;
}
person.setSex('female');
setSex();    //错误,没有指明是哪个对象的方法

普通函数的调用 

命名函数的调用

function add(num1,num2){
    return num1+num2;
}
add(1,1);

匿名函数的调用

//①
var add = function(){
}; 
add(); 
//②
var add=function(){
}();
//③【最常用】
(function(){
})();
(function(){
}());

说明:以上的匿名函数调用方法要点就是避免function打头,不让解析器提前解析,就不会把函数调用当做函数声明。所以function前面加上其他合法字符,如:!、+、-、~,也能正常执行

对象函数(方法)的调用

document.onclick=function(){
    console.log('你已经点击');
}
//document也是一个对象,onclick是这个对象的一个方法,它由浏览器自动调用,当然我们也可以手动调用
document.onclick();
var operation={
    add:function(num1,num2){
        return num1+num2;
    },
    sub:function(num1,num2){
        return num1-num2;
    },
    '@':function(){
        console.log('@');
    }
};
console.log(operation.add(1,2));
console.log(operation['@'](1,2));
var key='add';    //把这个属性的名字赋给一个变量
console.log(operation[key](1,2));    //相当于operation.['add'];
 
 
//链式调用
$('p').html('段落').css('background-color','red');

说明:合法属性名(不以数字开头,只包含字母、数字、_、$)可以不用加引号,非法属性名需要加引号。并且在访问这些属性时,非法属性名不能用 . 运算符来访问,但可以用 [ ] 来访问,同时记得加上引号。

构造函数的调用

构造函数返回的永远是一个对象

var obj=new Person();
 
new Person()    //这个一个对象
Person()    //这是一个函数

函数的间接调用

每个函数都有一个call()方法和apply()方法,可以帮助我们间接调用函数

var name='xm';
var person={};
person.name='xh';
person.getName=function(){
    return this.name;
};
console.log(person.getName());    //xh
console.log(person.getName.call(window));    //xm
console.log(person.getName.apply(window));    //xm
 
//call和apply方法在第一个参数上是没有任何区别的
//第一个参数是改变this的值,第二个参数上call是一个一个传,apply是通过一个数组来传
 
function add(num1,num2){
    return num1+num2;
}
console.log(add.call(window,1,2));
console.log(add.apply(window,[1,2]));

参数的个数

实参=形参

实参<形参

function ad(num1,num2){
    return num1+num2;
}
add(1);
num1=1;
num2=undefined;
 
//可选参数
function pow(base,power){
    power=power||2;
    return Math.pow(base,power);
}
console.log(pow(3));
console.log(pow(3,3));

实参>形参

function add(){
    if(arguments.length==0) return;
    var sun=0;
    for(var i=0;i<arguments.length;i++){
        sum+=arguments[i];
    }
    return sum;
}
console.log(add());    //打印undefined
console.log(add(1,2,3,4));    //打印10

arguments 

arguments是一个类数组,本质是一个对象,不是真正的数组。函数的所有参数都放在arguments这个类数组中,可以通过下标去访问它们。

注意:arguments.callee指代函树本身

什么可以作为参数

什么都不传、数字、字符串、布尔值、undefined、null

数组

$.each([1,2,3],function (index,item){
    console.log(index);
    console.log(item);
});
for(var i=0;i<[1,2,3].length;i++){
    console.log(index);
    console.log(item);
}

对象

$.each({name:'xm',sex:'male'},function (index,item){
    console.log(index);
    console.log(item);
});
function setPerson(obj){
    var person={};
    person.name=obj.name||'xh';
    person.sex=obj.sex||'male';
    person.age=obj.age||'18';
    person.tel=obj.tel||'110';
    person.addr=obj.addr||'China';
    
}
setPerson({
    name:'xm'age:'18'addr:'China'sex:'male'
});

函数

$.each({name:'xm',sex:'male'},function(index,item){
    console.log(index);
    console.log(item);
});
setTimeout(function(){
    //body
},1000)

return

return:在函数中使用,

continue:在循环中使用,跳出本次循环

break:在循环中使用,跳出循环

什么可以作为返回值

没有返回值、数字、字符串、布尔值、数组、对象、函数

说明:为了写出更好更优雅的函数,会将函数作为参数传到另一个函数中,组成一个新功能函数。

这样函数具有两个功能:传进来的函数功能,接收函数的功能

JS面向对象

面向对象

对代码的一种抽象,对外统一提供调用接口的编程思想

基于原型的面向对象

基于原型的面向对象方式中,对象(object)则是依靠构造器(constructor)利用原型(prototype)构造出来的。

JS面向对象的名词解释

属性:事物的特性

方法:事物的功能

对象:事物的一个实例

原型:JS函数中用prototype属性引用了一个对象,即原型对象(原型)

说明:函数的prototype指向一个内存地址,这个地址中存储一个对象

//构造函数对象:函数构造器 创建函数对象
//var obj=new Function(var1,var2,...,functionBody());
//var1,var2 正常变量  functionBody() 自定义函数体
//注意:构造器构造的对象,效率低,var1 var2 顺序在functionBody中不能变
var obj=new Function('a','b','return a+b');
var s=obj(2,5);
alert(s);    //打印7
//说明:对象分为普通对象和函数对象,而用new Function构造的一定是函数对象

JS中的闭包

闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数)

JS中变量分为两种:全局变量和局部变量。在JS中,函数内部可以直接读取全局变量。

注意:在函数内用var声明的是局部变量,省略var的是全局变量。

以上就是一个函数闭包,通过返回函数中定义的函数,实现在全局作用域中读取局部变量的功能。首先调用a();,返回的是b,b是一个局部函数,再把b赋给c,c此时就相当于b函数,调用c();就相当于调用a();,由于函数b与局部变量i在同一个作用域,因此可以访问到局部变量b。

特点:函数b在函数a内嵌套,函数a需要返回函数b

用途:

  • 读取函数内部变量
  • 让 i 变量的值保留在内存中
function f1(){
    var n=999;
    nAdd=function(){
        n=n+1;
    }
    function f2(){
        alert(n);
    }
    return f2;
}
 
var rs=f1();
rs();    //999
nAdd();    //说明这个调用执行了,而且变量n始终存在
rs();    //1000

以上的rs其实就是f2闭包函数, 因为f1相当于f2的父亲,对于f2来说,n相当于全局变量,导致f2依赖于f1,所以f2与f1都一直在内存中。【重点】

优缺点:

  • 优点:有利于封装,可以访问局部变量
  • 缺点:内存占用时间长,浪费严重,会造成内存泄漏

对象声明方式

JS字面式对象声明对象

var obj={
    属性名称:属性值;
    属性名称:属性值;
    属性名称:属性值;
    ...
    方法名称:function(){},
    方法名称:function(){},
    ...
}
 
//example
var person = {
	name:"zhangsan",
	age:26,
	sex:"man",
	eat:function(fds){
		alert("我在吃"+fds);
	},
	play:function(ga){
		alert("我在玩"+ga);
	}
}
alert(person.age);
person.eat("面条");
alert(person instanceof Object);    //true

new操作符后跟Object构造函数

Object是所有对象的基类、根,所有的JavaScript对象都是由Object延伸的

var obj=new Object();
obj.属性=属性值;
obj.属性=属性值;
obj.方法=function(str){
    方法代码
};
 
//example
var box = new Object();
box.name = "zhangsan";
box.age = 100;
box.infos = function(str){
	return this.name+" "+this.age+" "+str; //this指向当前对象
}
alert(box.name);
var con = box.infos("吃饭那");
alert(con);

JS中构造方法声明对象

function test([参数列表]){
    this.属性=属性值;
    this.方法=function(){
        方法代码
    }
}
var obj=new test(参数列表);
 
//example
function person(name,sex,age){
	this.name = name;    //this.name是属性,name是参数,习惯上属性名称==参数
	this.sex  = sex;
	this.age  = age;
	this.show = function(){
		alert(this.name+" "+this.sex+" "+this.age);
	}
}
var obj1 = new person("zhangsan","nan",18);
alert(obj1.sex);
obj1.show();
var obj2 = new person("lisi","nv",20);
obj2.show();

注意: this代表当前对象,obj1和obj2两者之间是独立的,函数内部只能用this访问属性和方法

JS中工厂方式声明对象

工厂模式:按照某种模式,可以不断的创造对象

function createObject(name,age){
    var obj=new Object();
    obj.name=name;
    obj.age=age;
    obj.run=function(){
        return this.name+this.age+'运行中...';
    };
    return obj;    //★★★必须返回obj对象本身
}
var box1=createObject('zhagnsan',100);
var box2=createObject('lisi',200);
 
 
//example
function createObject(name,age){
	var obj = new Object();
	obj.name = name;// obj.name 属性  name参数
	obj.age  = age;
	obj.run = function(){ //在obj对象中,调用obj对象的属性,this代表的当前对象
		return this.name +" " + this.age +"运行中....";  
	}
	obj.say = function(){
		return "今天天气不错";
	}
	return obj;
}
var box1 = createObject("张三",18);
alert(box1.name); //调用属性成功
alert(box1.run()); //调用方法成功
var box2 = createObject("李四",20);
alert(box2.name);
alert(box2.run());
//box1 box2完全独立没有任何关系

构造和工厂模式的区别:

  • 构造方式不会显示创建对象将属性赋值给this ,不需要return对象
  • 工厂在方法内部创建object对象,返回object对象,属性和方法都是赋给object对象

JS中原型模式声明对象

原型模式根本:函数本身声明为空内容,利用prototype定义一些属性及方法

function test(){
}
test.prototype.属性=属性值;
test.prototype.属性=属性值;
test.prototype.方法名称=function(){
    执行代码
}
var obj=new test();
 
 
//example1
function test(){
}
alert(test.prototype instanceof Object); //自带该对象 prototype是Object子对象
test.prototype.color="red";
test.prototype.heights="1.7";
test.prototype.widths="1.2";	
test.prototype.showInfo=function(){
	alert(this.color+" "+this.heights+" "+this.widths);
}
test.prototype.getinfo=function(){
	alert("aaaaa");
}
var car1 = new test();
car1.getinfo();
 
//example2
function test(){
}
//json数据定义属性和方法
test.prototype={
	color:"red",
	heights:"1.7",
	widths:"1.2",
	showinfo:function(){
		alert(this.color+" "+this.heights+" "+this.widths);
	},
	getinfo:function(str){
		alert(str);
	}
}
var car1 = new test();
car1.getinfo("abc");

JS中混合模式声明对象

混合模式:原型+构造

function test(v1,v2,v3){
    this.v1=v1;
    this.v2=v2;
    this.v3=v3;
}
test.prototype.方法名称=function(){
    执行代码
}
var obj=new test(v1,v2,v3);
 
//example
function blog(name,url,friend){
	this.name 	= name;
	this.url	= url;
	this.friend	= friend;
}
blog.prototype={
	test:"awt",
	showinfo:function(){
		alert(this.name+" "+this.url);
	},
	gets:function(){
		alert(this.friend);
	}
}
var peo = new blog("张三","http://www.baidu.com","李四");
peo.showinfo();

对象的遍历

对象有的可以当作数组来处理

for-in语句中的变量表示单元的下标,在遍历对象时,i 的值取的是对象所有属性名称和方法名称

//example1
var ren={};
ren.name='zhangsan';
ren.age='18';
ren.len='180';
ren.demo=function(){
    alert(this.name);
}
for(var i in ren){
    alert(i);    //弹出属性和方法名称
    alert(ren[i]);    //弹出属性和方法内容
}
 
 
//example2
function ren(){
    this.name='zhangsan';
    this.age='18';
    this.length='180';
    this.demo=function(){
        alert(this.name);
    }
}
//使用构造函数声明对象要实例化后才可以进行遍历
var r=new ren();
for(var i in r){
    alert(i);    //弹出属性和方法名称
    alert(r[i]);    //弹出属性和方法内容
}
 

对象的存储

对象在内存中的分布:

对象的地址存在栈内存中,每个对象之间是独立的,对象地址指向堆内存中一块内存空间,这块内存空间中存放对象的所有属性以及方法的名称。方法名称也是一个地址,指向代码段中一块内存空间,这块内存空间存放方法的具体代码。

封装

封装:把对象内部数据和操作细节进行隐藏

大多面向对象的语言都支持封装的特性,提供了private关键字来隐藏某些属性或方法,用来限制被封装的数据或者内容的访问,只对外提供一个对象的专门访问的接口,接口一般为调用方法。

JavaScript中可以通过闭包实现封装,即函数内部声明的变量,外部是访问不到的。

公有与私有内容的区别是:能否在对象外部被访问

//example1
function demo(){
    var n=1;    //局部变量,在方法外部不能直接访问
    function test(){    //外部调用的出口
        n++;
    }
    test();
    return n;
}
alert(demo());    //打印2
 
//example2
function demo(){
    var n=1;
    function test(){    //特权方法
        return ++n;
    }
    return test;
}
var at=demo();    //at就相当于demo()的返回值test
alert(at());    //at()就相当于test(),打印2
 
//example3
function demo(){
    var n=1;
    this.test=function(){
        return ++n;
    }
    return test;
}

封装的缺陷:占用内存、不利于继承

原型和原型链

原型:是利用prototype添加属性和方法

原型链:JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用来指向创建它的函数对象的原型对象prototype

var person=function(){
    
};
var p=new person();    //对象的实例化分为三个阶段
// 1 var p={};    创建对象
// 2 p.__protp__=person.prototype    __proto__自带的一个属性
// 3 创建对象(初始化对象)p --> person.call(p)
 
alert(p.__proto__ instanceof Object);    //返回true,说明一个对象的__proto__属性也是一个对象
alert(p.__proto__==person.prototype);    //返回true,说明一个对象的__proto__属性就是这个构造函数的prototype
 
p.name 不存在→到__proto__去查找→p.__proto__是一个对象它也有自己的__proto__属性→p.__proto__.__proto__...
var person=function(){
    alert("天气很好");
}
var p=new person();
p.say();    //p实际上没有say方法,但为何能够调用?
//p没有say方法,就会到它的__proto__中去找
//p.__proto__=person.prototype
//由于person.prototype中是有say方法的,所以p.__proto__也有say方法,因此p可以调用这个say方法
var person=function(){
    alert("天气很好");
}
person.prototype.gongzi=500;
var programmer=function(){};
programmer.prototype=new person();
programmer.prototype.wcd=function(){
    alert('明天天气也不错');
}
programmer.prototype.gongzi=1000;
var p=new programmer();
p.say();
p.wcd();
p.gongzi;    //1000
 
//分析:
//var p=new programmer();    p.__proto__ == programmer.prototype == new person()
//相当于var p1=new person(); programmer.prototype == p1
//p.say(); 由于p本身不具有say方法,而p.__proto__ == new person() == p1
//p1本身也没有say方法,就到p1.__proto__中去找,p1.__proto__ == person.prototype
//person.prototype中有say方法,因此p可以调用say方法
//*****以上就是原型链实现过程*****
 
//在这里person相当于父方法,programmer相当于子方法,如果父子都有同一个属性,那么父属性会被子属性覆盖。

原型继承

//父
function person(name,age){
    this.name=name;
    this.age=age;
}
person.prototype.sayhello=function(){
    alert('属性name值'+this.name);
}
 
var per=new person('zhangsan',20);
per.sayhello();
 
//下面要实现继承
//子
function student(){
    student.prototype=new person("李四",18);
    student.prototype.grade=3;
    student.prototype.test=function(){
        alert(this.grade);
    }
}
var s=new student();
s.sayhello();
s.grade;    //3
 
//过程分析:
//s本身没有sayhello方法,到s.__proto__中去找,s.__proto__ == student.prototype
//student.prototype被实例化为person,相当于var p1=new person(); student.prototype=p1;
//因此p1.__proto__ == person.prototype,person.prototype中有sayhello方法,因此s可以调用这个方法

构造函数继承 

在子类内部构造父类的对象实现继承

function parents(name){
    this.name=name;
    this.say=function(){
        alert("父亲的名字:"+this.name);
    }
}
function child(name,age){    //继承parents
    this.pObj=parents;    //用父对象来创建子对象
    this.pObj(name);
    this.age=age;
    this.sayC=function(){
        alert('child:'+this.name+'age:'+this.age);
    }
}
 
var p=new parents('zhangsan');
p.say();
var c=new child('lisi',20);
c.sayC(); 
 
//lisi this.pObj(name) → parents(name) → this.name=name;
//this.sayC → this.name → parents→this.name;

说明:父对象被子对象继承,所有的属性和方法都传递到子对象中

call和apply的用法

call:调用一个对象的一个方法,以另一个对象替换当前对象

apply:应用某一对象的一个方法,用另一个对象替换当前对象

function person(name,age,len){
    this.name=name;
    this.age=age;
    this.len=len;
    this.say=function(){
        alert(this.name+':'+this.age+':'+this.len);
    }
}
//call继承
function student(name,age){
    person.call(this,name,age);
}
//apply继承
function teacher(name,age,len){
    person.apply(this,[name,age,len]);
}
var per=new('zhangsan',25,'170');
per.say();    //zhangsan:25:170
var stu=new('lisi',18);
stu.say();    //lisi:18:undefined
var tae=new('wangwu',20,'180');
tea.say();    //wangwu:20:180

分析以上代码,创建新对象stu时,李四和18分别传到student构造函数中,形参name和age的值被赋为李四和18。person.call(this,name,age)中的this指代调用该方法的对象,即stu,name是李四,age是18。

由于person.call()中的this被替换成了stu,所以person构造函数中的this也都被替换成stu,this.name=stu.name=李四,this.age=stu.age=18,而this.len=stu.len=undefined,所以stu.say()打印出来的是李四:18:undefined

JS面向对象的关键词

instanceof

变量是否是对象的实例

var arr=new Array();
alert(arr instanceof Array);    //true
alert(arr instanceof Object);    //true
 
function test(){}
var obj=new test();
alert(obj instanceof test);
alert(obj instanceof Object);

说明:所有对象本质上都是继承于Objecct

delete

删除对象的属性,对方法不起作用,对变量不起作用,对原型链中的属性和变量也不起作用

function fun(){
    this.name='zhangsan';
    this.say=function(){
        alert('this.name');
    }
}
var obj=new fun();
alert(obj.name);    //zhangsan
delete obj.name;    //删除name属性
alert(obj.name);    //undefined

call

call用来改变调用它的函数中this的指向

function add(a,b){
    alert(a+b);
}
function subs(a.b){
    alert(a-b);
}
add.call(subs,5,3);    //相当于把subs替换成add,也就是调用了add(5,3)
 
 
 
function animal(){
    this.name='ani';
    this.showName=function(){
        alert(this.name);
    }
}
function cat(){
    this.name='cat';
}
var an=new animal();
var c=new cat();
an.showName.call(c,',');    //an的showName方法中的this被改为c,c的name就是cat

注意:第一个参数的位置只能引用已经存在的对象

apply

apply和call的差别只在于第二个参数传递的方式

function add(a,b){
    alert(a+b);
}
function subs(a.b){
    alert(a-b);
}
add.apply(subs,[5,3]);    //相当于把subs替换成add,也就是调用了add(5,3)
 
 
 
function animal(){
    this.name='ani';
    this.showName=function(){
        alert(this.name);
    }
}
function cat(){
    this.name='cat';
}
var an=new animal();
var c=new cat();
an.showName.apply(c,[]);    //an的showName方法中的this被改为c,c的name就是cat

callee 

返回正在执行的function对象,function内容,callee是arguments的一个属性

arguments.callee的默认值就是正在执行的function对象,callee就是指代函数本身

function demo(){
    alert(arguments.callee);    //返回arguments所属的函数本身
    alert(arguments.callee());    //报错,说明callee是一个属性而不是方法
}
demo();
 
 
//由于callee可以指代函数本身,所以可以用来改写递归函数
var sum=function(n){
    if(n<=1){
        return 1;
    }else{
        return n+arguments.callee(n-1);
    }
}
alert(sum(5));

arugments 

每个函数都有一个Arguments对象的实例arguments,引用函数的参数(实参)

可以用数组下标方式引用arguments元素

arguments.length,参数个数

arguments.callee,引用函数自身

function test(a,b,c){
    alert(arguments.length);    //3
    alert(arguments[0]);    //1    arguments如果表示参数,是可以遍历的 
}
 
test(1,2,3);

this

主要用于构造

//① this 可以在函数内部定义属性/变量
function test(){
    this.x=1;    //this代表全局变量
    alert(this.x);
}
test();
 
//常用写法
 
var x=1;
function test(){
    this.x=0;    //改变了全局变量x的值
}
test();
alert(x);
 
//② 作为方法调用,主要存在于构造函数内,this指当前对象
function test(){
    this.name='zhangsan';    //this表示当前对象
    this.age=18;
}
var t=new test();
alert(t.name);
 
//③ 在call apply中,this指向第一个参数
var x=0;
function test(){
    alert(this.x);
}
var o={};
o.x=1;
o.m=test
o.m.apply();    //0
o.m.apply(o);    //1,因为test方法中的this被改为o了
 

对象冒充

将父类的属性和方法一起传给子类作为特权属性和特权方法

说明:继承有四种方式:原型继承、构造继承、call、apply

function person(name,age){
    this.name=name;
    this.age=age;
    function sayHi=function(){
        alert('hi');
    }
}
person.prototype.walk=function(){
    alert('walk....');
}
function student(name,age,grade){
    this.newMethod=person;    //冒充person对象,就会把父类的特权属性和特权方法一起传给子类
    this.newMethod(name,age)
    this.grade=grade;
}
var s1=new student('zhagnsan',15,2);    //s1是student对象,继承person,拥有person的所有属性和方法
alert(s1.name);    //zhangsan
alert(s1.sayHi());    //hi
s1.walk();    //报错
//因为walk属于person.prototype这个对象,不属于person对象
//student只继承person中的特权属性和方法,没有继承共有方法和属性

正则表达式

正则表达式的模式匹配

匹配:作比较,找出字符串中符合规则的字符

模式匹配:用模式去匹配字符串

注意:正则表达式都是操作字符串的

什么是正则表达式

普通字符(例如26个字母、数字等)、特殊字符(有特殊含义的,例如.\等)

说明:该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

为什么使用正则表达式

有助于我们快速查找所需要的文字内容

可以在查找到文字内容的基础上替换文字内容

正则表达式可以对用户输入得数据进行有效性验证(密码、邮箱之类)

创建正则表达式的两种方式

字面量或直接量的方式

类比:[ ]、{ }

正则表达式都是写在一对//之间的,如/js/可以匹配字符串中的js

构造函数

类比:new Array()、new Object()

regular expression(正则表达式),所以正则表达式的构造函数就是RegExp()

正则如何匹配字符串

注意:匹配到的内容一定是字符串中的内容

var str = 'I love js';    //匹配到的是str中的js
var pattern = /js/;    //正常模式是从左到右一个字符一个字符匹配
'3.4'    //正则中的.没法匹配字符串中的点,因为它是特殊字符,具有特殊含义
/js5_正则 ,;!@./    //这些都是普通字符,而特殊字符是具有特殊含义的字符

正则中的方法

test测试方法

通过正则去匹配字符串中相应的字符

exec执行方法(可以找到匹配到的字符)

通过正则找到匹配的字符串并扔到数组中返回出来

var str = 'I love js';
var pattern = /js/;
// test exec
console.log(pattern.test(str));    //true
console.log(pattern.exec(str));    //["js"]
console.log(pattern.test('I love Js'));    //false 说明正则区分大小写
console.log(pattern.exec('I love Js'));    //null 说明正则区分大小写

正则中的修饰符 

i:ignoreCase——忽略大小写

g:global——全局匹配

m:multiline——多行匹配

注意:构造函数法定义一个模式时,第一个参数是模式,第二个参数是修饰符,且都用引号括起来

//字面量定义法
var str = 'I love Js';
var pattern = /js/i;    //忽略大小写
console.log(pattern.test(str));    //true
console.log(pattern.exec(str));    //["Js"]
 
//构造函数法
var str = 'I love js';
var pattern = new RegExp('js');    //模式作为参数传进去
console.log(pattern.test(str));    //true
console.log(pattern.exec(str));    //["js"]
 
var str = 'I love Js';
var pattern = new RegExp('js', 'i');    //构造函数可以传递两个参数,第一个是模式,第二个是修饰符
console.log(pattern.test(str));    //true
console.log(pattern.exec(str));    //["Js"]

若既想实现忽略大小写又想全局匹配就把模式修饰符都写在一起,而且不区分顺序先后

/js/ig
/js/gi
/js/igm
/js/img

注意:用构造函数定义正则表达式可以随意改变正则的模式,原因是它本身是作为一个字符串传进去的

var str = 'I love Js';
var userInput = 'love';
var pattern = /userInput/i;    //导致匹配的就是字符串userInput
 
var str = 'I love Js';
var userInput = 'love';
var pattern = '/' + userInput + '/i';
console.log(typeof pattern);    //返回string,不是正则表达式类型,没有test和exec方法
 
//以上两种字面量定义的方法都没有办法动态修改正则表达式的模式,因此只能用构造函数法
 
var str = 'I love Js';
var userInput = 'love';
var pattern = new RegExp(userInput, 'i');
console.log(pattern);    //RegExp /love/i
console.log(typeof pattern);    //object
console.log(pattern.exec(str));    //["love"]

简单的转义字符

转义:将字符从一种含义转换成另一种含义

注意:/ 在正则中有特殊含义,表示正则的边界,所以若要表示真正的字符 / ,需要用转义字符

特殊字符转普通字符

//斜杠/转义
var str = '// 我是注释';
var pattern = /\/\//;    /*    \/是/的转义    */
console.log(pattern.exec(str));
 
 
//反斜杠\转义
var str = '\\\\';
var pattern = /\\\\/;    /*    \\是\的转义    */
console.log(str);
console.log(pattern.exec(str));

普通字符转特殊字符

var str = 'nba';
var pattern = /\n/;    //    \n表示换行符
 
var str = '1.html\n2.css\n3.js';
var pattern = /\n/;
console.log(str);
console.log(pattern.exec(str));
 
var str = '	js';
var pattern = /\t/;    //    \t表示制表符
console.log(pattern.exec(str));
 
var str = 'a\nb';
var pattern = /\x61\x0A\x62/;    //ascii码中的编号 \x0A = \n    \x61 = a    \x62 = b
console.log(pattern.exec(str));
 
var str = '	js';
var pattern = /\u0009/;    //Unicode中的编号 \u0009 = \t
console.log(pattern.exec(str));
 
var str = 'Alex帅';
var pattern = /衰/;
\u4e00-\u9fa5    //中文范围

字符类

正则表达式中,[ ] 是一个字符类,含义是匹配 [ ] 中的任意一个字符,它只会找出一个来匹配,先匹配到谁就是谁。

var str = 'sjavascript';
var pattern = /[js]/;    //j匹配到了
 
var str = 'javascript';
var pattern = /[^js]/;    //^取反,表示除了j和s以外的任何字符,那么匹配到的第一个是a
console.log(pattern.exec(str));
 
var str = 'javascript';
var pattern = /[a-z]/;    //a-z是一个范围,匹配到的第一个是j,范围一定要遵循 前<=后
console.log(pattern.exec(str));
 
var str = 'javascript';
var pattern = /[c-d]/;    //c-d是一个范围,匹配到的第一个是c
console.log(pattern.exec(str));
 
var str = 'javascript';
var pattern = /[c-c]/;    //c-c是一个范围,匹配到的第一个是c
console.log(pattern.exec(str));
 
var str = 'Alex帅';
var pattern = /[\u4e00-\u9fa5]/;    //\u4e00-\u9fa5是汉字范围,匹配到的第一个是帅
console.log(pattern.exec(str));
 
var str = 'JavaScript';    
var pattern = /[a-zA-Z]/;    //小写a-z和大写A-Z,但不能写成a-Z,匹配到的第一个是J
console.log(pattern.exec(str));
 
var str = '00544';
var pattern = /[0-9]/;    //0-9是一个范围,匹配到的第一个是0
console.log(pattern.exec(str));
 
 
var str = 'JavaScript2.0';
var pattern = /[a-zA-Z0-9]/;    //a-z或A-Z或0-9,匹配到的第一个是J
console.log(pattern.exec(str));
 
// /[a-zA-Z0-9@_asdf]/    //可以加上任意普通字符
// /[^\n]/    //表示除了\n都能匹配
// /./    //表示除了\n都能匹配
 
var str = '3.1415926';
var pattern = /./;    //匹配到3
console.log(pattern.exec(str));
 
var str = '3.1415926';
var pattern = /\./;    //    \.就是没有特殊含义的字符. ,匹配到的第一个是.
console.log(pattern.exec(str));
 
var str = '\n';
var pattern = /./;
console.log(pattern.exec(str));    //匹配不到,返回null
 
// /[a-zA-Z0-9_]/ 等价于 /\w/    所有字母和数字以及下划线
// /[^a-zA-Z0-9_]/ 等价于 /\W/    除了字母和数字以及下划线
 
var str = '_@';
var pattern = /\w/;
console.log(pattern.exec(str));    //匹配到的第一个是_
 
var str = '00544';
var pattern = /\d/;    //匹配到的第一个是0    \d是所有数字     \D是非数字
console.log(pattern.exec(str));
 
var str = ' ';
var pattern = /\s/;    //匹配到的第一个是空格    \s既能匹配空格也能匹配制表符还有unicode的空白符
console.log(pattern.exec(str));
// \S    除了空格、制表符和其他空白符以外的所有字符
 
var str = '1 1';
var pattern = /[\s\d]/;    //匹配空格或者数字,匹配到的第一个是1

说明:正则中大写都表示小写的反面。

正则的量词

var str = '110';
var pattern = /\d{2}/;    //想匹配几个就写几,这里匹配到11
console.log(pattern.exec(str));
 
var str = '110';
var pattern = /\d{1,2}/;    //{1,2}表示匹配个数>=1&&<=2,这里匹配到11,前面必须小于等于后面的,且中间不能有空格
console.log(pattern.exec(str));
 
var pattern = /\d{1, 2}/;    //格式错误
 
var str = '110';
var pattern = /\d{1,}/;    //{1,}表示匹配至少1个,这里匹配到110
console.log(pattern.exec(str));
 
var str = '110';
var pattern = /\d{,2}/;    //★★★这样的写法是错误的
console.log(pattern.exec(str));
 
var str = '110';
var pattern = /\d?/; // ? 相当于 {0,1} 0个或1个
console.log(pattern.exec(str));
 
var str = '110';
var pattern = /\d+/; // + 相当于 {1,} 至少1个
console.log(pattern.exec(str));
 
var str = '110';
var pattern = /\d*/; // * 相当于 {0,} 0个或更多
console.log(pattern.exec(str));
 
var str = 'XXX豪华午餐:¥15.5!';
var pattern = /\d+\.?\d*/;    //    \d+:至少一个数字,小数点前    \.?:.先转移,0个或1个    \d*:0个或更多个数字,小数点后
var pattern = /\d{1,}\.{0,1}\d{0,}/;
console.log(pattern.exec(str));
 
var str = 'aaab';
var pattern = /a+/;    //正则在默认情况下都是贪婪的,只要条件允许就会尽可能多地匹配符合正则表达式的字符,这里匹配到aaa
console.log(pattern.exec(str));
 
var str = 'aaab';
var pattern = /a+?/;    //在量词后面加个?可以把贪婪模式转换成非贪婪模式,这里匹配到a
console.log(pattern.exec(str));
 
var str = 'aaab';
var pattern = /a+b/;    //贪婪模式,匹配到aaab
console.log(pattern.exec(str));
 
var str = 'aaab';
var pattern = /a+?b/;    //转换成非贪婪模式,还是匹配到aaab,正则会选择第一个可能匹配上的,而不是最适的,ab是最适的,但不是第一个能匹配上的,因为非贪婪匹配的前提条件是条件允许的情况下,这里要确保有a又有b,并且是从前往后依次匹配,那么只有aaab是最先符合要求的
console.log(pattern.exec(str));
 
var str = '<td><p>a</p></td><td><p>b</p></td>';
var pattern = /<td>.*<\/td>/;    //中间内容用.表示,*表示最少0个最多无限,贪婪模式尽可能多的匹配,匹配到<td><p>a</p></td><td><p>b</p></td>
console.log(pattern.exec(str));
 
var str = '<td><p>a</p></td><td><p>b</p></td>';    //非贪婪模式,尽可能少的匹配,匹配到<td><p>a</p></td>
var pattern = /<td>.*?<\/td>/;
console.log(pattern.exec(str));

正则中的选择、分组、引用

选择

var str = 'css html js';
var pattern = /html|css|js/;    //匹配html或css或js,会从模式字符串的最左边开始尝试匹配,这里匹配到css
console.log(pattern.exec(str));
 
var str = 'ab';
var pattern = /a|ab/;    //匹配a或ab,这里匹配到a,选择最先符合的而不是最合适的
console.log(pattern.exec(str));

分组 () 和引用

var str = 'abab';
var pattern = /(ab)+/;    //匹配到abab,(ab)意为ab作为一个分组,也就是作为一个整体进行匹配,由于+仅能影响在它之前与它紧挨的字符,而不能影响到前面所有字符,所以用()分组可以将多个字符视为一个字符来处理
console.log(pattern.exec(str));    //["abab","ab"],数组的第一个内容是正则匹配到的内容,第二个是分组内容,这些返回值可以捕获到我们感兴趣的数据
 
var str = 'abcd';
var pattern = /(ab)c/;    //匹配到abc
console.log(pattern.exec(str));    //["abc","ab"],数组的第一个内容是正则匹配到的内容,第二个是分组内容,这些返回值可以捕获到我们感兴趣的数据
 
var str = 'abcd';
var pattern = /(?:ab)c/;    // ?:开启非捕获模式,也就不会返回除了匹配的字符串以外的字符串了
console.log(pattern.exec(str));    //["abc"]
 
var str = 'abcd';
var pattern = /(ab)(c)/;    //匹配到abc
console.log(pattern.exec(str));    //["abc","ab","c"]
 
var str = 'abcd';
var pattern = /(a(b(c)))/;    //嵌套也是从左边开始数括号,第一对括号是第一个分组,以此类推
console.log(pattern.exec(str));    //["abc","abc","bc","c"]
 
var str = 'ab cd ab';
var pattern = /(ab) cd \1/;    //使用正则捕获到的字符串,\1代表第一个分组    \2代表第二个分组
console.log(pattern.exec(str));    //["ab cd ab","ab"]    //ab是第一个分组所匹配到的内容
 
var str = '<p><a>这是一段文字</a></p>';
var pattern = /<([a-zA-Z]+)>(.*?)<\/\1>/;    //左半边标签作为一个分组,右半边标签就可以直接用分组1,?开启非贪婪模式
console.log(pattern.exec(str));
 
'abc' /c/ index    //exex的返回值有index属性,返回匹配的字符串在模式串中的位置,还有input属性,可以把exec方法的参数完整的传出来

指定位置匹配

首尾匹配

var str = 'html js';
var pattern = /^js/;    //^强调从模式串开头匹配
console.log(pattern.exec(str));    //js不在模式串开头,匹配不到,返回null
/[^0-9]/    //[]中的^是取反,不是首匹配
 
var str = 'html js css';
var pattern = /js$/;    //$强调从模式串开头匹配
console.log(pattern.exec(str));    //js不在模式串末尾,匹配不到,返回null
 
var str = '110119120';
var pattern = /^\d+$/;    //以数字开头,匹配一个或多个数字(贪婪),并以数字结尾——输入的都是数字
console.log(pattern.exec(str));    //匹配到110119120
if (pattern.test(str)) {
 	console.log('全是数字!');
 } else {
 	console.log('不全是数字!');
 }

注意:字符类中的^含义是匹配除了[ ]中的字符以外的所有字符

边界匹配

var str = 'js html';
var pattern = /js\b/;    // \b匹配单词的边界,这里匹配完js就到达边界停止匹配
console.log(pattern.exec(str));    //["js"]
 
var str = '@@@js@@@';
var pattern = /\bjs\b/;    //@显然不是单词,而js前后都有边界,把所有@都过滤掉了,匹配到js
console.log(pattern.exec(str));
 
 
//方法调用
var oddP = getByClassName('odd');
var evenP = getByClassName('even');
for (var i = 0; i < oddP.length; i++) {
 	oddP[i].style.backgroundColor = 'red';
}
for (var i = 0; i < evenP.length; i++) {
	evenP[i].style.backgroundColor = 'yellow';
}
 
//一个能兼容IE浏览器的根据class名获取元素的方法
 
//最终版
function getByClassName(className, parentNode) {
    if (document.getElementsByClassName) {    //若是非IE则可直接使用getByClassName方法
        return document.getElementsByClassName(className);
    } else {console.log(1);
    parentNode = parentNode || document;    //若用户没有传入父节点名称,则使用document作为父节点
    var nodeList = [];
    var allNodes = parentNode.getElementsByTagName('*');    //先获取父节点下面所有子元素存进allNodes数组中
    var pattern = new RegExp('\\b' + className + '\\b');    //设置正则表达式的边界以及目标className
 
    for (var i = 0; i < allNodes.length; i++) {
        if (pattern.test(allNodes[i].className)) {    //若allNodes数组中的元素class名被正则匹配
            nodeList.push(allNodes[i]);    //把匹配的元素加入nodeList
        }
    }
    return nodeList;
    }
}
 
//改进版
function getByClassName(className, parentNode) {
	if (document.getElementsByClassName) {
		return document.getElementsByClassName(className);
	} else {console.log(1);
		parentNode = parentNode || document;
		var nodeList = [];
		var allNodes = parentNode.getElementsByTagName('*');
		var pattern = new RegExp('(^|\\s+)' + className + '($|\\s+)');    //开头无字符或有一个至多个空格,结尾无字符或有一个至多个空格,满足多个class名以及class名之间空格数目不定的情况
 
		for (var i = 0; i < allNodes.length; i++) {
			if (pattern.test(allNodes[i].className)) {
				nodeList.push(allNodes[i]);
			}
		}
		return nodeList;
	}
}
 
//最初版本
function getByClassName(className, parentNode) {
	if (document.getElementsByClassName) {
		return document.getElementsByClassName(className);
	} else {console.log(1);
		parentNode = parentNode || document;
		var nodeList = [];
		var allNodes = parentNode.getElementsByTagName('*');
 
		for (var i = 0; i < allNodes.length; i++) {
			if (allNodes[i].className === className) {    //这种判断方法无法满足多个类名的情况
				nodeList.push(allNodes[i]);
			}
		}
 
		return nodeList;
	}
}

前瞻性匹配和负向前瞻性匹配

//前瞻性匹配
var str = 'javascript';
var pattern = /java(?=script)/;    //java后面跟的是script,这个java才能被匹配
console.log(pattern.exec(str));    //返回["java"]
var str = 'java';
var pattern = /java(?=script)/;    //java后面跟的是script,这个java才能被匹配
console.log(pattern.exec(str));    //返回null
 
//负向前瞻性匹配
var str = 'javascript';
var pattern = /java(?!script)/;    //java后面是script,这个java就不能被匹配
console.log(pattern.exec(str));    //返回null
var str = 'javasda';
var pattern = /java(?!script)/;    //java后面是script,这个java就不能被匹配
console.log(pattern.exec(str));    //返回["java"]

RegExp对象的实例方法

//一般方法,用RegExp对象实例化的方法可以灵活修改正则表达式
/js/
new RegExp('js')
var className = 'box';
new RegExp(className)
 
console.log(new RegExp('\\b'));    /*    边界符需要转义    */
/\\/    /*    \\才是\    */    
new RegExp('\\\\')    //四个反斜杠才能匹配字符串中的一个反斜杠?????
 
// RegExp实例方法
var pattern = /js/;    //字面量定义法,pattern是一个实例对象
var pattern = new RegExp('js');    //对象定义法,pattern也是一个实例对象
 
 
//非全局匹配模式下,匹配到一个符合条件的就结束匹配
var str = 'js js js';
var pattern = /js/;
console.log(pattern.exec(str));    //无论执行几次,匹配到的永远是第一个js,查看index属性都是0
console.log(pattern.exec(str));
console.log(pattern.exec(str));
console.log(pattern.exec(str));
 
 
//全局匹配模式下,要看完整个模式串,把能匹配的都匹配了才结束
var str = 'js js js';
var pattern = /js/g;
console.log(pattern.exec(str));    //第一次执行,匹配第一个js,index=0
console.log(pattern.exec(str));    //第二次执行,匹配第二个js,index=3
console.log(pattern.exec(str));    //第三次执行,匹配第三个js,index=6
console.log(pattern.exec(str));    //第四次执行,返回null
console.log(pattern.exec(str));    //第五次执行,匹配第一个js,index=0
 
//pattern.lastIndex    
//这个属性非全局匹配下都是0,不会变化。
//而全局模式下,每次执行匹配后lastIndex都指向匹配上的字符的下一个位置,
//下一次匹配就会从这个lastIndex的位置往下继续匹配,
//而一旦扫描完模式串再找不到匹配的字符就会返回null,然后将lastIndex重置为0,这就解释了为何上述代码第五次执行又回到了第一个js。
 
 
//非全局匹配模式
var str = 'js js js';
var pattern = /(j)s/;
console.log(pattern.exec(str));    //["js","j"]    index=0
console.log(pattern.exec(str));    //["js","j"]    index=0
console.log(pattern.exec(str));    //["js","j"]    index=0
console.log(pattern.exec(str));    //["js","j"]    index=0
 
//全局匹配模式-exec
var str = '1.js 2.js 3.js';
var pattern = /js/g;
var total = 0,
match = '',
result;
 
while ((result = pattern.exec(str)) != null) {
total++;    //匹配个数计数器
match += '第' + total + '个匹配到的是:' + result[0] + ', 它的位置是:' + result.index + '\n';}    //result[0]是匹配到的结果
match += '共找到' + total + '处匹配\n';
console.log(str);
console.log(match);
 
//全局匹配模式-test
var str = 'js js js';
var pattern = /js/g;
console.log(pattern.test(str));    //true
console.log(pattern.test(str));    //true
console.log(pattern.test(str));    //true
console.log(pattern.test(str));    //false 这是返回值为null的结果
console.log(pattern.test(str));    //true 这是lastIndex重置为0的结果
 
//pattern的toString方法和toLocaleString方法
var pattern = new RegExp('a\\nb')
console.log(pattern.toString());    //    a\nb
console.log(pattern.toLocaleString());    //    转换成具有本地特色的字符串(?)a\nb
console.log(pattern.valueOf() === pattern);    //    valueOf返回正则本身

RegExp对象的实例属性和构造函数属性

//RegExp实例属性
var str = 'js js js';
var pattern = new RegExp('\\b', 'i');
console.log(pattern.ignoreCase);    //判断正则是否有i属性
console.log(pattern.global);    //判断正则是否有g属性
console.log(pattern.multiline);    //判断正则是否有m属性
console.log(pattern.source);    //返回字面量形式对应的字符串
console.log(pattern.lastIndex);
 
//非全局匹配模式
var str = 'js js js';
var pattern = /js/;
console.log(pattern.lastIndex);    //0
pattern.test(str);    //true
console.log(pattern.lastIndex);    //0
pattern.test(str);    //true
console.log(pattern.lastIndex);    //0
pattern.test(str);    //true
console.log(pattern.lastIndex);    //0
pattern.test(str);    //true
console.log(pattern.lastIndex);    //0
pattern.test(str);    //true
console.log(pattern.lastIndex);    //0
 
//全局匹配模式
var str = 'js js js';
var pattern = /js/g;
console.log(pattern.lastIndex);    //2
pattern.test(str);
console.log(pattern.lastIndex);    //5
pattern.test(str);
console.log(pattern.lastIndex);    //8
pattern.test(str);
console.log(pattern.lastIndex);    //0
pattern.test(str);
console.log(pattern.lastIndex);    //2
pattern.test(str);
console.log(pattern.lastIndex);    //5
 
// 构造函数属性(并不常用,还有兼容性问题)
var str = 'js js js';
var pattern = /(j)s/;
pattern.exec(str);    //★★★必须执行之后正则里面的属性才有值
console.log(RegExp.input);    //返回匹配的字符串
console.log(RegExp.$_);    //返回匹配的字符串,上一条语句的别名
//合法的标识符构成:以字母、_、$开头,后面跟字母、_、$、数字
console.log(RegExp['$_']);    //所有能用 . 表示的都能用 [ ] 表示,反之不一定能成立, [ ]的使用范围更广泛
console.log(RegExp.lastMatch);    //返回最后一次匹配到的字符串,返回js
console.log(RegExp['$&']);    //返回最后一次匹配到的字符串,上一条语句的别名,由于&不是合法标识符,所以不能写在 . 后面,要写在 [ ] 里面
console.log(RegExp.leftContext);    //匹配到的字符串左边剩余的字符,返回空字符,因为左边什么都没有
console.log(RegExp['$`']);    //别名
console.log(RegExp.rightContext);    //匹配到的字符串右边剩余的字符,返回 js js
console.log(RegExp["$'"]);    //别名,PS:由于里面有单引号,所以字符串改用双引号括起来
console.log(RegExp.lastParen);    //上一次匹配到的子选项,即通过分组捕获的内容,返回j
console.log(RegExp["$+"]);    //别名
 
// exec
// /\1\2/    //在正则中使用分组的写法 \1代表第一组 \2代表第二组
// console.log(RegExp.$1);    //构造函数的分组用$,并且只能从1到9

String对象中与正则相关的方法之search,match和split

search()

判断匹配是否存在,因此是否全局匹配没有影响

var str = 'html js js';
var pattern = /js/g;
console.log(str.search(pattern));    //由于是字符串的方法,要用字符串去调用,并把正则表达式作为参数传入,返回匹配到的位置,返回5,若没有匹配到,则返回-1。search方法无论是否是全局匹配都只返回第一次匹配的位置,因为它只用来判断这个匹配是否存在。
 
var str = 'html js js';
console.log(str.search('js'));    //用字符串形式传入也还是会先转化成正则形式再进行匹配

match()

与exec差不多,但有一些区别:

  • match在非全局的情况下才会返回分组中匹配到的内容,全局匹配只能匹配到所有匹配到的字符
  • exec无论是否全局匹配都会返回分组中匹配到的内容,都只会返回当前匹配到的一个内容,而不是全部返回
var str = 'js js js';
var pattern = /(j)s/;
console.log(str.match(pattern));    //["js","j"]
 
var str = 'js js js';
var pattern = /(j)s/g;
console.log(str.match(pattern));    //["js","js","js"]
 
// match VS exec
 
var str = '1.js\n2.js\n3.js';
var pattern = /js/m;    //多行匹配,但由于不是全局匹配,所以一行和多行没有区别
console.log(str);
console.log(str.match(pattern));    //["js"]
 
var str = '1.js\n2.js\n3.js';
var pattern = /js/mg;    //多行匹配常和全局匹配一起用(但由于有全局匹配,所以多行匹配也显得鸡肋)
console.log(str);
console.log(str.match(pattern));    //["js","js","js"]
 
//多行匹配真正起作用的场合
var str = '1.js\n2.js\n3.js';
var pattern = /js$/g;    //全局匹配,匹配以js结尾的
console.log(str);
console.log(str.match(pattern));    //看作一行,只匹配到最后一个js
 
var str = '1.js\n2.js\n3.js';
var pattern = /js$/mg;    //多行全局匹配,匹配以js结尾的
console.log(str);
console.log(str.match(pattern));    //看作三行,每行都匹配到行末的js,返回["js","js","js"]
 
var str = 'js1\njs2\njs3';
var pattern = /^js/mg;    //多行全局匹配,匹配以js开头的
console.log(str);
console.log(str.match(pattern));    //看作三行,每行都匹配到行首的js,返回["js","js","js"]

split()

var str = 'html,css,js';
console.log(str.split(','));    //用,分割,分割结果放在字符串数组中
 
var str = 'html,css,js';
var pattern = /,/g;    //使用正则可以达到相同效果,且加不加g都一样
console.log(str.split(pattern));
 
var str = 'html ,   css  ,   js';
var pattern = /\s*,\s*/;    //用,分割并且消除不定空格
console.log(str.split(pattern));

replace()

非全局匹配,只替换第一个匹配到的内容,只有全局匹配下才替换所有匹配到的内容

var str = 'I love js js';
console.log(str.replace('js', 'html'));    //把js替换成html,且只能替换最左边的一个
 
var str = 'I love js js';
var pattern = /js/g;
console.log(str.replace(pattern, 'html'));    //配合全局匹配使用,可以把所有js替换成html
 
var str = '1111-11-11';
var pattern = /-/g;
console.log(str.replace(pattern, '.'));    //把xxxx-xx-xx换成xxxx.xx.xx
 
var str = 'I love js';
var pattern = /(js)/;
document.write(str.replace(pattern, '<strong style="color: red;">$1</strong>'));    //$1表示分组1,也就是js
 
//实现敏感词过滤
var str = '中国军队和阿扁一起办证';
var pattern = /国军|阿扁|办证/g;
console.log(str.replace(pattern, '*'));    //把敏感词提取出来,替换成*,返回中*队和*一起*,不是我们想要的,应该是过滤掉多少字符就用多少个*替换
 
var str = '中国军队和阿扁一起办证';
var pattern = /国军|阿扁|办证/g;
console.log(str.replace(pattern, function ($0) {    //$0就是匹配到的字符串
    var result = '';
    for (var i = 0; i < $0.length; i++) {    //数长度
		result += '*';
	}
	return result;
}));

常用正则表达式

QQ

要求

  • 全是数字
  • 首位不能为0
  • 最少5位
  • 最多11位

/^[1-9]\d{4,10}$/

/^[1-9]\d{4,}$/

昵称

要求

  • 中、英文、数字以及下划线
  • 2-18位

/^[\u4e00-\u9fa5a-zA-Z0-9_]{2,18}$/

/^[\u4e00-\u9fa5\w]{2,18}$/

密码

要求

  • 6-16位
  • 不能用空白字符\s

/^\S{6,16}$/

/^[\w~!@#$%^

]{6,16}$/

去除字符串首尾的空白字符

//首匹配
var str = '        Alex         ';
var pattern = /^\s+/;
console.log('|' + str.replace(pattern, '') + '|');
 
//尾匹配
var str = '        Alex         ';
var pattern = /\s+$/;
console.log('|' + str.replace(pattern, '') + '|');
 
//首尾都匹配
var str = '        Alex         ';
var pattern = /^\s+|\s+$/g;    //应该用全局匹配
console.log('|' + str.replace(pattern, '') + '|');
 
//首尾匹配变式
var str = '        Alex         ';
var pattern1 = /^\s+/;
var pattern2 = /\s+$/;
console.log('|' + str.replace(pattern1, '').replace(pattern2, '') + '|');
 
//总结一个常用方法
function trim(str) {
	return str.replace(/^\s+/, '').replace(/\s+$/, '');
}
trimLeft trimRight    //尝试自行实现
var str = '        Alex         ';
console.log('|' + trim(str) + '|');

转驼峰

css: background-color: red;
js: elem.style.backgroundColor = 'red';
jquery: $(elem).css('background-color', 'red');
 
var str = 'background-color';
var pattern = /-([a-z])/gi;    //用分组可以把它单独拎出来
console.log(str.replace(pattern, function (all, letter) {
	return letter.toUpperCase();
}));
 
function toCamelCase(str) {
	return str.replace(pattern, function (all, letter) {
		return letter.toUpperCase();
	})
}

匹配HTML标签

var str = '<p class="odd" id="odd">123</p>';
var pattern = /<\/?[a-zA-Z]+(\s+[a-zA-Z]+=".*")*>/g;    //?表示可有可无,.表示所有内容,*表示可以没有也可以有多个
console.log(str.match(pattern));
 
var str = '<p class="odd" id="odd">123</p>';
var pattern = /<[^>]+>/g;
console.log(str.match(pattern));
 
//特殊情况
var str = '<input type="text" value=">" name="username" />';
var pattern = /<(?:[^"'>]|"[^"]*"|'[^']*')*>/g;    //<>中间可以有什么内容:①除了引号和>②双引号中间除了双引号以外都可以③单引号中间除了单引号都可以。?:开启非捕获
console.log(str.match(pattern));
 
//另一种写法
var str = '<input type="text" value=">" name="username" />';
var pattern = /<(?:[^"'>]|(["'])[^"']*\1)*>/g;    //
console.log(str.match(pattern));

email邮箱

alex@yahoo.com.cn
alex_1@yahoo.com.cn
alex_1.a-sdasd.asdasdas.com@yah-o_o.com.adasdsd.cn
/(?:\w+\.)*\w+@(?:\w+\.)+[a-z]/i\
/^[a-z0-9]+(?:[._-][a-z0-9]+)*@[a-z0-9]+(?:[._-][a-z0-9]+)*\.[a-z]{2,4}$/i

URL

分析:(协议://)主机名(:端口号)(/路径)
ftp|http|https|mailto|file:/// //协议的种类
/^(https?:\/\/)?([^:\/]+)(:\d+)?(\/.*)?$/

匹配主机名
/[^:\/]+/\
-www.-imos-oc.casfasf.sadasd.co-m // -不允许出现在首尾,但可以出现在中间
ai //主机名也可以是这种情况
/[a-z0-9]|[a-z0-9][-a-z0-9]*[a-z0-9]/i
/^([a-z0-9]\.|[a-z0-9][-a-z0-9]*[a-z0-9]\.)*(com|edu|gov|net|org|[a-z]{2})$/i
/^([a-z0-9]\.|[a-z0-9][-a-z0-9]*[a-z0-9]\.)*([a-z]+)$/i