1. 程式人生 > 實用技巧 >Javascript-高階

Javascript-高階

原型基礎

引入

原型物件的作用

  • 資料共享,節省空間(需要共享的解除安裝原型物件中,不需要的寫在建構函式中)
  • 為了實現繼承

簡單瞭解原型物件

問題:用建構函式建立例項化物件時,如果例項化物件中有相同的屬性或方法,例項化多個物件會佔多個記憶體,浪費空間

解決:通過原型的方式,把共享的方法單獨提出來寫

//建構函式
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組值,一個()表示一組