成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 進(jìn)階知識(shí) - 高級(jí)篇

LiuRhoRamen / 2930人閱讀

摘要:汪汪汪哈士奇大黃狗輸出結(jié)果為這樣寫依然存在問題全局變量增多,會(huì)增加引入框架命名沖突的風(fēng)險(xiǎn)代碼結(jié)構(gòu)混亂,會(huì)變得難以維護(hù)想要解決上面的問題就需要用到構(gòu)造函數(shù)的原型概念

JS高級(jí) 前言
經(jīng)過前面幾篇文章的學(xué)習(xí),相信大家已經(jīng)對(duì)js有了大部分的理解了,但是要想真正的掌握好js,本篇才是關(guān)鍵。由于js高級(jí)階段的知識(shí)點(diǎn)比較難理解,所以本篇文章花了大量的時(shí)間去理思路,有可能有一些知識(shí)點(diǎn)遺漏了,也有可能有部分知識(shí)點(diǎn)寫的不對(duì),歡迎大家留言糾正。
1.異常處理

常見的異常分類

運(yùn)行環(huán)境的多樣性導(dǎo)致的異常(瀏覽器)

語法錯(cuò)誤,代碼錯(cuò)誤

異常最大的特征,就是一旦代碼出現(xiàn)異常,后面的代碼就不會(huì)執(zhí)行。

1.1異常捕獲
捕獲異常,使用try-catch語句:
try{
    // 這里寫可能出現(xiàn)異常的代碼
}catch(e){
    // e-捕獲的異常對(duì)象
    // 可以在此處書寫出現(xiàn)異常后的處理代碼
}
異常捕獲語句執(zhí)行的過程為:

代碼正常運(yùn)行, 如果在try中出現(xiàn)了錯(cuò)誤,try里面出現(xiàn)錯(cuò)誤的語句后面的代碼都不再執(zhí)行, 直接跳轉(zhuǎn)到catch

catch中處理錯(cuò)誤信息

然后繼續(xù)執(zhí)行后面的代碼

如果try中沒有出現(xiàn)錯(cuò)誤, 那么不走catch直接執(zhí)行后面的代碼

通過try-catch語句進(jìn)行異常捕獲之后,代碼將會(huì)繼續(xù)執(zhí)行,而不會(huì)中斷。

示例代碼:

console.log("代碼開始執(zhí)行");
try{
    console.log(num); // num 在外部是沒有定義的
}catch(e){
    console.log(e);
    console.log("我已經(jīng)把錯(cuò)誤處理了");
}
console.log("代碼結(jié)束執(zhí)行");

效果圖:

從效果圖中我們可以看到,num是一個(gè)沒有定義的變量,如果沒有放在try-catch代碼塊中,后面的‘代碼結(jié)束執(zhí)行’就不會(huì)被打印。通過把try-catch放在代碼塊中,出現(xiàn)錯(cuò)誤后,就不會(huì)影響后面代碼的運(yùn)行了,他會(huì)把錯(cuò)誤信息打印出來。

注意:

語法錯(cuò)誤異常用try-catch語句無法捕獲,因?yàn)樵陬A(yù)解析階段,語法錯(cuò)誤會(huì)直接檢測出來,而不會(huì)等到運(yùn)行的時(shí)候才報(bào)錯(cuò)。

try-catch在一般日常開發(fā)中基本用不到,但是如果要寫框架什么的,用的會(huì)非常多。因?yàn)檫@個(gè)會(huì)讓框架變得健壯

異常捕獲語句的完整模式

異常捕獲語句的完整模式為try-catch-finally
try {
    //可能出現(xiàn)錯(cuò)誤的代碼
} catch ( e ) {
    //如果出現(xiàn)錯(cuò)誤就執(zhí)行
} finally {
    //結(jié)束 try 這個(gè)代碼塊之前執(zhí)行, 即最后執(zhí)行
}

finally中的代碼,不管有沒有發(fā)生異常,都會(huì)執(zhí)行。一般用在后端語言中,用來釋放資源,JavaScript中很少會(huì)用到

1.2拋出異常

如何手動(dòng)的拋出異常呢?

案例:自己寫的一個(gè)函數(shù),需要一個(gè)參數(shù),如果用戶不傳參數(shù),此時(shí)想直接給用戶拋出異常,就需要了解如何拋出異常。

拋出異常使用throw關(guān)鍵字,語法如下:

throw 異常對(duì)象;

異常對(duì)象一般是用new Error("異常消息"), 也可以使用任意對(duì)象

示例代碼:

function test(para){
    if(para == undefined){
        throw new Error("請(qǐng)傳遞參數(shù)");
        //這里也可以使用自定義的對(duì)象
        throw {"id":1, msg:"參數(shù)未傳遞"};
    }
}

try{
    test();
}catch(e){
    console.log(e);
}

效果圖:

1.3異常的傳遞機(jī)制
function f1 () {
    f2(); 
}

function f2 () {
    f3();
}

function f3() {
    throw new Error( "error" );
}
f1();  // f1 稱為調(diào)用者, 或主調(diào)函數(shù), f2 稱為被調(diào)用者, 或被調(diào)函數(shù)

當(dāng)在被調(diào)函數(shù)內(nèi)發(fā)生異常的時(shí)候,異常會(huì)一級(jí)一級(jí)往上拋出。

2.面向?qū)ο缶幊?/b>
在了解面向?qū)ο缶幊讨?,我們先來了解下什么是面向過程,什么是面向?qū)ο?,他們之間的區(qū)別是什么。
2.1 面向過程和面向?qū)ο蟮牡膶?duì)比

舉個(gè)例子:

日常洗衣服

1.面向過程的思維方式:

面向過程編程:將解決問題的關(guān)注點(diǎn)放在解決問題的具體細(xì)節(jié)上,關(guān)注如何一步一步實(shí)現(xiàn)代碼細(xì)節(jié);
step 1:收拾臟衣服
step 2:打開洗衣機(jī)蓋
step 3:將臟衣服放進(jìn)去
step 4:設(shè)定洗衣程序
step 5:開始洗衣服
step 6:打開洗衣機(jī)蓋子
step 7:曬衣服

2.面向?qū)ο蟮乃季S方式:

面向?qū)ο缶幊蹋簩⒔鉀Q問題的關(guān)注點(diǎn)放在解決問題所需的對(duì)象上,我們重點(diǎn)找對(duì)象;
人(對(duì)象)
洗衣機(jī)(對(duì)象)

在面向?qū)ο蟮乃季S方式中:我們只關(guān)心要完成事情需要的對(duì)象,面向?qū)ο笃鋵?shí)就是對(duì)面向過程的封裝;

示例代碼:

在頁面上動(dòng)態(tài)創(chuàng)建一個(gè)元素
//面向過程
//1-創(chuàng)建一個(gè)div
var  div=document.createElement("div");
//2-div設(shè)置內(nèi)容
div.innerHTML="我是div";
//3-添加到頁面中
document.body.appendChild(div);

//面向?qū)ο?$("body").append("
我也是div
");

我們可以看出,jQ封裝的其實(shí)就是對(duì)面向過程的封裝。

總結(jié): 面向?qū)ο笫且环N解決問題的思路,一種編程思想。

2.2 面向?qū)ο缶幊膛e例
設(shè)置頁面中的divp的邊框?yàn)?b>"1px solid red"

1、傳統(tǒng)的處理辦法

// 1> 獲取div標(biāo)簽
var divs = document.getElementsByTagName( "div" );
// 2> 遍歷獲取到的div標(biāo)簽
for(var i = 0; i < divs.length; i++) {
    //3> 獲取到每一個(gè)div元素,設(shè)置div的樣式
    divs[i].style.border = "1px dotted black";
}

// 4> 獲取p標(biāo)簽
var ps = document.getElementsByTagName("p");
// 5> 遍歷獲取到的p標(biāo)簽
for(var j = 0; j < ps.length; j++) { 
    // 獲取到每一個(gè)p元素 設(shè)置p標(biāo)簽的樣式
    ps[j].style.border = "1px dotted black"; 
}

2、使用函數(shù)進(jìn)行封裝優(yōu)化

// 通過標(biāo)簽名字來獲取頁面中的元素 
function tag(tagName) { 
    return document.getElementsByTagName(tagName); 
}

// 封裝一個(gè)設(shè)置樣式的函數(shù) 
function setStyle(arr) { 
    for(var i = 0; i < arr.length; i++) { 
        // 獲取到每一個(gè)div或者p元素 
        arr[i].style.border = "1px solid #abc"; 
    } 
}
var dvs = tag("div");
var ps = tag("p");
setStyle(dvs); 
setStyle(ps);

