【知识体系】JavaScript高级

该文章的内容主要包括原型对象、原型继承、函数更细节的部分、闭包、递归和正则表达式

这是js系列的最后一篇文章。在开始看这篇文章之前最好先要掌握js基础语法。开头先给js的数据类型做了一个简单的梳理。


JavaScript中的数据类型
基本类型(值类型)

  • undefined
  • null
  • boolean
  • number
  • string

复杂类型(引用类型)

  • Object
  • Array
  • Date
  • RegExp
  • Funtion
  • 基本包装类型
    • boolean
    • number
    • string
  • 单体内置对象
    • Global
    • Math

类型检测

  • typeof
  • instanceof
  • Object.prototype.toString.call()

原型基础

引入

原型对象的作用

  • 数据共享,节省空间(需要共享的卸载原型对象中,不需要的写在构造函数中)
  • 为了实现继承

简单了解原型对象

问题:用构造函数创建实例化对象时,如果实例化对象中有相同的属性或方法,实例化多个对象会占多个内存,浪费空间

解决:通过原型的方式,把共享的方法单独提出来写

//构造函数
function Person(name,age){
    this.name=name;
    this.age=age;
}

//通过原型对象来添加方法和属性,共享方法和属性
Person.prototype.eat=function(){}
Person.prototype.height="160"

//实例化对象
var per1=new Person("小明",18)
var per2=new Person("小红",16)
per1.eat()//可调用
per2.eat()//可调用

构造函数,原型对象与实例对象的区别

构造函数可以实例化对象

构造函数中有个属性prototype,它是构造函数的原型对象;而构造函数的原型对象(prototype)中有一个构造器,这个构造器指向的就是该原型对象所在的构造函数

原型对象有个构造器,就是构造函数本身

实例对象的原型对象(__proto__)指向的是构造函数的原型对象

构造函数的原型对象(prototype)中的方法可以被实例对象直接访问

原型链:实例对象和原型对象之间的关系,关系是通过原型(__proto__)来联系的。

原型对象也有__proto__,它指向的是Objec原型对象,而Objec原型对象的__proto__最后指向的是null。这在原型指向中有图解

在这里插入图片描述

三者的特点

  • 构造函数和原型对象的方法都可以相互访问

    //以原型对象举例
    function Person(name,age){
        this.name=name;
        this.age=age;
    }
    Person.prototype.eat=function(){
        this.play()
    }
    Person.prototype.play=function(){}
    
  • 实例对象使用的方法和属性,先在实例对象中找,没有再到原型对象里找

    function Person(name,age){
        this.name=name;
        this.age=age;
    }
    Person.prototype.age=20
    
    
    //情况1:实例对象有该属性
    var person=new Person("小明",18)
    console.log(person.age)//--->18
    //情况2:实例对象没有该属性
    var person=new Person("小明")
    console.log(person.age)//--->20
    
  • 可以给内置对象的原型对象添加方法,会直接改变源码

  • 如果构造函数没有某一属性,实例对象却去访问了,结果是undefined而不是报错

    function Person(age){
        this.age=age
    }
    var per=new Person(){10}
    cosnole.log(per.name)//undefined
    //解释:因为js是一门动态类型的语言。如果对象没有,但只要obj.——点了,那么这个对象就相当于有了这个东西,只是没有属性值。
    

原型对象的简单写法

Person.prototype={
    //注意:需要手动修改构造器的指向
    constructor:Person
    height:"160",
    age:18,
    eat:function(){}
}

原型指向

functin Person(age){
    this.age=age
    console.log(this)//------------------构造函数的this
}
Person.prototype.eat=function(){
    console.log(this)//------------------原型对象的方法中的this
}

var per=new Person(10)
console.log(per)//-----------------------实例对象

//打印得出结论:构造函数中this是实例对象;原型对象中方法中的this是实例对象;

改变原型指向

//人的构造函数
function Person(){}
//人的原型对象方法
Person.prototype.eat=function(){}

