前端筆記之JavaScript物件導向(一)Object&函式上下文&建構函式&原型鏈

mufengsm發表於2019-04-17

一、物件(Object

1.1 認識物件

物件在JS中狹義物件、廣義物件兩種。

廣義:相當於巨集觀概念,是狹義內容的昇華,高度的提升,範圍的擴充。
狹義:相當於微觀概念,什麼是“狹”?因為內容狹隘具體,範圍窄所以稱為“狹”

狹義物件

就是用{}這種字面量的形式定義的物件,它是一組屬性的無序集合

var obj = {
   name : "小明",
   age : 12,
   sex : "男",
   hobby : ["足球","刺繡","打麻將"]
}

 上面這個物件,表示一個“人”裡面有4個屬性,換句話說,這個物件裡面除了4個屬性,別的什麼都沒有。

 

比如不用物件,而用陣列來儲存一組剛才的值:

var arr = ["小明",12,"男",["足球","刺繡","打麻將"]]
console.log(arr)

陣列只能儲存“值”,不能儲存“鍵”。

換句話說,陣列中的值“語義”不詳。物件除了能儲存值,還能儲存值的“語義”,術語叫“鍵(key)

其實,物件就是一組值和值的語義的封裝。

 

【廣義物件】

DOM元素是物件,但是和剛剛說的“狹義物件裡面只有一組值,別的什麼都沒有”不同

var oBox = document.getElementById("box"); //得到一個DOM物件
oBox.xingming = "小明";
oBox.age = 12;
oBox.sex = "男";
oBox.hobby = ["足球","刺繡","打麻將"];
console.log(oBox.hobby);
console.log(typeof oBox); //object

通過DOM方法得到一個DOM物件,此時可以通過“.”點語法,給這個物件新增屬性,用oBox訪問age

此時這個物件不僅僅只有4個屬性,還有別的,因為oBox畢竟有一個HTML標籤實體在頁面上。

 

陣列也是物件

var arr = [1,2,3,4,5];
//也可以通過“點”語法,給陣列新增屬性
arr.xingming = "小明";
arr.age = 12;
arr.sex = "男";
console.log(typeof arr); //object
console.log(arr.xingming); //小明

說明陣列有物件的一切特徵,能新增屬性,但是你不能說這個陣列只有nameagesex三個屬性,別的什麼都沒有,畢竟它有一組數。

 

函式也是物件

typeof檢測型別返回結果是function不是object,這是系統規定,但function也是object物件,後面詳解。

function fun(){
}
console.log(typeof fun);
fun.xingming = "小明";
fun.age = 12;
fun.sex = "男";
console.log(fun.xingming)

此時物件新增了4個屬性,但是你不能說這個fun物件只有4個屬性別的什麼都沒有,因為它畢竟是一個函式。能夠圓括號執行。

 

正規表示式也是物件:

var regexp = /\d/g;
console.log(typeof regexp)
regexp.xingming = "小明";
regexp.age = 12;
regexp.sex = "男";
console.log(regexp.xingming)

能新增屬性成功,但是你不能說只有3個屬性,畢竟是一個正則。

 

系統內建的所有引用型別值,都是物件,都能新增自定義屬性,並且能夠訪問這些屬性:

function 函式
Array 陣列
RegExp 正規表示式
DOM元素
window、document、Math、Date物件
Number()、Sting()內建包裝建構函式

這些物件除了一組屬性之外,還有其他的東西。比如陣列還有一組值;比如函式還有一組語句,能夠圓括號執行。

 

什麼不是物件?就是系統的基本型別:

數字不能新增屬性,因為數字是基本型別,不是物件

var a = 100;
//試圖新增屬性
a.xingming = "小明";
a.age = 12;
console.log(a.age); //undefined

這幾天研究的就是物件,只有JS提供(除了屬性還有別的東西)的物件,我們不能建立,也就是說,對開發者而言,我們只能建立狹義物件。

 

那麼到底有什麼性質,就稱它為是物件?

能新增屬性,特別的微觀層面,只要這個東西能存放在堆記憶體中,就可以認為是一個物件。


1.2物件的方法

如果一個物件的屬性是函式,我們稱這個屬性叫這個物件的方法(methods

當一個函式當作物件的方法被呼叫時,這個函式裡面的this表示這個物件。

//下面這個物件有一個屬性,叫sayHello,它的值是函式,所以可以把它叫做obj的方法
var obj = {
   xingming : "小明",
   age : 12,
   sex :"男",
   sayHello : function(){
       alert("你好,我是" + this.xingming +",今年" + this.age)
   }
};
obj.sayHello()

 

現在呼叫sayHello函式時,是通過obj打點呼叫的,所以這個sayHello函式的上下文就是obj物件。

sayHello函式內部的this指向obj

 

但是千萬不要認為寫在物件裡面的函式,上下文就是這個物件!!!

比如:

var xingming = "小紅";
var age = 18;
var obj = {
   xingming : "小明",
   age : 12,
   sex : "男",
   sayHello : function(){
       alert("你好,我是" + this.xingming +",今年" + this.age)
   }
};
var fn = obj.sayHello;
fn(); //直接()呼叫,不是物件打點呼叫,所以this上下文是window

函式的上下文是什麼,取決於函式是怎麼呼叫的,而不是函式如何定義。

函式的上下文是函式的呼叫時表現的性質,不是函式定義時寫死的性質。


1.3物件和JSON的區別

JSON(JavaScript Object Notation, JS 物件標記) 是一種輕量級的資料交換格式JS物件表示法

JSONJS物件的嚴格子集。

區別就是引號:JSON要求所有的鍵都必須加引號,而JS物件實際上不要求加引號。

這是一個標準的JSON

var obj = {
   "name" : "小明",
   "age" : 12,
   "sex" : "男"
}


實際上不加引號也合法:
var obj = {
   name : "小明",
   age : 12,
   sex : "男"
}

為什麼JSON規定要加上引號呢?因為JSON是一個資料互動格式,它是前端PHPJava等後臺語言的資訊交換媒介,後臺工程師可以從資料庫得到資料,組建JSON,前臺通過Ajax拿到這個JSON之後,解析JSON渲染頁面。

所以是其他語言要求這個JSON有引號,否則其他語言會報錯,不是JS要求的,JSON天生為通訊而生!!

但是,有一種必須加引號,就是不符合命名規範的鍵名,必須加引號,否則報錯。

 

 比如下面的鍵名都不符合識別符號的命名規範,必須加引號:

var obj = {
       "-" : 18,
       "@#$" : 20,
       "2018" : 100,
       "哈哈" : 200,
       "key"  : 888,
"true" : 999
}
console.log(obj["-"]); //訪問屬性時,也要加引號,表示鍵名
console.log(obj["@#$"]);
console.log(obj["2018"]);
console.log(obj.哈哈);
console.log(obj["哈哈"]);

 

特別的是,如果用一個變數儲存一個key,此時必須用[]列舉,並且[]不能加引號

var key = 2018;
var k = 3 < 8; //true
console.log(obj.key);   //888 點語法只能以字串形式訪問物件的屬性,key不能是變數
console.log(obj["key"]); //888
console.log(obj[key]); //2018的100,實際上讀取的是obj["2018"],[]會隱式轉換為字串
console.log(obj[k]); //999

1.4全域性變數是window物件的屬性

var a = 100;
var b = 200;
var c = 300;
var d = 400;
alert(window.a)
alert(window.b)
alert(window.c)
alert(window.d)

二、函式的上下文(context

所謂的上下文就是指函式裡面的this是誰。就說“函式的上下文是什麼”

函式中的this是誰,看這個函式是如何呼叫的,而不是看這個函式如何定義的。

舉個例子:踢足球的時候,球進對方的門,看誰最後碰到球,我方球員射門的那一腳踢到了門柱,反彈給對方球員進門,就是烏龍球。

2.1規則1:函式直接圓括號呼叫,上下文是window物件

直接兩個字,表示這個函式代號之前,沒有任何識別符號,沒有小圓點,沒有方括號。通常從陣列、物件中“提”出函式的操作(把函式賦給變數):

obj物件中定義一個函式,叫fun,這個是obj的屬性:

var a = 888;
var obj = {
   a : 100,
   fun : function(){
       alert(this.a);
   }
}

如直接物件打點呼叫函式:此時彈出100,說明函式上下文是obj物件本身。

obj.fun();

 

但如果這個函式被一個變數接收(讓變數直接指向這個物件的方法)

var fn = obj.fun;
fn(); //這個叫()直接執行

然後呼叫,函式的this將是window物件,this.a就是訪問全域性的a變數是888

 

注意,所有IIFE,都屬於直接呼叫範圍,裡面的this都是window

不管IIFE寫的有多深,不管所在的環境多複雜,上下文一律是window物件。

比如下面obj物件中的b,是IIFE

var a = 888;
var obj = {
   a : 100,
   b : (function(){
       alert(this.a);
   })()
}
obj.b; //888

 

小題目:

var xiaoming = {
    name :"小明",
    age : 23,
    chengwei: (function(){
        return this.age >= 18 ? "先生" : "小朋友"
    })()
}
alert(xiaoming.name + xiaoming.chengwei)

 

2.2規則2:定時器直接呼叫函式,上下文是window物件

這個fn的最終呼叫者是定時器

var a = 100;
function fn(){
   console.log(this.a++);
}
setInterval(fn,1000)

 

注意臨門一腳誰踢的,是誰最終呼叫那個函式,比如:

var a = 100;
var obj = {
   a : 200,
   fn : function(){
       console.log(this.a++);
   }
}
setInterval(obj.fn, 1000); //obj.fn沒有()執行,是定時器呼叫的

 

var a = 100;
var obj = {
   a : 200,
   fn : function(){
       console.log(this.a++);
   }
}
setInterval(function(){
   obj.fn();  //obj.fn()直接呼叫,上下文的this是obj
}, 1000);


2.3規則3DOM事件處理函式的this,指的是觸發事件的這個元素

var box1 = document.getElementById("box1");
var box2 = document.getElementById("box2");
var box3 = document.getElementById("box3");
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var btn3 = document.getElementById("btn3");
function setColor(){
    this.style.backgroundColor = 'red';
}
box1.onclick = setColor;
box2.onclick = setColor;
box3.onclick = setColor;
btn1.onclick = setColor;
btn2.onclick = setColor;
btn3.onclick = setColor;

 

此時點選上面的元素,上面元素就是函式的上下文。

var box1 = document.getElementById("box1");
box1.onclick = function(){
    var self = this; //備份this
    setTimeout(function(){
        //這裡this指向window,所以先在外面備份this,再用
        self.style.background = 'red';
    },1000)
}

2.4規則4call()apply()設定函式的上下文

普通函式functionthis是指向window

function fun(){
   console.log(this);
}
fun();

我們說函式的上下文看函式是如何呼叫的,但任何函式可以通過call()apply()這兩個內建方法來呼叫函式的同時,還能改變它的this指向。

 

公式:函式將以某物件為上下文執行。

函式.call(某物件);
函式.apply(某物件);
var oBox = document.getElementById('box');
function fun(){
    console.log(this);
    this.style.backgroundColor = 'red';
}
//call和apply作用都一樣,有兩個作用:
//1、執行fun函式
//2、改變fun函式的this指向div
fun.call(oBox)
fun.apply(oBox)

callapply功能是一樣的,都是讓函式呼叫,並且設定函式this指向誰。區別在於函式傳遞引數的語法不同。

 

l call需要用逗號隔開羅列所有引數

l apply是把所有引數寫在陣列裡面,即使只有一個引數,也必須寫在陣列中。

var obj = {
   a:100
}
function fun(a,b,c){
   console.log(a,b,c)
   console.log(this);
}
fun.call(obj,10,20,30);
fun.apply(obj,[10,20,30]);

比如有一個函式叫變性函式(bianxing),它能夠將自己上下文的sex屬性改變。

 

此時小明物件(xiaoming),迫切要變性,xiaoming就成為bianxing的上下文:

function bianxing(){
   if(this.sex == '男'){
       this.sex  = '女'
   }else{
       this.sex  = '男'
   }
}
var xiaoming = {
   name : "小明",
   sex  : "男",
   // bianxing : bianxing
}
// xiaoming.bianxing()
bianxing.call(xiaoming);
bianxing.apply(xiaoming);
console.log(xiaoming)

callapply方法幫我們做了兩件事:

呼叫bianxing函式

改變bianxing函式的this指向為xiaoming


小題目:

apply通常用於一個函式呼叫另一個函式的時,將自己所有的引數都傳入一個函式:

function fun1(){
   fun2.apply(obj, arguments)
}
function fun2(a,b,c){
   console.log(this === obj);//true
   console.log(a)
   console.log(b)
   console.log(c)
}
var obj = {}
fun1("蘋果","西瓜","哈密瓜")

 

比如要求陣列中的最大值

// Math.max(); 方法可以返回所有引數的最大值
// Math.min(); 方法可以返回所有引數的最小值
console.log(Math.max(312,432,64,654,88,213,888,999));
console.log(Math.min(312,432,64,654,88,213,888,999));

但是,如果給你一個陣列呢?此時迫切要將陣列拆解為裸寫的一個個的引數。

那麼apply足夠好用,這裡不能用call,因為call是裸寫引數,不是傳陣列。

var arr = [31,88,543,999,777,42]
console.log(Math.max.apply(null, arr))
console.log(Math.min.apply(null, arr))

2.5規則5:從物件或陣列中列舉的函式,上下文是這個物件或陣列

來看一個最基本的模型,就是物件中的方法,方法中出現this

如果呼叫是:物件.方法(),此時函式中this就是指向這個物件。

var obj = {
   a : 100,
   b : function(){
       alert(this.a)
   }
}
obj.b(); //100

 

陣列也一樣,如果一樣函式是從陣列中列舉的,加圓括號執行,陣列[0](); 此時上下文就是陣列

var arr = [
    "A",
    "B",
    function(){
        alert(this.length)
    }
]
arr[2](); //輸出3,這寫法是從陣列中列舉出來的,所以是陣列在呼叫函式。
var f = arr[2];
f(); //0 全域性沒有length長度
console.log(arr)

知識複習

函式的length值是函式的:形參列表的長度

function f(a,b,c,d,e,f,g,h){
}
console.log(f.length); //8

 

arguments.length表示函式的:實參列表的長度

function f(a,b,c,d,e,f,g,h){
   console.log(arguments);
   console.log(arguments.length); //5
   console.log(arguments.callee); //callee等價於函式本身f
   console.log(arguments.callee.length); //8
}
f(1,2,3,4,5)
function fun1(a,b,c){
   console.log(arguments[0].length); //5
}
function fun2(a,b,c,d,e) {
}
fun1(fun2)

 


 

小題目1

//arguments列舉出了第0項,就是傳入的fun2函式,加()執行。
//這裡就符合規律5的內容,所以fun2的上下文this執行的是fun1的arguments類陣列物件
//所以它的length表示呼叫fun1的時候傳入的實參長度,是9
function fun1(a,b,c){
   arguments[0]();
}
function fun2(a,b,c,d,e) {
   alert(this.length); //9
}
fun1(fun2,2,3,4,5,6,7,8,9); //9

 


 

小題目2

小題目進階版:

function fun1(a,b,c){
   arguments[0](1,2,3,4,5,6); //arguments[0] == fun2函式
}
function fun2(a,b,c,d,e) {
   //這個函式裡面的this表示fun1函式的arguments物件
   alert(this.length);         //9    fun1的實參個數
   alert(this.callee.length);  //3   fun1的形參個數
   alert(arguments.length);    //6   fun2的實參個數
   alert(arguments.callee.length);  //5  fun2的形參個數
}
fun1(fun2,2,3,4,5,6,7,8,9); //9

 


 

小題目3

var m = 1;
var obj = {
    fn1 : function(){
        return this.fn2();
    },
    fn2 : fn2,
    m : 2
}
function fn2(){
    return this.m;
}
alert(obj.fn1()); //2

 


 

小題目4

不管函式的“身世”多複雜,一定要只看呼叫的哪一下是如何呼叫的

var length = 1;
var obj = {
   length : 10,
   b : [{
       length:20,
       fn:function(){
           alert(this.length) //this == {}
       }
   }]
}
obj.b[0].fn(); //20 b[0] == {}
var o = obj.b[0];
var fn = obj.b[0].fn;
o.fn();     //20
fn();          //1
var length = 1;
var obj = {
   length : 10,
   b : [{
           length:20,
           fn:function(){
               alert(this.length)
           }
       }]
}
var arr = [obj, obj.b, obj.b[0], obj.b[0].fn];
arr[0].b[0].fn();  //20
arr[1][0].fn();    //20
arr[2].fn();       //20
arr[3]();          //4
如何判斷上下文(this):
規則1:直接圓括號呼叫fn(),IIFE呼叫,此時this是window
規則2:物件打點呼叫obj.fn(),此時this是obj
規則3:陣列中列舉函式呼叫arr[3](),此時this是arr
規則4:定時器呼叫函式setInterval(fn , 10),此時this是window
規則5:DOM事件監聽oBtn.onclick = fn,此時this是oBtn
規則6:call和allpay可以指定,fn.call(obj),此時this是obj
規則7:用new呼叫函式,new fun(),此時this是祕密新建立的空白物件。

三、建構函式

3.1建構函式

到目前為止,呼叫一個函式的方法,有很多:直接()圓括號呼叫、陣列或物件列舉呼叫、定時器呼叫、DOM事件呼叫,隨著呼叫的方法不同,函式的上下文也不同。

現在,要介紹一種函式的呼叫方式,用new來呼叫。

function fun(){
   alert("我呼叫");
}
var obj = new fun();
console.log(obj)

function People(name,age,sex){
   //建構函式,可以稱為一個“類”,描述的是一個類物件需要擁有的屬性
   this.name = name;
   this.age = age;
   this.sex = sex;
}
//建構函式的例項,也可以稱為“類的例項”,就相當於按照類的要求,例項化了一個個人
var xiaoming = new People("小明",12,"男");
var xiaohong = new People("小紅",13,"女");
var xiaogangpao = new People("小鋼炮",16,"女");
console.log(xiaoming)
console.log(xiaohong)
console.log(xiaogangpao)

new是一個動詞,表示產生“新”的,會發現,的確這個函式產生了新的物件。

 

結論:當用new呼叫一個函式時,會發生4四件事(4步走)

1) 函式內部會建立一個新的空物件{}

2) 將建構函式的作用域賦值給新物件(因此this就指向這個新的空物件)

3) 執行建構函式中的程式碼(為這個新的空物件新增屬性)