3、使用面向?qū)ο蟮姆绞?/strong>

// 更好的做法:是將功能相近的代碼放到一起 
var obj = {     // 命名空間
    getEle: { 
        tag: function (tagName) { 
            return document.getElementsByTagName(tagName); 
        }, 
        id: function (idName) { 
            return document.getElementById(idName); 
        } 
        // ...
    },    
    setCss: { 
        setStyle: function (arr) { 
            for(var i = 0; i < arr.length; i++) { 
                arr[i].style.border = "1px solid #abc"; 
            } 
        }, 
        css: function() {}, 
        addClass: function() {}, 
        removeClass: function() {} 
        // ... 
    } 
    // 屬性操作模塊 
    // 動(dòng)畫模塊 
    // 事件模塊 
    // ... 
};

var divs = obj.getEle.tag("div");
obj.setCss.setStyle(divs);
2.3 面向?qū)ο蟮娜筇匦?/b>
面向?qū)ο蟮娜筇匦苑謩e是:"封裝","繼承""多態(tài)"。

1、封裝性

對(duì)象就是對(duì)屬性和方法的封裝,要實(shí)現(xiàn)一個(gè)功能,對(duì)外暴露一些接口,調(diào)用者只需通過接口調(diào)用即可,不需要關(guān)注接口內(nèi)部實(shí)現(xiàn)原理。

js對(duì)象就是“鍵值對(duì)”的集合

鍵值如果是數(shù)據(jù)( 基本數(shù)據(jù), 復(fù)合數(shù)據(jù), 空數(shù)據(jù) ), 就稱為屬性

如果鍵值是函數(shù), 那么就稱為方法

對(duì)象就是將屬性與方法封裝起來

方法是將過程封裝起來

2、繼承性

所謂繼承就是自己沒有, 別人有,拿過來為自己所用, 并成為自己的東西

2.1、傳統(tǒng)繼承基于模板

子類可以使用從父類繼承的屬性和方法。

class Person {
 string name;
 int age;
}

class Student : Person {
}
var stu = new Student();
stu.name

即:讓某個(gè)類型的對(duì)象獲得另一個(gè)類型的對(duì)象的屬性的方法

2.2、js 繼承基于對(duì)象

JavaScript中,繼承就是當(dāng)前對(duì)象可以使用其他對(duì)象的方法和屬性。

js繼承實(shí)現(xiàn)舉例:混入(mix

// 參數(shù)o1和o2是兩個(gè)對(duì)象,其中o1對(duì)象繼承了所有o2對(duì)象的“k”屬性或者方法
var o1 = {};
var o2 = {
    name: "Levi",
    age: 18,
    gender: "male"
};
function mix ( o1, o2 ) {
    for ( var k in o2 ) {
        o1[ k ] = o2[ k ];
    }
}
mix(o1, o2);
console.log(o1.name); // "Levi"

3、多態(tài)性(基于強(qiáng)類型,js中沒有多態(tài))只做了解

同一個(gè)類型的變量可以表現(xiàn)出不同形態(tài),用父類的變量指向子類的對(duì)象。
動(dòng)物 animal = new 子類(); // 子類:麻雀、狗、貓、豬、狐貍...
動(dòng)物 animal = new 狗();
animal.叫();
2.4 創(chuàng)建對(duì)象的方式

1、字面量 {}

var student1 = {
    name:"諸葛亮",
    score:100,
    code:1,
}

var student2 = {
    name:"蔡文姬",
    score:98,
    code:2,
}

var student3 = {
    name:"張飛",
    score:68,
    code:3,
}

字面量創(chuàng)建方式,代碼復(fù)用性太低,每一次都需要重新創(chuàng)建一個(gè)對(duì)象。

2、Object()構(gòu)造函數(shù)

var student1 = new Object();
    student1.name = "諸葛亮";
    student1.score = 100;
    student1.code = 1;

var student2 = new Object();
    student2.name = "蔡文姬";
    student2.score = 98;
    student2.code = 2;
    
var student3 = new Object();
    student3.name = "張飛";
    student3.score = 68;
    student3.code = 3;

代碼復(fù)用性太低,字面量創(chuàng)建的方式其實(shí)就是代替Object()構(gòu)造函數(shù)創(chuàng)建方式的。

3、自定義構(gòu)造函數(shù)

自定義構(gòu)造函數(shù),可以快速創(chuàng)建多個(gè)對(duì)象,并且代碼復(fù)用性高。
// 一般為了區(qū)分構(gòu)造函數(shù)與普通函數(shù),構(gòu)造函數(shù)名首字母大寫
function Student(name,score,code){  
    this.name = name;
    this.score = score;
    this.code = code;
}

var stu1 = new Student("諸葛亮",100,1);
var stu2 = new Student("蔡文姬",98,2);
var stu3 = new Student("張飛",68,3);

構(gòu)造函數(shù)語法:

構(gòu)造函數(shù)名首字母大寫;

構(gòu)造函數(shù)一般與關(guān)鍵字:new一起使用;

構(gòu)造函數(shù)一般不需要設(shè)置return語句,默認(rèn)返回的是新創(chuàng)建的對(duì)象;

this指向的是新創(chuàng)建的對(duì)象。

構(gòu)造函數(shù)的執(zhí)行過程:

new關(guān)鍵字,創(chuàng)建一個(gè)新的對(duì)象,會(huì)在內(nèi)存中開辟一個(gè)新的儲(chǔ)存空間;

讓構(gòu)造函數(shù)中的this指向新創(chuàng)建的對(duì)象;

執(zhí)行構(gòu)造函數(shù),給新創(chuàng)建的對(duì)象進(jìn)行初始化(賦值);

構(gòu)造函數(shù)執(zhí)行(初始化)完成,會(huì)將新創(chuàng)建的對(duì)象返回。

構(gòu)造函數(shù)的注意點(diǎn):

構(gòu)造函數(shù)本身也是函數(shù);

構(gòu)造函數(shù)有返回值,默認(rèn)返回的是新創(chuàng)建的對(duì)象;

但是如果手動(dòng)添加返回值,添加的是值類型數(shù)據(jù)的時(shí)候,構(gòu)造函數(shù)沒有影響。如果添加的是引用類型(數(shù)組、對(duì)象等)值的時(shí)候,會(huì)替換掉新創(chuàng)建的對(duì)象。

function Dog(){
    this.name="哈士奇";
    this.age=0.5;
    this.watch=function(){
        console.log("汪汪汪,禁止入內(nèi)");
    }
    // return false;          返回值不會(huì)改變,還是新創(chuàng)建的對(duì)象
    // return 123;            返回值不會(huì)改變,還是新創(chuàng)建的對(duì)象
    // return [1,2,3,4,5];    返回值發(fā)生改變,返回的是這個(gè)數(shù)組
    return  {aaa:"bbbb"};  // 返回值發(fā)生改變,返回的是這個(gè)對(duì)象
}

var d1=new Dog();  // 新創(chuàng)建一個(gè)對(duì)象
console.log(d1);

構(gòu)造函數(shù)可以當(dāng)做普通函數(shù)執(zhí)行,里面的this指向的是全局對(duì)象window。

 function Dog(){
    this.name="husky";
    this.age=0.5;
    this.watch=function(){
        console.log("汪汪汪,禁止入內(nèi)");
    }
    console.log(this);  // window對(duì)象
    return 1;
}
console.log(Dog());  // 打印 1
2.5 面向?qū)ο蟀咐?/b>
通過一個(gè)案例,我們來了解下面向?qū)ο缶幊蹋ò咐杏幸粋€(gè)prototype概念,可以學(xué)完原型那一章后再來看這個(gè)案例)。

需求:

實(shí)現(xiàn)一個(gè)MP3音樂管理案例;

同種類型的MP3,廠家會(huì)生產(chǎn)出成百上千個(gè),但是每個(gè)MP3都有各自的樣式、使用者、歌曲;

每個(gè)MP3都有一樣的播放、暫停、增刪歌曲的功能(方法);

圖解:

示例代碼:

    // 每個(gè)MP3都有自己的 主人:owner 樣式:color 歌曲:list
    function MP3(name,color,list){
        this.owner = name || "Levi";  // 不傳值時(shí)默認(rèn)使用者是‘Levi’
        this.color = color || "pink";
        this.musicList = list || [
            {songName:"男人哭吧不是罪",singer:"劉德華"},
            {songName:"吻別",singer:"張學(xué)友"},
            {songName:"對(duì)你愛不完",singer:"郭富城"},
            {songName:"今夜你會(huì)不會(huì)來",singer:"黎明"}
        ];
    }
    // 所有的MP3都有 播放 暫停 音樂 增刪改查的功能
    MP3.prototype = {
        // 新增
        add:function(songName,singer){
            this.musicList.push({songName:songName,singer:singer});
        },
        // 查找
        select:function(songName){
            for(var i=0;i

打印結(jié)果:

3.原型 3.1 傳統(tǒng)構(gòu)造函數(shù)存在問題

通過自定義構(gòu)造函數(shù)的方式,創(chuàng)建小狗對(duì)象:

兩個(gè)實(shí)例化出來的“小狗”,它們都用的同一個(gè)say方法,為什么最后是false呢?
function Dog(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() {
        console.log("汪汪汪");
    }
}
var dog1 = new Dog("哈士奇", 1.5);
var dog2 = new Dog("大黃狗", 0.5);

console.log(dog1);
console.log(dog2);

console.log(dog1.say == dog2.say); //輸出結(jié)果為false

畫個(gè)圖理解下:

每次創(chuàng)建一個(gè)對(duì)象的時(shí)候,都會(huì)開辟一個(gè)新的空間,我們從上圖可以看出,每只創(chuàng)建的小狗有一個(gè)say方法,這個(gè)方法都是獨(dú)立的,但是功能完全相同。隨著創(chuàng)建小狗的數(shù)量增多,造成內(nèi)存的浪費(fèi)就更多,這就是我們需要解決的問題。

為了避免內(nèi)存的浪費(fèi),我們想要的其實(shí)是下圖的效果:

解決方法:

這里最好的辦法就是將函數(shù)體放在構(gòu)造函數(shù)之外,在構(gòu)造函數(shù)中只需要引用該函數(shù)即可。
function sayFn() {
    console.log("汪汪汪");
}

function Dog(name, age) {
    this.name = name;
    this.age = age;
    this.say = sayFn();
}
var dog1 = new Dog("哈士奇", 1.5);
var dog2 = new Dog("大黃狗", 0.5);

console.log(dog1);
console.log(dog2);

console.log(dog1.say == dog2.say); //輸出結(jié)果為 true

這樣寫依然存在問題:

全局變量增多,會(huì)增加引入框架命名沖突的風(fēng)險(xiǎn)

代碼結(jié)構(gòu)混亂,會(huì)變得難以維護(hù)

想要解決上面的問題就需要用到構(gòu)造函數(shù)的原型概念。

3.2 原型的概念
prototype:原型。每個(gè)構(gòu)造函數(shù)在創(chuàng)建出來的時(shí)候系統(tǒng)會(huì)自動(dòng)給這個(gè)構(gòu)造函數(shù)創(chuàng)建并且關(guān)聯(lián)一個(gè)空的對(duì)象。這個(gè)空的對(duì)象,就叫做原型。

關(guān)鍵點(diǎn):

每一個(gè)由構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象,都會(huì)默認(rèn)的和構(gòu)造函數(shù)的原型關(guān)聯(lián);

當(dāng)使用一個(gè)方法進(jìn)行屬性或者方法訪問的時(shí)候,會(huì)先在當(dāng)前對(duì)象內(nèi)查找該屬性和方法,如果當(dāng)前對(duì)象內(nèi)未找到,就會(huì)去跟它關(guān)聯(lián)的原型對(duì)象內(nèi)進(jìn)行查找;

也就是說,在原型中定義的方法跟屬性,會(huì)被這個(gè)構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象所共享;

訪問原型的方式:構(gòu)造函數(shù)名.prototype。

示例圖:

示例代碼: 給構(gòu)造函數(shù)的原型添加方法

function Dog(name,age){
    this.name = name;
    this.age = age;
}

// 給構(gòu)造函數(shù)的原型 添加say方法
Dog.prototype.say = function(){
    console.log("汪汪汪");
}

var dog1 = new Dog("哈士奇", 1.5);
var dog2 = new Dog("大黃狗", 0.5);

dog1.say();  // 汪汪汪
dog2.say();  // 汪汪汪

我們可以看到,本身Dog這個(gè)構(gòu)造函數(shù)中是沒有say這個(gè)方法的,我們通過Dog.prototype.say的方式,在構(gòu)造函數(shù)Dog的原型中創(chuàng)建了一個(gè)方法,實(shí)例化出來的dog1、dog2會(huì)先在自己的對(duì)象先找say方法,找不到的時(shí)候,會(huì)去他們的原型對(duì)象中查找。

如圖所示:

在構(gòu)造函數(shù)的原型中可以存放所有對(duì)象共享的數(shù)據(jù),這樣可以避免多次創(chuàng)建對(duì)象浪費(fèi)內(nèi)存空間的問題。

3.3 原型的使用

1、使用對(duì)象的動(dòng)態(tài)特性

使用對(duì)象的動(dòng)態(tài)屬性,其實(shí)就是直接使用prototype為原型添加屬性或者方法。
function Person () {}

Person.prototype.say = function () {
    console.log( "講了一句話" );
};

Person.prototype.age = 18;

var p = new Person();
p.say();  // 講了一句話
console.log(p.age);  // 18

2、直接替換原型對(duì)象

每次構(gòu)造函數(shù)創(chuàng)建出來的時(shí)候,都會(huì)關(guān)聯(lián)一個(gè)空對(duì)象,我們可以用一個(gè)對(duì)象替換掉這個(gè)空對(duì)象。
function Person () {}

Person.prototype = {
    say : function () {
        console.log( "講了一句話" );
    },
};

var p = new Person();
p.say();  // 講了一句話

注意:

使用原型的時(shí)候,有幾個(gè)注意點(diǎn)需要注意一下,我們通過幾個(gè)案例來了解一下。

使用對(duì)象.屬性名去獲取對(duì)象屬性的時(shí)候,會(huì)先在自身中進(jìn)行查找,如果沒有,就去原型中查找;

// 創(chuàng)建一個(gè)英雄的構(gòu)造函數(shù) 它有自己的 name 和 age 屬性
function Hero(){
    this.name="德瑪西亞之力";
    this.age=18;
}
// 給這個(gè)構(gòu)造函數(shù)的原型對(duì)象添加方法和屬性
Hero.prototype.age= 30;
Hero.prototype.say=function(){
    console.log("人在塔在?。。?);
}

var h1 = new Hero();
h1.say();   // 先去自身中找 say 方法,沒有再去原型中查找  打?。?人在塔在!?。?
console.log(p1.name);  // "德瑪西亞之力"
console.log(p1.age);   // 18 先去自身中找 age 屬性,有的話就不去原型中找了

使用對(duì)象.屬性名去設(shè)置對(duì)象屬性的時(shí)候,只會(huì)在自身進(jìn)行查找,如果有,就修改,如果沒有,就添加;

// 創(chuàng)建一個(gè)英雄的構(gòu)造函數(shù)
function Hero(){
    this.name="德瑪西亞之力";
}
// 給這個(gè)構(gòu)造函數(shù)的原型對(duì)象添加方法和屬性
Hero.prototype.age = 18;

var h1 = new Hero();
console.log(h1);       // {name:"德瑪西亞之力"}
console.log(h1.age);   // 18

h1.age = 30;           // 設(shè)置的時(shí)候只會(huì)在自身中操作,如果有,就修改,如果沒有,就添加 不會(huì)去原型中操作
console.log(h1);       // {name:"德瑪西亞之力",age:30}
console.log(h1.age);   // 30

一般情況下,不會(huì)將屬性放在原型中,只會(huì)將方法放在原型中;

在替換原型的時(shí)候,替換之前創(chuàng)建的對(duì)象,和替換之后創(chuàng)建的對(duì)象的原型不一致?。?!

// 創(chuàng)建一個(gè)英雄的構(gòu)造函數(shù) 它有自己的 name 屬性
function Hero(){
    this.name="德瑪西亞之力";
}
// 給這個(gè)構(gòu)造函數(shù)的默認(rèn)原型對(duì)象添加 say 方法
Hero.prototype.say = function(){
    console.log("人在塔在?。。?);
}

var h1 = new Hero();
console.log(h1);    // {name:"德瑪西亞之力"}
h1.say();           // "人在塔在?。?!"

// 開辟一個(gè)命名空間 obj,里面有個(gè) kill 方法
var obj = {
    kill : function(){
        console.log("大寶劍");
    }
}

// 將創(chuàng)建的 obj 對(duì)象替換原本的原型對(duì)象
Hero.prototype = obj;

var h2 = new Hero();

h1.say();           // "人在塔在?。?!"
h2.say();           // 報(bào)錯(cuò)

h1.kill();          // 報(bào)錯(cuò)
h2.kill();          // "大寶劍"

畫個(gè)圖理解下:

圖中可以看出,實(shí)例出來的h1對(duì)象指向的原型中,只有say()方法,并沒有kill()方法,所以h1.kill()會(huì)報(bào)錯(cuò)。同理,h2.say()也會(huì)報(bào)錯(cuò)。

3.4 __proto__屬性
js中以_開頭的屬性名為js的私有屬性,以__開頭的屬性名為非標(biāo)準(zhǔn)屬性。__proto__是一個(gè)非標(biāo)準(zhǔn)屬性,最早由firefox提出來。

1、構(gòu)造函數(shù)的 prototype 屬性

之前我們?cè)L問構(gòu)造函數(shù)原型對(duì)象的時(shí)候,使用的是prototype屬性:
function Person(){}

//通過構(gòu)造函數(shù)的原型屬性prototype可以直接訪問原型
Person.prototype;
在之前我們是無法通過構(gòu)造函數(shù)new出來的對(duì)象訪問原型的:
function Person(){}

var p = new Person();

//以前不能直接通過p來訪問原型對(duì)象

2、實(shí)例對(duì)象的 __proto__ 屬性

__proto__屬性最早是火狐瀏覽器引入的,用以通過實(shí)例對(duì)象來訪問原型,這個(gè)屬性在早期是非標(biāo)準(zhǔn)的屬性,有了__proto__屬性,就可以通過構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象直接訪問原型。
function Person(){}

var p = new Person();

//實(shí)例對(duì)象的__proto__屬性可以方便的訪問到原型對(duì)象
p.__proto__;

//既然使用構(gòu)造函數(shù)的`prototype`和實(shí)例對(duì)象的`__proto__`屬性都可以訪問原型對(duì)象
//就有如下結(jié)論
p.__proto__ === Person.prototype;

如圖所示:

3、__proto__屬性的用途

可以用來訪問原型;

在實(shí)際開發(fā)中除非有特殊的需求,不要輕易的使用實(shí)例對(duì)象的__proto__屬性去修改原型的屬性或方法;

在調(diào)試過程中,可以輕易的查看原型的成員;

由于兼容性問題,不推薦使用。

3.5 constuctor屬性
constructor:構(gòu)造函數(shù),原型的constructor屬性指向的是和原型關(guān)聯(lián)的構(gòu)造函數(shù)。

示例代碼:

function Dog(){
    this.name="husky";
}

var d=new Dog();

// 獲取構(gòu)造函數(shù)
console.log(Dog.prototype.constructor);  // 打印構(gòu)造函數(shù) Dog
console.log(d.__proto__.constructor);    // 打印構(gòu)造函數(shù) Dog

如圖所示:

獲取復(fù)雜類型的數(shù)據(jù)類型:

通過obj.constructor.name的方式,獲取當(dāng)前對(duì)象obj的數(shù)據(jù)類型。

在一個(gè)的函數(shù)中,有個(gè)返回值name,它表示的是當(dāng)前函數(shù)的函數(shù)名;

function Teacher(name,age){
    this.name = name;
    this.age = age;
}

var teacher = new Teacher();

// 假使我們只知道一個(gè)對(duì)象teacher,如何獲取它的類型呢?
console.log(teacher.__proto__.constructor.name);  // Teacher

console.log(teacher.constructor.name);  // Teacher

實(shí)例化出來的teacher對(duì)象,它的數(shù)據(jù)類型是啥呢?我們可以通過實(shí)例對(duì)象teacher.__proto__,訪問到它的原型對(duì)象,再通過.constructor訪問它的構(gòu)造函數(shù),通過.name獲取當(dāng)前函數(shù)的函數(shù)名,所以就能得到當(dāng)前對(duì)象的數(shù)據(jù)類型。又因?yàn)?b>.__proto__是一個(gè)非標(biāo)準(zhǔn)的屬性,而且實(shí)例出的對(duì)象繼承原型對(duì)象的方法,所以直接可以寫成:obj.constructor.name。

3.6 原型繼承
原型繼承:每一個(gè)構(gòu)造函數(shù)都有prototype原型屬性,通過構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象都繼承自該原型屬性。所以可以通過更改構(gòu)造函數(shù)的原型屬性來實(shí)現(xiàn)繼承。

繼承的方式有多種,可以一個(gè)對(duì)象繼承另一個(gè)對(duì)象,也可以通過原型繼承的方式進(jìn)行繼承。

1、簡單混入繼承

直接遍歷一個(gè)對(duì)象,將所有的屬性和方法加到另一對(duì)象上。
var animal = {
    name:"Animal",
    sex:"male",
    age:5,
    bark:function(){
        console.log("Animal bark");
    }
};

var dog = {};

for (var k in animal){
    dog[k]= animal[k];
}

console.log(dog);  // 打印的對(duì)象與animal一模一樣

缺點(diǎn):只能一個(gè)對(duì)象繼承自另一個(gè)對(duì)象,代碼復(fù)用太低了。

2、混入式原型繼承

混入式原型繼承其實(shí)與上面的方法類似,只不過是將遍歷的對(duì)象添加到構(gòu)造函數(shù)的原型上。
var obj={
     name:"zs",
     age:19,
     sex:"male"
 }

function Person(){
    this.weight=50;
}

for(var k in obj){
    // 將obj里面的所有屬性添加到 構(gòu)造函數(shù) Person 的原型中
    Person.prototype[k] = obj[k];
}

var p1=new Person();
var p2=new Person();
var p3=new Person();

console.log(p1.name);  // "zs"
console.log(p2.age);   // 19
console.log(p3.sex);   // "male"

面向?qū)ο笏枷敕庋b一個(gè)原型繼承

我們可以利用面向?qū)ο蟮乃枷?,將面向過程進(jìn)行封裝。
function Dog(){
    this.type = "yellow Dog";
}

// 給構(gòu)造函數(shù) Dog 添加一個(gè)方法 extend
Dog.prototype.extend = function(obj){
    // 使用混入式原型繼承,給 Dog 構(gòu)造函數(shù)的原型繼承 obj 的屬性和方法
     for (var k in obj){
        this[k]=obj[k];
    }
}

// 調(diào)用 extend 方法
Dog.prototype.extend({
    name:"二哈",
    age:"1.5",
    sex:"公",
    bark:function(){
        console.log("汪汪汪");
    }
});

3、替換式原型繼承

替換式原型繼承,在上面已經(jīng)舉過例子了,其實(shí)就是將一個(gè)構(gòu)造函數(shù)的原型對(duì)象替換成另一個(gè)對(duì)象。
function Person(){
    this.weight=50;
}

var obj={
    name:"zs",
    age:19,
    sex:"male"
}
// 將一個(gè)構(gòu)造函數(shù)的原型對(duì)象替換成另一個(gè)對(duì)象
Person.prototype = obj;

var p1=new Person();
var p2=new Person();
var p3=new Person();

console.log(p1.name);  // "zs"
console.log(p2.age);   // 19
console.log(p3.sex);   // "male"

之前我們就說過,這樣做會(huì)產(chǎn)生一個(gè)問題,就是替換的對(duì)象會(huì)重新開辟一個(gè)新的空間。

替換式原型繼承時(shí)的bug

替換原型對(duì)象的方式會(huì)導(dǎo)致原型的constructor的丟失,constructor屬性是默認(rèn)原型對(duì)象指向構(gòu)造函數(shù)的,就算是替換了默認(rèn)原型對(duì)象,這個(gè)屬性依舊是默認(rèn)原型對(duì)象指向構(gòu)造函數(shù)的,所以新的原型對(duì)象是沒有這個(gè)屬性的。

解決方法:手動(dòng)關(guān)聯(lián)一個(gè)constructor屬性

function Person() {
    this.weight = 50;
}

var obj = {
    name: "zs",
    age: 19,
    sex: "male"
}
// 在替換原型對(duì)象函數(shù)之前 給需要替換的對(duì)象添加一個(gè) constructor 屬性 指向原本的構(gòu)造函數(shù)
obj.constructor = Person;

// 將一個(gè)構(gòu)造函數(shù)的原型對(duì)象替換成另一個(gè)對(duì)象
Person.prototype = obj;

var p1 = new Person();

console.log(p1.__proto__.constructor === Person);  // true

4、Object.create()方法實(shí)現(xiàn)原型繼承

當(dāng)我們想把對(duì)象1作為對(duì)象2的原型的時(shí)候,就可以實(shí)現(xiàn)對(duì)象2繼承對(duì)象1。前面我們了解了一個(gè)屬性:__proto__,實(shí)例出來的對(duì)象可以通過這個(gè)屬性訪問到它的原型,但是這個(gè)屬性只適合開發(fā)調(diào)試時(shí)使用,并不能直接去替換原型對(duì)象。所以這里介紹一個(gè)新的方法:Object.create()。

語法: var obj1 = Object.create(原型對(duì)象);

示例代碼: 讓空對(duì)象obj1繼承對(duì)象obj的屬性和方法

var obj = {
    name : "蓋倫",
    age : 25,
    skill : function(){
        console.log("大寶劍");
    }
}

// 這個(gè)方法會(huì)幫我們創(chuàng)建一個(gè)原型是 obj 的對(duì)象
var obj1 = Object.create(obj);

console.log(obj1.name);     // "蓋倫"
obj1.skill();               // "大寶劍"

兼容性:

由于這個(gè)屬性是ECMAScript5的時(shí)候提出來的,所以存在兼容性問題。

利用瀏覽器的能力檢測,如果存在Object.create則使用,如果不存在的話,就創(chuàng)建構(gòu)造函數(shù)來實(shí)現(xiàn)原型繼承。

// 封裝一個(gè)能力檢測函數(shù)
function create(obj){
    // 判斷,如果瀏覽器有 Object.create 方法的時(shí)候
    if(Object.create){
        return Object.create(obj);
    }else{
        // 創(chuàng)建構(gòu)造函數(shù) Fun
        function Fun(){};
        Fun.prototype = obj; 
        return new Fun();
    }
}

var hero = {
    name: "蓋倫",
    age: 25,
    skill: function () {
        console.log("大寶劍");
    }
}

var hero1 = create(hero);
console.log(hero1.name);    // "蓋倫"
console.log(hero1.__proto__ == hero);   // true
4.原型鏈
對(duì)象有原型,原型本身又是一個(gè)對(duì)象,所以原型也有原型,這樣就會(huì)形成一個(gè)鏈?zhǔn)浇Y(jié)構(gòu)的原型鏈。
4.1 什么是原型鏈