//学生的构造函数
function Student(){}
//学生的原型对象方法
Student.prototype.say=function(){}

//改变原型指向
//方式1:
Student.prototype=new Person()
//实例化student对象
var stu=new Student()
stu.say()//报错
stu.eat()//成功

最开始
在这里插入图片描述
在这里插入图片描述

//人的构造函数
function Person(){}
//添加原型对象方法
Person.prototype.eat=function(){}

//改变原型指向
//方式2:
Person.prototype={
   say:function(){} 
}
//实例化Person对象
var per=new Person()
per.eat()//报错
per.say()//成功

如何在原型指向后添加方法?

//在上面方式一的基础上,只需要在指向改变后面添加方法即可。此时的say在Person实例对象中
Student.prototype=new Person()
Student.prototype.say=function(){}
stu.say()//成功

原型继承

继承就是类与类之间的关系。js没有类,但可以通过构造函数模拟类,然后通过原型来实现继承。继承也是为了实现数据共享

1. 改变原型指向

问题:同样是人,但是身份不一样,人们具有的属性和方法就会有差异。因为这些差异我就要重新创建一个构造函数来创建实例对象,太烦太占空间了!
解决:改变原型指向

//我需要继承的人共有的方法和属性
function Person(name,age){
    this.name=name;
    this.age=age
}
Person.prototype.eat=function(){console.log("人可以吃")}
Person.prototype.sleep=function(){console.log("人可以睡")}

//这是我作为学生特有的属性,我该怎么继承人的呢?
function Student(score){
    this.score=score
}

//解决:改变学生的原型指向
Student.prototype=new Person("小明",18)

//给Student原型对象添加方法
Student.prototype.study=function(){console.log("学生要学习")}

//ok完成
//实例化对象
var stu=new Student(100)
//学生所继承的
console.log(stu.name)//小明
console.log(stu.age)//18
stu.eat()//人可以吃
stu.sleep()//人可以睡
//学生所特有的
console.log(stu.score)//100
stu.study()//学生要学习

2. 借用构造函数

问题:但是吧,如果我改变了指向,做到了继承。那如果我创建第二个学生?第三个学生呢?他们的名字和年龄是一样的!只有分数不一样。太可怕了,怎么办?

解决:借用构造函数:构造函数名字.call(当前对象,属性,属性,...)——解决属性值重复问题

//被继承的
function Person(name,age){
    this.name=name;
    this.age=age
}
Person.prototype.eat=function(){console.log("人可以吃")}


//我需要继承
function Student(name,age,score){
    Person.call(this,name,age,score)
    //这里相当于直接把Person的构造函数拿过来使用
    //这里的this指向实例对象,上面讲过。表示实例对象在调用Person的构造函数
    this.score=score
}

//ok完成
//实例化对象
var stu=new Student("小明",18,100)
console.log(stu.name)//小明
console.log(stu.age)//18
console.log(stu.score)//100

stu.eat()//报错,继承不了方法

3. 组合继承

问题:借用构造函数的方法不能继承方法,怎么办!

解决:组合继承:改变原型指向+借用构造函数

//被继承的
function Person(name,age){
    this.name=name;
    this.age=age
}
Person.prototype.eat=function(){console.log("人可以吃")}

//借用构造函数
function Student(name,age,score){
    Person.call(this,name,age,score)
    this.score=score
}
//改变原型指向,不传值
Student.prototype=new Person()

//给Student原型对象添加方法
Student.prototype.study=function(){console.log("学生要学习")}

//ok完成
//实例化对象
var stu=new Student("小明",18,100)
console.log(stu.name)//小明
console.log(stu.age)//18
console.log(stu.score)//100
stu.eat()//人可以吃
stu.study()//学生要学习

4. 拷贝继承

把一个对象中的属性或者方法直接复制到另一个对象中

var person1={
    name:"小明",
    age:20,
    eat:function(){
        console.log("吃东西")
    }
}