4) 函式執行完畢後,將這個物件返回(return)到外面被接收。(函式將把自己的上下文返回給這個物件)

 

 物件是什麼?一個泛指,JS中萬物皆物件。

類:物件的一個具體的細分

例項:類中的一個具體事物

例如:自然界萬物皆物件,把自然界中的事物分為幾大類:人類、動物類、植物類...等,而每一個人都是人類中的一個例項。

學習JS,需要給JS分類,然後再研究每一個類別中具體的物件 → 物件導向程式設計思想。

所有的程式語言都是物件導向開發js是一門輕量級的指令碼程式語言。

 

物件導向開發 → 研究類的繼承、封裝、多型

可以認為People是一個人類(classxiaomingxiaoming都是這個People類的例項(instance

會發現產生的物件擁有相同的屬性群,我們稱它們是同一個型別的物件

 

當函式被new呼叫時,此時總會返回同一型別的物件,感覺在構造什麼東西,這個函式就被稱為“建構函式”。

注意:

●函式是建構函式,不是因為函式本身,而是因為它被new呼叫了
●習慣上:建構函式要用大寫字母開頭,暗示其他程式設計師這是一個建構函式。但是,記住了,不是說大寫字母開頭的就是建構函式,而是因為被new了
●顧名思義,它能夠構造同一型別的物件,都有相同屬性群。

類比JavaOO語言(面嚮物件語言),People可以叫做類(class)。

JavaC++C#Python中,這種東西很像“類”的功能。實際上JavaScript沒有類(class)的概念,只有建構函式(constructor)的概念!

JavaScript基於物件的語言Base on Object),而不是具體意義上“物件導向”(oriented object)語言。但是,我們將Java的一些概念,給移植過來

 

灰色部分, 表示屬性在自己身上。

 

DOM語句也可以寫在建構函式中:

function Star(name,age,sex,url){
   this.name = name;
   this.age = age;
   this.sex = sex;
   this.url = "images/star/"+ url +".jpg";
   //建立圖片物件
   this.img = document.createElement('img');
   this.img.src = this.url; //新增圖片地址
   //上樹
   document.body.appendChild(this.img)
}
var liudehua = new Star("劉德華", 55, "男", 61);
var wanglihong = new Star("王力巨集", 50, "男", 73);
console.log(liudehua)
console.log(wanglihong)

3.2建構函式return

建構函式不用寫return就能幫你返回一個物件,但如果寫了return怎麼辦?

 

面試題考察:

●如果return基本型別值,則無視return,函式該返回什麼就返回什麼,但return會結束建構函式的執行。

●如果return引用型別值,就不返回new出的物件了,則返回return的這個,原有return被覆蓋。

function People(name,age,sex){
   this.name = name;
   this.age = age;
   return 100; //返回基本型別值,所以被忽略
   this.sex = sex; //return會打斷程式執行
}
var xiaoming = new People("小明",12,"男");
console.log(xiaoming)

 

類似的,都會被忽略:注意,null此時看作基本型別
return "abc"
return false
return undefined
return null

 

如果返回引用型別值:

function People(name,age,sex){
   this.name = name;
   this.age = age;
   return {a:100,b:200}; //返回引用型別值,所以被返回,原有的被覆蓋
}
var xiaoming = new People("小明",12,"男");
console.log(xiaoming)

四步走失效了,建構函式沒有任何意義了。

類似的,都會被返回,原有的新物件被覆蓋

return {}
return []
return /\d/
return Math
return function
return document

工作中不允許在建構函式return,面試都是偏、難、怪。


3.3建構函式-寫成工廠模式

工廠模式是軟體工程領域一種廣為人知的設計模式,而由於在ECMAScript中無法建立類,因此用函式封裝以特定介面建立物件。其實現方法非常簡單,也就是在函式內建立一個物件,給物件賦予屬性及方法再將物件返回即可。可以看到工廠模式的實現方法非常簡單,解決了建立多個相似物件的問題,但是工廠模式卻無從識別物件的型別,因為全部都是Object,不像DateArray等,因此出現了建構函式模式。

function People(name,age,sex){
   var obj = {}
   obj.name = name;
   obj.age = age;
   obj.sex = sex;
   return obj;
}
var xiaoming = People("小明",12,"男");
console.log(xiaoming)

3.4建構函式-新增方法

function People(name,age,sex){
   this.name = name;
   this.age = age;
   this.sex = sex;
   this.sayHello = function(){
       alert("我是" + this.name)
   }
}
var xiaoming = new People("小明",12,"男");
var xiaohong = new People("小紅",13,"女");
var xiaogangpao = new People("小鋼炮",16,"女");
xiaoming.sayHello()
xiaohong.sayHello()
xiaogangpao.sayHello()

資訊不會串,因為物件自己打點呼叫自己的方法,函式中的this是這個物件。

但是要研究一個事情,函式在哪?函式在每個例項身上。會發現每個人身上都有sayHello函式

 

 

以下測試結果為false,因為每一個物件身上都有一個獨立的函式:

console.log(xiaoming.sayHello === xiaohong.sayHello); //false

 

此時有一個重大的問題,就是函式都分別定義在三個例項身上,是三個函式的不同副本。

但是,函式天生就是要被複用,否則封裝函式有毛毛用?而且造成資源浪費,每個函式都佔記憶體空間。

深層次原因:你new出的例項,往往有一個需求:它們需要呼叫、使用、得到同一個物件。

用建構函式生成的例項物件,有一個缺點,就是無法共享屬性和方法。

 

JS創始人Brendan Eich鬼才發明了原型鏈。


四、原型鏈

用建構函式生成例項物件,有一個缺點,就是無法共享屬性和方法。

4.1原型物件和原型鏈查詢

每一個函式天生都有一個屬性(prototype)原型,指向一個物件。當函式被new呼叫時,它產生的每一個例項都會有一個__proto__屬性,也指向這個物件(函式的prototype

函式都有prototype屬性:

function fun(){

}
console.log(fun.prototype)

一般來說,函式的prototype對它自己沒有任何意義,它唯一的意義是兒子們的指明燈,所以,如果這個函式是一個母親(要構造東西,要被new呼叫時),這個原型就太有用了。

function People(name,age,sex){
   this.name = name;
   this.age = age;
   this.sex = sex;
}
var xiaoming = new People("小明",12,"男");
console.log(People.prototype)
console.log(xiaoming.__proto__)
console.log(People.prototype === xiaoming.__proto__); //true

三角關係圖

l 大白話:

你可以認為People建構函式是媽媽,它有一個prototype屬性可以找到孩子它爹,每一個例項都有__proto__屬性,可以找到它爹,他爹有的屬性或方法,孩子都有。

 

l 術語:

l People.prototype是建構函式People的“原型”

l People.prototype是小明例項的“原型物件”

上面這張關係圖,叫“原型鏈”

l 小明例項會沿著原型鏈向上查詢,如果原型有一些屬性和方法,小明例項都有擁有。

一定要記住三角戀:建構函式的prototype屬性指向誰,new出來的例項__proto__就指向誰。這個__proto__有原型鏈查詢功能。

所以,當xiaoming身上沒有某個屬性的時候,系統會沿著__proto__(原型鏈)尋找它的建構函式的原型有沒有這個屬性。

每一個屬性在用.”語法訪問自己的屬性、方法時,都會沿著__proto__尋找,如果自己身上沒有,將訪問自己的原型物件,如果原型物件身上有,此時物件可以打點呼叫這個方法,如同自己的方法一樣。

function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
People.prototype = {
    teacher : "朱老師",
    age : 18,
    sex : "帥哥"
}
var xiaoming = new People("小明",12,"男");
var xiaohong = new People("小紅",13,"女");
var xiaogangpao = new People("小鋼炮",16,"女");
console.log(xiaoming.teacher)
console.log(xiaohong.teacher)
console.log(xiaogangpao.teacher)

 

更改指向:

function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 改變建構函式指向Math物件,自己的例項能呼叫Math物件所有的方法
People.prototype = Math;
var xiaoming = new People("小明",12,"男");
var xiaohong = new People("小紅",13,"女");
var xiaogangpao = new People("小鋼炮",16,"女");
//小明身上雖然沒有random方法,但是小明的父親有,會沿著__proto__去找
console.log(xiaoming.random())
console.log(xiaohong.PI)
console.log(xiaohong.pow(3,4))


4.2方法定義在原型上

function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
//屬性和方法定義在原型上,所有的例項都可以共用一個方法
People.prototype.aaa = 100;
People.prototype.bbb = 200;
People.prototype.sayHello = function(){
    alert(this.name);
};
var xiaoming = new People("小明",12,"男");
var xiaohong = new People("小紅",13,"女");
var xiaogangpao = new People("小鋼炮",16,"女");
console.log(People.prototype)
console.log(xiaoming.aaa)
console.log(xiaoming.bbb)
xiaoming.sayHello()
xiaohong.sayHello()
xiaogangpao.sayHello()

在上面例子中,屬性都是直接在物件自己的身上,而方法在例項的原型物件上(也就是建構函式的原型)

 

 

可以驗證:返回true,表示我們的圖正確的

console.log(xiaoming.sayHello === xiaohong.sayHello); //true

 

今後我們寫物件導向的程式,建立一個類(建構函式),套路如下:

function 建構函式名字(屬性1,屬性2,屬性3,屬性N){
this.屬性1 = 屬性1;
this.屬性2 = 屬性2;
this.屬性3 = 屬性3;
...
}

建構函式名字.prototype.方法1 = function(){}
建構函式名字.prototype.方法2 = function(){}
建構函式名字.prototype.方法3 = function(){}
建構函式名字.prototype.方法4 = function(){}
建構函式名字.prototype.方法5 = function(){}
建構函式名字.prototype.方法N = function(){}

五、練習題

5.1題目:1

var length = 10;
function fn() {
    alert(this.length);
}
var obj = {
    length: 5, 
    haha: function(fn) {
        alert(this === obj); //true
        fn(); //函式fn的上下文是window,彈出10
        fn.call(this); //強制指定上下文為這裡的上下文obj,彈出5
        arguments[0]();//函式fn的this是此時的arguments,彈出1
    }
}
obj.haha(fn);

 

5.2題目2

function haha(){
    var a = (function(){
        return 8;
    })();
    return a;
}
var m = haha();
console.log(m); //8
function getLength(){
    return this.length;
}
function foo(){
    this.length = 1; //全域性的length
    return (function(){ //返回了一個IIFE的執行
        var length = 2;
        return {
            length : function(a,b,c){
                return this.arr.length
            },
            arr : [1,2,3,4],
            info : function(){
             //this == {},此時這裡的this是{}物件
                 return getLength.call(this.length);
            }
        }
    })();
}
var l = foo().info();
console.log(l); //3

答案:3,返回的是上面綠色length函式的形參個數

解釋:foo()函式的執行結果返回了紅色的{}物件,此時foo().info()語句表示紅色{}物件打點呼叫info()函式。所以info函式中的this表示紅色{}物件,而紅色物件的length函式(就是綠色部分),也就是說,getLength.call(this.length);表示藍色函式執行,並且藍色函式this指向綠色函式,所以表示綠色函式的形參長度。


5.3題目3

function A(){
    this.m = 1;
}
function B(){
    this.m = 2;
}
A.call(B);
B.call(A);
var a = new A();
var b = new B();
console.log(a.m == B.m); 
console.log(b.m == A.m); 


5.4題目4

function Fun(){
    this.a = 5;
    function fun(){
        this.a = 10;
    }
    fun.a = 15;
    return fun;
}
var o = new(new Fun())();
console.log(o)
console.log(o.a);  //10

先執行內層new Fun(),裡面有return 引用型別值(四步走失效),所以相當於紅色fun函式被返回,此時紅色fun函式身上有一屬性a15.

外層new ()實際上就是new紅色函式,此時四步走,建立{}物件,給物件新增a屬性為10,然後返回物件給o接收,o.a就是10


 

相關文章