示例代碼: 原型繼承練習(xí)

// 創(chuàng)建一個(gè) Animal 構(gòu)造函數(shù)
function Animal() {
    this.weight = 50;
    this.eat = function() {
        console.log("蜂蜜蜂蜜");
    }
}

// 實(shí)例化一個(gè) animal 對(duì)象
var animal = new Animal();

// 創(chuàng)建一個(gè) Preson 構(gòu)造函數(shù)
function Person() {
    this.name = "zs";
    this.tool = function() {
        console.log("菜刀");
    }
}

// 讓 Person 繼承 animal (替換原型對(duì)象)
Person.prototype = animal;

// 實(shí)例化一個(gè) p 對(duì)象 
var p = new Person();

// 創(chuàng)建一個(gè) Student 構(gòu)造函數(shù)
function Student() {
    this.score = 100;
    this.clickCode = function() {
        console.log("啪啪啪");
    }
}

// 讓 Student 繼承 p (替換原型對(duì)象)
Student.prototype = p;

//實(shí)例化一個(gè) student 對(duì)象
var student = new Student();


console.log(student);           // 打印 {score:100,clickCode:fn}

// 因?yàn)槭且患?jí)級(jí)繼承下來的 所以最上層的 Animate 里的屬性也是被繼承的
console.log(student.weight);    // 50
student.eat();         // 蜂蜜蜂蜜
student.tool();        // 菜刀