var person2=person1
//仅仅改变了地址的指向
//真正的拷贝继承
function Person(){}
Person.prototype.name="小明"
Person.prototype.age=18
Person.prototype.eat=function(){}

var person={}
for(var key in Person.prototype){
    person[key]=Person.prototype[key]
}
cosnole.log(person.name)//小明
person.eat()//成功

函数进阶

回顾

  • 函数的声明

    function f1(){}
    
  • 函数表达式

    var f1=function(){}
    

函数的不同调用方式

  • 普通函数

    function f1(){}
    f1()
    
  • 构造函数

    //通过new调用,创建对象
    function F1(){}
    var f1=new F1()
    
  • 对象的方法

    function F1(){
        this.eat=function(){}
    }
    var f1=new F1()
    f1.eat()
    

函数中的this

  • 普通函数的this是谁?——window
  • 对象.方法中的this是谁?——实例对象
  • 定时器方法中的this是谁?——window
  • 构造函数中的this是谁?——实例对象
  • 原型对象方法中的this是谁?——实例对象

严格模式

//普通
function f1(){console.log(this)}
f1()//window
//严格模式
"use strict"
function f1(){console.log(this)}
f1()//undefined  //你没有指明是谁调用的,我也不知
window.f1()//window  //你指明了,那就window把

改变指向

  • apply和call

    apply和call的第一个参数如果没有传入或者传入了null,对象的this就是window。
    它们都可以调用函数,调用的时候改变指向,效果 一样。只是传入参数的方式不一样,apply是数组,call是一个一个的参数

    • apply(对象,[参数1,参数2,...])

    • call(对象,参数1,参数2...])

      f1.apply(null,[1,2])
      f1.call(null,1,2)
      
  • bind

    复制,即不用调用函数就能改变指向:bind(obj,参数1,参数2)

    //人的构造函数
    function Person(age){
        this.age=age
    }
    Person.prototype.go=function(){
        console.log(this+"--"+this.age)
    }
    
    //学生的构造函数
    function Student(age){
        this.age=age
    }
    
    //实例化人与学生
    var per=new Person(10)
    var stu=new Student(100)
    
    //复制一份人的实例对象,传入学生对象===>指向改变
    var f=per.go.bind(stu)//[object object]--100
    

    应用

    //一个构造函数
    function ShowRandom(){
        this.number=parseInt(Math.random()*10+1)
    }
    //给原型对象添加两个方法
    ShowRandom.prototype.show1=function{ 
        //定时器里边的this.show2里的this是window
        //此时我想改变指向,改成实例对象而不是wimdow,这里就要用到.bind(this),这个时候,show2指向的就是实例对象。即bind里面写的是哪个对象,bind前面的方法就指向谁
        window.setInterval(this.show2.bind(this),1000)
    }
    ShowRandom.prototype.show2=function{
        console.log(this.number)
    }
    
    //实例对象
    var sr=new ShowRandom()
    //调用
    sr.show2()
    

函数也是对象

函数也是对象,但对象不一定是函数

对象中有__proto__原型,是对象
构造函数中有prototype原型

//构造函数
function F1(){}
console.log(F1)//既有prototype,也有__proto__,即构造函数既是对象又是函数

//已知Math是内置对象
console.log(Math)//有__proto__但没有prototype,即对象不是函数

所有的函数实际上是Function的构造函数创建出来的实例对象

var f1=new Function()

数组中的函数调用

数组可以储存任何类型的数据

var arr=[
    function(){console.log("我是f1")},
    function(){console.log("我是f2")},
    function(){console.log("我是f3")}
]

//回调函数:函数作为参数使用
arr.foreach(function(item){
    item()
})
//我是f1 我是f2 我是f3

函数的成员

  • name——函数名称,只读

  • arguments——实参的个数

  • length——形参的个数

  • caller——函数调用者

    function f1(x,y){
        console.log(f1.name)//f1
        console.log(f1.arguments)//[10,20,...]
        console.log(f1.arguments.length)//3
        console.log(f1.length)//2
        console.log(f1.caller)//f2
    }
    function f2(){
    	f1(10,20,30)    
    }
    f2()
    