如圖所示:

我們將上面的案例通過畫圖的方式展現(xiàn)出來后就一目了然了,實(shí)例對(duì)象animal直接替換了構(gòu)造函數(shù)Person的原型,以此類推,這樣就會(huì)形成一個(gè)鏈?zhǔn)浇Y(jié)構(gòu)的原型鏈。

完整的原型鏈

結(jié)合上圖,我們發(fā)現(xiàn),最初的構(gòu)造函數(shù)Animal創(chuàng)建的同時(shí),會(huì)創(chuàng)建出一個(gè)原型,此時(shí)的原型是一個(gè)空的對(duì)象。結(jié)合原型鏈的概念:“原型本身又是一個(gè)對(duì)象,所以原型也有原型”,那么這個(gè)空對(duì)象往上還能找出它的原型或者構(gòu)造函數(shù)嗎?

我們?nèi)绾蝿?chuàng)建一個(gè)空對(duì)象? 1、字面量:{};2、構(gòu)造函數(shù):new Object()。我們可以簡單的理解為,這個(gè)空的對(duì)象就是,構(gòu)造函數(shù)Object的實(shí)例對(duì)象。所以,這個(gè)空對(duì)象往上面找是能找到它的原型和構(gòu)造函數(shù)的。

// 創(chuàng)建一個(gè) Animal 構(gòu)造函數(shù)
function Animal() {
    this.weight = 50;
    this.eat = function() {
        console.log("蜂蜜蜂蜜");
    }
}

// 實(shí)例化一個(gè) animal 對(duì)象
var animal = new Animal();

console.log(animal.__proto__);      // {}
console.log(animal.__proto__.__proto__);  // {}
console.log(animal.__proto__.__proto__.constructor);  // function Object(){}
console.log(animal.__proto__.__proto__.__proto__);  // null

如圖所示:

4.2 原型鏈的拓展

1、描述出數(shù)組“[]”的原型鏈結(jié)構(gòu)

// 創(chuàng)建一個(gè)數(shù)組
var arr = new Array();

// 我們可以看到這個(gè)數(shù)組是構(gòu)造函數(shù) Array 的實(shí)例對(duì)象,所以他的原型應(yīng)該是:
console.log(Array.prototype);   // 打印出來還是一個(gè)空數(shù)組

// 我們可以繼續(xù)往上找 
console.log(Array.prototype.__proto__);  // 空對(duì)象

// 繼續(xù)
console.log(Array.prototype.__proto__.__proto__)  // null

如圖所示:

2、擴(kuò)展內(nèi)置對(duì)象

js原有的內(nèi)置對(duì)象,添加新的功能。

注意:這里不能直接給內(nèi)置對(duì)象的原型添加方法,因?yàn)樵陂_發(fā)的時(shí)候,大家都會(huì)使用到這些內(nèi)置對(duì)象,假如大家都是給內(nèi)置對(duì)象的原型添加方法,就會(huì)出現(xiàn)問題。

錯(cuò)誤的做法:

// 第一個(gè)開發(fā)人員給 Array 原型添加了一個(gè) say 方法
Array.prototype.say = function(){
    console.log("哈哈哈");
}

// 第二個(gè)開發(fā)人員也給 Array 原型添加了一個(gè) say 方法
Array.prototype.say = function(){
    console.log("啪啪啪");
}

var arr = new Array();

arr.say();  // 打印 “啪啪啪”  前面寫的會(huì)被覆蓋

為了避免出現(xiàn)這樣的問題,只需自己定義一個(gè)構(gòu)造函數(shù),并且讓這個(gè)構(gòu)造函數(shù)繼承數(shù)組的方法即可,再去添加新的方法。

// 創(chuàng)建一個(gè)數(shù)組對(duì)象 這個(gè)數(shù)組對(duì)象繼承了所有數(shù)組中的方法
var arr = new Array();

// 創(chuàng)建一個(gè)屬于自己的構(gòu)造函數(shù)
function MyArray(){}

// 只需要將自己創(chuàng)建的構(gòu)造函數(shù)的原型替換成 數(shù)組對(duì)象,就能繼承數(shù)組的所有方法
MyArray.prototype = arr;

// 現(xiàn)在可以多帶帶的給自己創(chuàng)建的構(gòu)造函數(shù)的原型添加自己的方法
MyArray.prototype.say = function(){
    console.log("這是我自己添加的say方法");
}

var arr1 = new MyArray();

arr1.push(1);   // 創(chuàng)建的 arr1 對(duì)象可以使用數(shù)組的方法
arr1.say();     // 也可以使用自己添加的方法  打印“這是我自己添加的say方法”
console.log(arr1);  // [1]
4.3 屬性的搜索原則
當(dāng)通過對(duì)象名.屬性名獲取屬性時(shí),會(huì)遵循以下屬性搜索的原則:

1-首先去對(duì)象自身屬性中找,如果找到直接使用,

2-如果沒找到,去自己的原型中找,如果找到直接使用,

3-如果沒找到,去原型的原型中繼續(xù)找,找到直接使用,

4-如果沒有會(huì)沿著原型不斷向上查找,直到找到null為止。

5.Object.prototype成員介紹
我們可以看到所有的原型最終都會(huì)繼承Object的原型:Object.prototype

打印看看Object的原型里面有什么:

// Object的原型
console.log(Object.prototype)

如圖所示:

我們可以看到Object的原型里有很多方法,下面就來介紹下這些方法的作用。

5.1 constructor 屬性
指向了和原型相關(guān)的構(gòu)造函數(shù)
5.2 hasOwnProperty 方法
判斷對(duì)象自身是否擁有某個(gè)屬性,返回值:布爾類型。

示例代碼:

function Hero() {
    this.name = "蓋倫";
    this.age = "25";
    this.skill = function () {
        console.log("蓋倫使用了大寶劍");
    }
}

var hero = new Hero();
console.log(hero.name); // "蓋倫"
hero.skill();           // "蓋倫使用了大寶劍"

console.log(hero.hasOwnProperty("name"));       // true
console.log(hero.hasOwnProperty("age"));        // true
console.log(hero.hasOwnProperty("skill"));      // true
console.log(hero.hasOwnProperty("toString"));   // false toString是在原型鏈當(dāng)中的方法,并不是這里對(duì)象的方法

console.log("toString" in hero); // true in方法 判斷對(duì)象自身或者原型鏈中是否有某個(gè)屬性
5.3 isPrototypeOf 方法
對(duì)象1.isPrototypeOf(對(duì)象2),判斷對(duì)象1是否是對(duì)象2的原型,或者對(duì)象1是否是對(duì)象2原型鏈上的原型。

示例代碼:

var obj = {
    age: 18
}
var obj1 = {};

// 創(chuàng)建一個(gè)構(gòu)造函數(shù)
function Hero() {
    this.name = "蓋倫";
}

// 將這個(gè)構(gòu)造函數(shù)的原型替換成 obj
Hero.prototype = obj;

// 實(shí)例化一個(gè) hero 對(duì)象
var hero = new Hero();

console.log(obj.isPrototypeOf(hero));   // true  判斷 obj 是否是 hero 的原型
console.log(obj1.isPrototypeOf(hero));  // false  判斷 obj1 是否是 hero 的原型
console.log(Object.prototype.isPrototypeOf(hero));  // true  判斷 Object.prototype 是否是 hero 的原型
// 注意 這里的 Object.prototype 是原型鏈上最上層的原型對(duì)象
5.4 propertyIsEnumerable 方法
對(duì)象.propertyIsEnumerable("屬性或方法名"),判斷一個(gè)對(duì)象是否有該屬性,并且這個(gè)屬性可以被for-in遍歷,返回值:布爾類型。

示例代碼:

// 創(chuàng)建一個(gè)構(gòu)造函數(shù)
function Hero (){
    this.name = "蓋倫";
    this.age = 25;
    this.skill = function(){
        console.log("蓋倫使用了大寶劍");
    }
}

// 創(chuàng)建一個(gè)對(duì)象
var hero = new Hero();

// for-in 遍歷這個(gè)對(duì)象 我們可以看到分別打印了哪些屬性和方法
for(var k in hero){
    console.log(k + "—" + hero[k]); // "name-蓋倫" "age-25" "skill-fn()"
}

// 判斷一個(gè)對(duì)象是否有該屬性,并且這個(gè)屬性可以被 for-in 遍歷
console.log(hero.propertyIsEnumerable("name"));     // true
console.log(hero.propertyIsEnumerable("age"));      // true
console.log(hero.propertyIsEnumerable("test"));     // false
5.5 toString 和 toLocalString 方法
兩種方法都是將對(duì)象轉(zhuǎn)成字符串的,只不過toLocalString是按照本地格式進(jìn)行轉(zhuǎn)換。

示例代碼:

// 舉個(gè)例子,時(shí)間的格式可以分為世界時(shí)間的格式和電腦本地的時(shí)間格式
var date = new Date();

// 直接將創(chuàng)建的時(shí)間對(duì)象轉(zhuǎn)換成字符串
console.log(date.toString());

// 將創(chuàng)建的時(shí)間對(duì)象按照本地格式進(jìn)行轉(zhuǎn)換
console.log(date.toLocaleString());

效果圖:

5.6 valueOf 方法
返回指定對(duì)象的原始值。

MDN官方文檔

6.靜態(tài)方法和實(shí)例方法
靜態(tài)方法和實(shí)例方法這兩個(gè)概念其實(shí)也是從面相對(duì)象的編程語言中引入的,對(duì)應(yīng)到JavaScript中的理解為:

靜態(tài)方法: 由構(gòu)造函數(shù)調(diào)用的

js中,我們知道有個(gè)Math構(gòu)造函數(shù),他有一個(gè)Math.abs()的方法,這個(gè)方法由構(gòu)造函數(shù)調(diào)用,所以就是靜態(tài)方法。
Math.abs();

實(shí)例方法: 由構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象調(diào)用的

var arr = new Array();

// 由構(gòu)造函數(shù) Array 實(shí)例化出來的對(duì)象 arr 調(diào)用的 push 方法,叫做實(shí)例方法
arr.push(1);

示例代碼:

function Hero(){
    this.name="亞索";
    this.say=function(){
        console.log("哈撒ki");
    }
}
Hero.prototype.skill=function(){
    console.log("吹風(fēng)");
}

// 直接給構(gòu)造函數(shù)添加一個(gè) run 方法(函數(shù)也是對(duì)象,可以直接給它加個(gè)方法)
Hero.run=function(){
    console.log("死亡如風(fēng),常伴吾身");
}

var hero = new Hero();

hero.say();
hero.skill();   //實(shí)例方法

Hero.run();     //靜態(tài)方法

如果這個(gè)方法是對(duì)象所有的,用實(shí)例方法。一般的工具函數(shù),用靜態(tài)方法,直接給構(gòu)造函數(shù)添加方法,不需要實(shí)例化,通過構(gòu)造函數(shù)名直接使用即可;

7.作用域
“域”,表示的是一個(gè)范圍,“作用域”就是作用范圍。作用域說明的是一個(gè)變量可以在什么地方被使用,什么地方不能被使用。
7.1 塊級(jí)作用域
ES5ES5之前,js中是沒有塊級(jí)作用域的。
{
    var num = 123;
    {
        console.log( num ); // 123
    }
}
console.log( num ); // 123