函数作为参数

//这是一个以函数为参数的函数
function f1(fn){
    fn()
}

//给函数从传入匿名函数
f1(function(){})
//给函数传入命名函数,只需传入名字
function f2(){我是一个要被传进去的命名函数}
f1(f2)

应用——定时器传入函数

function f1(fn){
    setInterval(function(){
        console.log("定时器开始")
        fn()
        console.log("定时器结束") 
    },1000)
}
//传入匿名函数
f1(function(){console.log("我在中间")})

//打印输出
定时器开始
我在中间
定时器结束

函数作为返回值

function f1(){
    console.log("我是f1")
    return function(){console.log("我来了")}
}
f1()
//此时只能打印出:我是f1

//已知f1返回了一个匿名函数,那么我把这个返回函数赋值给一个变量,这个变量就是这个返回的函数
var f=f1()
//调用这个返回的函数
f()//打印出:我来了

闭包

闭包的作用:缓存数据,延长作用域链

函数闭包:在函数中有一个函数

function f1(){
    num=10
    function f2(){
        console.log(num)
    }
    f2()
}
f1()

对象闭包:函数中有对象

function f1(){
    var num=10;
    var obj={
        age:num
    }
    console.log(obj.age)
}
1()

应用

function f1(){
    var num=10
    return function(){
        return num
    }  
}
//把返回的函数赋值给变量
var f=f1()
//把返回函数返回的值赋值给变量
var result=f()
console.log(result)//10
function f1(){
    var num=10;
    return function(){
        num++;
        return num
    }
}
var f=f1()
console.log(f())//11
console.log(f())//12
console.log(f())//13
//解析:在这个例子中,函数f1只调用了一次,返回函数的num是在第一次调用f1的基础上处理的,即在num=10的基础上。之后处理的是返回函数自身,不受外部干扰

递归

函数中调用函数自己

function getSum(x){
    if(x==1){
        return 1;
    }
    return x+getSum(x-1)
}

getSum(5)//15
//解析:当你传5的时候-->5+getSum(4)
//此时这个getSum(4)就是-->4+getSum(3)
//这个getSum(3)就是-->3+getSum(2)
//这个getSum(2)就是-->2+getSum(1)
//这个getSum(1)就是1
//总的就是:5+4+3+2+1

浅拷贝

相当于把一个对象中的所有内容,复制一份给另一个对象。即把一个对象的地址给了另一个对象,两者指向相同。

var obj1={
    name:"小明",
    dog:["小黑","小白"]
}
var obj2={}

//写一个函数,把a对象中的所有属性复制到b对象
function extend(a,b){
    for(var key in a){
        b[key]=a[key]
    }
}

//调用复制函数
extend(obj1,obj2)
consolo.log(obj1)//一样
console.log(obj2)//一样

深拷贝

把一个对象中的所有属性和方法,一个一个的找到并在另一个对象中开辟相应的地址,一个一个的储存到另一个对象中

var obj1={
    name:"小明",
    dog:["小黑","小白"]
    pen:{
        color:black;
        price:5
    }
}
var obj2={}

//写一个函数,把a对象中的所有属性复制到b对象
function extend(a,b){
    for(var key in a){
        //先把对象的属性的值放在item存储
        var item=a[key];
        //判断属性类型
        //如果是数组
        if(item instanceof Array){
           //开辟一个新数组(数组)
           b[key]=[]
            //再把存的数组拷贝到新的空间去
            exentd(item,b[key])
        //下面同理
        }else if(item instanceof Object){
            b[key]={}
            exentd(item,b[key])     
        }else{
         //如果值不是数组也不是对象,即普通的数据,就直接把值直接拷贝给另一个对象
          b[key]=item
        }
    }
}

//调用复制函数
extend(obj1,obj2)
consolo.log(obj1)//一样
console.log(obj2)//一样
//与浅拷贝不同的地方就是开辟了一个新地址