上面這段代碼在JavaScript中是不會(huì)報(bào)錯(cuò)的,但是在其他的編程語言中(C#、C、JAVA)會(huì)報(bào)錯(cuò)。這是因?yàn)?,?b>JavaScript中沒有塊級(jí)作用域,使用{}標(biāo)記出來的代碼塊中聲明的變量num,是可以被{}外面訪問到的。但是在其他的編程語言中,有塊級(jí)作用域,那么{}中聲明的變量num,是不能在代碼塊外部訪問的,所以報(bào)錯(cuò)。

注意:塊級(jí)作用域只在在ES5ES5之前不起作用,但是在ES6開始,js中是存在塊級(jí)作用域的。

7.2 詞法作用域
詞法( 代碼 )作用域,就是代碼在編寫過程中體現(xiàn)出來的作用范圍。代碼一旦寫好,不用執(zhí)行,作用范圍就已經(jīng)確定好了,這個(gè)就是所謂詞法作用域。

js中詞法作用域規(guī)則:

函數(shù)允許訪問函數(shù)外的數(shù)據(jù);

整個(gè)代碼結(jié)構(gòu)中只有函數(shù)可以限定作用域;

作用域規(guī)則首先使用提升規(guī)則分析;

如果當(dāng)前作用規(guī)則中有名字了,就不考慮外面的名字。

作用域練習(xí):

第一題

var num=250;

function test(){
    // 會(huì)現(xiàn)在函數(shù)內(nèi)部查找有沒有這個(gè)num變量,有的話調(diào)用,沒有的話會(huì)去全局中查找,有就返回,沒有就返回undefined
    console.log(num);  // 打印 250
}

function test1(){
   var num=222;
   test();
}

test1();  

第二題

if(false){
    var num = 123;
}

console.log(num); // undefined 
// {}是沒有作用域的 但是有判斷條件,var num會(huì)提升到判斷語句外部 所以不會(huì)報(bào)錯(cuò) 打印的是undefined

第三題

var num = 123;
function foo() {
    var num = 456;
    function func() {
        console.log( num );
    }
    func();
}
foo();  // 456
// 調(diào)用foo時(shí),在函數(shù)內(nèi)部調(diào)用了func,打印num的時(shí)候,會(huì)先在func中查找num  沒有的時(shí)候會(huì)去外層作用域找,找到即返回,找不到即再往上找。

第四題

var num1 = 123;
function foo1() {
   var num1 = 456;
   function foo2() {
       num1 = 789;
       function foo3 () {
           console.log( num1 );  // 789  自己的函數(shù)作用域中沒有就一層層往上找
       }
       foo3();
   }
   foo2();
}
foo1();
console.log( num1 ); // 123 
7.3 變量提升(預(yù)解析)
JavaScript是解釋型的語言,但是它并不是真的在運(yùn)行的時(shí)候逐句的往下解析執(zhí)行。

我們來看下面這個(gè)例子:

func();

function func(){
    alert("函數(shù)被調(diào)用了");
}

在上面這段代碼中,函數(shù)func的調(diào)用是在其聲明之前,如果說JavaScript代碼真的是逐句的解析執(zhí)行,那么在第一句調(diào)用的時(shí)候就會(huì)出錯(cuò),然而事實(shí)并非如此,上面的代碼可以正常執(zhí)行,并且alert出來"函數(shù)被調(diào)用了"。

所以,可以得出結(jié)論,JavaScript并非僅在運(yùn)行時(shí)簡簡單單的逐句解析執(zhí)行!

JavaScript預(yù)解析

JavaScript引擎在對(duì)JavaScript代碼進(jìn)行解釋執(zhí)行之前,會(huì)對(duì)JavaScript代碼進(jìn)行預(yù)解析,在預(yù)解析階段,會(huì)將以關(guān)鍵字varfunction開頭的語句塊提前進(jìn)行處理。

關(guān)鍵問題是怎么處理呢?

當(dāng)變量和函數(shù)的聲明處在作用域比較靠后的位置的時(shí)候,變量和函數(shù)的聲明會(huì)被提升到當(dāng)前作用域的開頭。

示例代碼:函數(shù)名提升

正常函數(shù)書寫方式

function func(){
    alert("函數(shù)被調(diào)用了");
}
func();

預(yù)解析之后,函數(shù)名提升

func();
function func(){
    alert("函數(shù)被調(diào)用了");
}

示例代碼:變量名提升

正常變量書寫方式

alert(a);  // undefined  
var a = 123;
// 由于JavaScript的預(yù)解析機(jī)制,上面這段代碼,alert出來的值是undefined,
// 如果沒有預(yù)解析,代碼應(yīng)該會(huì)直接報(bào)錯(cuò)a is not defined,而不是輸出值。

不是說要提前的嗎?那不是應(yīng)該alert出來123,為什么是undefined?

// 變量的時(shí)候 提升的只是變量聲明的提升,并不包括賦值
var a;      // 這里是聲明
alert(a);   // 變量聲明之后并未有初始化和賦值操作,所以這里是 undefined
a = 123;    // 這里是賦值

注意:特殊情況

1、函數(shù)不能被提升的情況

函數(shù)表達(dá)式創(chuàng)建的函數(shù)不會(huì)提升

test();   // 報(bào)錯(cuò) "test is not a function"
var test = function(){
    console.log(123);
}

new Function創(chuàng)建的函數(shù)也不會(huì)被提升

test();   // 報(bào)錯(cuò) "test is not a function"
var test = new Function(){
    console.log(123);
}

2、出現(xiàn)同名函數(shù)

test();  // 打印 "好走的都是下坡路"

// 兩個(gè)函數(shù)重名,這兩個(gè)函數(shù)都會(huì)被提升,但是后面的函數(shù)會(huì)覆蓋掉前面的函數(shù)
function test(){
   console.log("眾里尋她千百度,他正在自助烤肉....");
}

function test(){
   console.log("好走的都是下坡路");
}

3、函數(shù)名與變量名同名

// 如果函數(shù)和變量重名,只會(huì)提升函數(shù),變量不會(huì)被提升
console.log(test);  // 打印這個(gè)test函數(shù)

function test(){
   console.log("我是test");
}
var test=200;

再看一種情況:

var num = 1;
function num () {
    console.log(num); // 報(bào)錯(cuò) “num is not a function”
}
num();

直接上預(yù)解析后的代碼:

function num(){
    console.log(num);
}

num = 1;
num();

4、條件式的函數(shù)聲明

// 如果是條件式的函數(shù)申明, 這個(gè)函數(shù)不會(huì)被預(yù)解析
test();  // test is not a function
if(true){
    function test(){
        console.log("只是在人群中多看了我一眼,再也忘不掉我容顏...");
    }
}

預(yù)解析是分作用域的

聲明提升并不是將所有的聲明都提升到window 對(duì)象下面,提升原則是提升到變量運(yùn)行的當(dāng)前作用域中去。

示例代碼:

function showMsg(){
    var msg = "This is message";
}
alert(msg); // 報(bào)錯(cuò)“Uncaught ReferenceError: msg is not defined”

預(yù)解析之后:

function showMsg(){
    var msg;    // 因?yàn)楹瘮?shù)本身就會(huì)產(chǎn)生一個(gè)作用域,所以變量聲明在提升的時(shí)候,只會(huì)提升在當(dāng)前作用域下最前面
    msg = "This is message";
}
alert(msg); // 報(bào)錯(cuò)“Uncaught ReferenceError: msg is not defined”

預(yù)解析是分段的

分段,其實(shí)就分script標(biāo)簽的


在上面代碼中,第一個(gè)script標(biāo)簽中的兩個(gè)func進(jìn)行了提升,第二個(gè)func覆蓋了第一個(gè)func,但是第二個(gè)script標(biāo)簽中的func并沒有覆蓋上面的第二個(gè)func。所以說預(yù)解析是分段的。

tip: 但是要注意,分段只是單純的針對(duì)函數(shù),變量并不會(huì)分段預(yù)解析。

函數(shù)預(yù)解析的時(shí)候是分段的,但是執(zhí)行的時(shí)候不分段



7.4 作用域鏈

什么是作用域鏈?

只有函數(shù)可以制造作用域結(jié)構(gòu),那么只要是代碼,就至少有一個(gè)作用域, 即全局作用域。

凡是代碼中有函數(shù),那么這個(gè)函數(shù)就構(gòu)成另一個(gè)作用域。如果函數(shù)中還有函數(shù),那么在這個(gè)作用域中就又可以誕生一個(gè)作用域。將這樣的所有的作用域列出來,可以有一個(gè)結(jié)構(gòu): 函數(shù)內(nèi)指向函數(shù)外的鏈?zhǔn)浇Y(jié)構(gòu)。就稱作作用域鏈。

例如:

function f1() {
    function f2() {
    }
}

var num = 456;
function f3() {
    function f4() {    
    }
}

示例代碼:

var num=200;
function test(){
    var num=100;
    function test1(){
        var num=50;
        function test2(){
            console.log(num);
        }
        test2();
    }
    test1();
}

test();   // 打印 “50”

如圖所示:

繪制作用域鏈的步驟:

看整個(gè)全局是一條鏈, 即頂級(jí)鏈, 記為0級(jí)鏈

看全局作用域中, 有什么變量和函數(shù)聲明, 就以方格的形式繪制到0級(jí)練上

再找函數(shù), 只有函數(shù)可以限制作用域, 因此從函數(shù)中引入新鏈, 標(biāo)記為1級(jí)鏈

然后在每一個(gè)1級(jí)鏈中再次往復(fù)剛才的行為

變量的訪問規(guī)則:

首先看變量在第幾條鏈上, 在該鏈上看是否有變量的定義與賦值, 如果有直接使用

如果沒有到上一級(jí)鏈上找( n - 1 級(jí)鏈 ), 如果有直接用, 停止繼續(xù)查找.

如果還沒有再次往上剛找... 直到全局鏈( 0 級(jí) ), 還沒有就是 is not defined

注意,同級(jí)的鏈不可混合查找

來點(diǎn)案例練練手

第一題:

function foo() {
    var num = 123;
    console.log(num); //123
}
foo();
console.log(num); // 報(bào)錯(cuò)

第二題:

var scope = "global";
function foo() {
    console.log(scope); //  undefined
    var scope = "local";
    console.log(scope); // "local"
}
foo();

// 預(yù)解析之后
// var scope = "global";
// function foo() {
//   var scope;
//   console.log(scope); // undefined
//   scope = "local";
//   console.log(scope); // local
// }