正则表达式

按照一定的规则组成一个表达式,这个表达式主要匹配字符串

元字符

  • .——表示除了\n以外的任意一个字符

  • []——表示范围

    • [0-9]——表示0到9之间的任意一个数字。

      //注意:如果想表示100-200不能直接写成[100,200]而是:
      [1][0-9][0-9]
      
    • [a-z]——表示所有小写字母中的任意一个

    • [A-Z]——表示所有大写字母中的任意一个

    • [a-zA-Z]——表示所有字母中的任意一个

    • [0-9a-zA-Z]——表示所有数字或字母中的任意一个

      []还可以表示把元字符中的意义取消,如[.]就只是一个.

  • |——或者

  • ()——分组 提升优先级

    //要么是数字、要么是小写字母、要么是大写字母。其中小写字母优先
    [0-9]|([a-z])|[A-Z]
    //分组 从左边开始计算
    ([0-9])([a-z])([A-Z])
    
  • *——表示前面的表达式出现了0次到多次

    [a-z][0-9]*
    "sgfja" //可以匹配,因为*包含了出现0次的情况
    
  • +——表示前面的表达式出现了1次到多次

    [a-z][9]+//表示小写字母后面最少有一个9
    
  • ?——表示前面的表达式出现了0次或1次

    [4][a-z]?//表示有4且4后面有1个小写字母或者没有小写字母
    "12e324jjk"//可以匹配
    
  • 限定符——限定前面表达式出现的次数

    • {0,}——表示前面的表达式出现了0次到多次,和*一样

    • {1,}——表示前面的表达式出现了1次到多次,和+一样

    • {0,1}——表示前面的表达式出现了1次到多次,和?一样

      可以更明确前面表达式出现的次数
      {5,10}//出现5到10次
      {4}//只能4次
      
  • ^——表示以什么开始,或者是取非

    ^[0-9]//以数字开头
    ^[a-z]//以小写字母开头
    [^0-9]//非数字
    [^0-9a-zA-Z]//特殊符号
    
  • $——表示以什么结束

    [0-9]$//以数字结束
    ^[a-z][0-9]$//严格模式:是能是一个字母一个数字比如"a1"
    
  • \d——数字中的任意一个,即[0-9]

  • \D——非数字中的一个

  • \s——空白符的一个

  • \S——非空白符的一个

  • \w——非特殊符号

  • \W——特殊符号

创建正则表达式对象

正则表达式要写在一对//中

  1. 通过构建函数创建对象

    var reg=new RegExp(/\d{5}/)
    
  2. 通过字面量的方式创建

    var reg=/\d{5}/
    

使用正则表达式

//创建对象,正则表达式要写在一对//里面
var reg=new RegExp(/\d{5}/)
//要检测的字符串
var str="我的电话是10086"
//调用正则表达式方法验证
reg.test(str)//返回的是布尔值

字符串中使用正则表达式

match()——匹配符合正则表达式的并返回该字符串

var str="中国移动10086;中国联通10010"
var array=str.match(/\d{5}/)//这样只能匹配一个
var array=str.match(/\d{5}/g)//全局匹配 ["10086","10010"]

replace()——替换符合正则表达式的字符

var str="小明睡懒觉了"
str=str.replace(/睡懒觉/,"起床")
console.log(str)//小明起床了

正则表达式的方法

  • test()——用来查看正则表达式与指定的字符串是否匹配。返回 truefalse

  • exec()——在一个指定字符串中执行一个搜索匹配。返回一个结果数组或null

    //一个字符串
    var str="中国移动10086;中国联通10010"
    //一个正则对象
    var reg=/\d{5}/
    //把exec返回的结果放在array变量中
    var array=reg.exec(str)
    

其他

  • g——表示全局模式匹配,在表达式后加

  • i——表示忽略大小写

  • RegExp.$n——获取匹配后的第n组值,一个()表示一组

OK完结撒花~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值