第三題:

if("a" in window){
   var a = 10;
}
console.log(a); // 10

// 預(yù)解析之后
// var a;
// if("a" in window){
//    a = 10;        // 判斷語句不產(chǎn)生作用域
// }
// console.log(a); // 10

第四題:

if(!"a" in window){
   var a = 10;
}
console.log(a); // undefined

// 預(yù)解析之后
// var a;
// if(!"a" in window){
//    a = 10;        // 判斷語句不產(chǎn)生作用域
// }
// console.log(a); // undefined

第五題

// console.log(num); 報(bào)錯(cuò) 雖然num是全局變量 但是不會(huì)提升
function test(){
   num = 100;  
}

test();

console.log(num);   // 100

第六題

var foo = 1;
function bar() {
   if(!foo) {
       var foo = 10;
   }
   console.log(foo); // 10
}
bar();

// 預(yù)解析之后
// var foo=1;
// function bar(){
//    var foo;
//    if(!foo){
//        foo=10;
//    }
//    console.log(foo); // 10
// }
// bar();
8.Function
Function是函數(shù)的構(gòu)造函數(shù),你可能會(huì)有點(diǎn)蒙圈,沒錯(cuò),在js中函數(shù)與普通的對(duì)象一樣,也是一個(gè)對(duì)象類型,只不過函數(shù)是js中的“一等公民”。

這里的Function類似于Array、Object

8.1 創(chuàng)建函數(shù)的幾種方式

1、函數(shù)字面量(直接聲明函數(shù))創(chuàng)建方式

function test(){   
    // 函數(shù)體
}   // 類似于對(duì)象字面量創(chuàng)建方式:{}

2、函數(shù)表達(dá)式

var test = function(){
    // 函數(shù)體
}

3、Function構(gòu)造函數(shù)創(chuàng)建

// 構(gòu)造函數(shù)創(chuàng)建一個(gè)空的函數(shù)
var fn = new Function();
fn1();  // 調(diào)用函數(shù)

函數(shù)擴(kuò)展名

有沒有一種可能,函數(shù)表達(dá)式聲明函數(shù)時(shí),function 也跟著一個(gè)函數(shù)名,如:var fn = function fn1(){}? 答案是可以的,不過fn1只能在函數(shù)內(nèi)部使用,并不能在外部調(diào)用。
var fn = function fn1(a,b,c,d){
    console.log("當(dāng)前函數(shù)被調(diào)用了");
    // 但是,fn1可以在函數(shù)的內(nèi)部使用
    console.log(fn1.name);
    console.log(fn1.length);
    // fn1();  注意,這樣調(diào)用會(huì)引起遞歸!?。? 下面我們會(huì)講到什么是遞歸。
}
// fn1();   // 報(bào)錯(cuò),fn1是不能在函數(shù)外部調(diào)用的
fn();   // "當(dāng)前函數(shù)被調(diào)用了"

// 函數(shù)內(nèi)部使用時(shí)打?。?// "當(dāng)前函數(shù)被調(diào)用了"
// console.log(fn1.name); => "fn1"
// console.log(fn1.length); => 4
8.2 Function 構(gòu)造函數(shù)創(chuàng)建函數(shù)
上面我們知道了如何通過Function構(gòu)造函數(shù)創(chuàng)建一個(gè)空的函數(shù),這里我們對(duì)它的傳參詳細(xì)的說明下。

1、不傳參數(shù)時(shí)

// 不傳參數(shù)時(shí),創(chuàng)建的是一個(gè)空的函數(shù)
var fn1 = new Function();
fn1();  // 調(diào)用函數(shù)

2、只傳一個(gè)參數(shù)

// 只傳一個(gè)參數(shù)的時(shí)候,這個(gè)參數(shù)就是函數(shù)體
// 語法:var fn = new Function(函數(shù)體);
var fn2 = new Function("console.log(2+5)");
f2();   // 7

3、傳多個(gè)參數(shù)

// 傳多個(gè)參數(shù)的時(shí)候,最后一個(gè)參數(shù)為函數(shù)體,前面的參數(shù)都是函數(shù)的形參名
// 語法:var fn = new Function(arg1,arg2,arg3.....argn,metthodBody);
var fn3 = new Function("num1","num2","console.log(num1+num2)");
f3(5,2);   // 7
8.3 Function 的使用

1、用Function創(chuàng)建函數(shù)的方式封裝一個(gè)計(jì)算m - n之間所有數(shù)字的和的函數(shù)

//求 m-n之間所有數(shù)字的和
//var sum=0;
//for (var i = m; i <=n; i++) {
//  sum+=i;
//}
var fn = new Function("m","n","var sum=0;for (var i = m; i <=n; i++) {sum+=i;} console.log(sum);");
fn(1,100);  // 5050

函數(shù)體參數(shù)過長問題:

函數(shù)體過長時(shí),可讀性很差,所以介紹解決方法:

1)字符串拼接符“+

var fn = new Function(
    "m",
    "n",
    "var sum=0;"+
    "for (var i = m; i <=n; i++) {"+
        "sum += i;"+
    "}"+
    "console.log(sum);"
    );
fn(1,100);  // 5050

2)ES6中新語法“ ` ”,(在esc鍵下面)

表示可換行字符串的界定符,之前我們用的是單引號(hào)或者雙引號(hào)來表示一個(gè)字符串字面量,在ES6中可以用反引號(hào)來表示該字符串可換行。
new Function(
    "m",
    "n",
    `var sum=0;
    for (var i = m; i <=n; i++) {
        sum+=i;
    }
    console.log(sum);`
);

3)模板方式




2、eval 函數(shù)

eval函數(shù)可以直接將把字符串的內(nèi)容,作為js代碼執(zhí)行,前提是字符串代碼符合js代碼規(guī)范。這里主要是用作跟Function傳參比較。

evalFunction 的區(qū)別:

Function();中,方法體是字符串,必須調(diào)用這個(gè)函數(shù)才能執(zhí)行

eval(); 可以直接執(zhí)行字符串中的js代碼

存在的問題:

性能問題

因?yàn)?b>eval里面的代碼是直接執(zhí)行的,所以當(dāng)在里面定義一個(gè)變量的時(shí)候,這個(gè)變量是不會(huì)預(yù)解析的,所以會(huì)影響性能。
// eval 里面的代碼可以直接執(zhí)行,所以下面的打印的 num 可以訪問到它
// 但是這里定義的 num 是沒有預(yù)解析的,所以變量名不會(huì)提升,從而性能可能會(huì)變慢
eval("var num = 123;");
console.log(num);   // 123

安全問題

主要的安全問題是可能會(huì)被利用做XSS攻擊(跨站腳本攻擊(Cross Site Scripting)),eval也存在一個(gè)安全問題,因?yàn)樗梢詧?zhí)行傳給它的任何字符串,所以永遠(yuǎn)不要傳入字符串或者來歷不明和不受信任源的參數(shù)。

示例代碼: 實(shí)現(xiàn)一個(gè)簡單的計(jì)算器









效果圖:

8.4 Function 的原型鏈結(jié)構(gòu)
7.2章節(jié)中我們知道函數(shù)也還可以通過構(gòu)造函數(shù)的方式創(chuàng)建出來,既然可以通過構(gòu)造函數(shù)的方式創(chuàng)建,那么函數(shù)本身也是有原型對(duì)象的。

示例代碼:

// 通過Function構(gòu)造函數(shù)創(chuàng)建一個(gè)函數(shù)test
var test = new Function();
// 既然是通過構(gòu)造函數(shù)創(chuàng)建的,那么這個(gè)函數(shù)就有指向的原型
console.log(test.__proto__);  // 打印出來的原型是一個(gè)空的函數(shù)
console.log(test.__proto__.__proto__);  // 空的函數(shù)再往上找原型是一個(gè)空的對(duì)象
console.log(test.__proto__.__proto__.__proto__);    // 再往上找就是null了

// 函數(shù)原型鏈: test() ---> Function.prototype ---> Object.prototype ---> null

如圖所示:

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100458.html

相關(guān)文章

  • 【連載】前端個(gè)人文章整理-從基礎(chǔ)到入門

    摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...

    madthumb 評(píng)論0 收藏0
  • 個(gè)人分享--web前端學(xué)習(xí)資源分享

    摘要:前言月份開始出沒社區(qū),現(xiàn)在差不多月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議那么今天我就把看過的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區(qū),現(xiàn)在差不多9月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議!那么今天我就...

    sherlock221 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<