web前端開發筆記整理(五)

JS繼承模式

ECMAScript不支持接口繼承,只支持實現繼承
繼承就是獲取存在對象已有屬性和方法的一種方式.簡單來說,A對象通過繼承 B 對象,就能直接擁有 B 對象的所有屬性和方法。

爲什麼要繼承?

1.傳統模式(原型鏈)

每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針,讓這個原型對象(子的原型)等於要繼承的引用類型(父)的實例,由於引用類型(父)的實例包含一個指向(父)原型對象的內部指針,以此類推,層層遞進,便構成實例與原型的鏈條,即原型鏈。

優點:
將父類的實例作爲子類的原型,可以方便的基礎父類型的原型中的方法;

缺點:
過多的繼承了沒有用的屬性
只執行一次,無法給屬性傳值

//父類型
function Person(name,age,sex){
	this.name=name;
	this.age=age;
	this.sex=sex;
	
}
//子類型
Person.prototype.sayHi=function(){
	console.log('大家好,我是'+this.name);
}
function Student(){
	this.score=100;
	
}
Student.prototype= new Person();
Student.prototype.constructor=Student;

var s1=new Student();
say.sayHi()

在這裏插入圖片描述
在這裏插入圖片描述

屬性的繼承沒有意義
注意點:
(1)給原型添加方法一定要放在替換原型的語句之後
(2)在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法

原型鏈存在的問題:
(1)子類型的所有實例都可以共享父類型的屬性
(2)子類型的實例無法在不影響所有對象的情況下,給父類型的構造函數傳遞參數

2.構造繼承(僞造對象或經典繼承)

在子類型構造函數的內部調用超類型構造函數(通過使用apply()和call()方法也可以在(將來)新創建的對象上執行構造函數);

優點

  • 創建子類實例時,可以向父類傳遞參數
  • 可以實現多繼承
  • 可以方便的繼承父類型的屬性,但是無法繼承原型中的方法

缺點:

  • 實例並不是父類的實例,只是子類的實例

  • 只能借用方法,不能借用原型

  • 無法實現函數複用,每個子類都有父類實例函數的副本,影響性能

Person.prototype.lastname = 'deng';
Person.prototype.hobbit = 'running';
function Person(name,age,sex){
    	this.name = name;
    	this.age = age;
    	this.sex = sex;
    }
    function Student(name,age,sex,tel,grade){
    	// var this = {name:"",age :"", sex:""}
    	Person.call(this,name,age,sex);
    	this.tel = tel;
    	this.grade = grade;
    }
    var student = new Student('sunny',123,'male',139,2017);
    console.log(Student.hobbit)//undefiend
    console.log(Student.lastname)//undefiend

方法都在構造函數中定義,函數複用變得沒有意義

1.就是雖然Student()利用了call實現了對Person()功能的繼承,可是它不能借用構造函數的原型

2.每當需要用call來實現功能的借用的時候,都要去執行函數一次。

3.共享原型(標準)

函數.prototype = 函數.prototype
不能隨便改動自己的原型

Father.prototype.lastName = "Deng";
function Father() {
}
function Son() {
}
Son.prototype = Father.prototype;
var father = new Father();
var son = new Son();
console.log(son.lastName );//Deng

在這裏插入圖片描述

這種共享原型的方式,Father和Son同時指向同一個原型,
·它們共用的一個原型,一個更改了原型上的屬性,其他的都會更改。

現在我們用一個函數來對上面的例子進行封裝

Father.prototype.lastName = "Deng";
function Father() {
}
function Son() {
}
function inherit(Targrt, Origin){
    Targrt.prototype = Origin.prototype;
}
inherit(Son,Father);
Son.prototype.sex = 'male'
var son = new Son()
var father = new Father();

在這裏插入圖片描述

當我們增加Son的原型的時候,Father的原型也就跟着增加了,可是我們並不想增加一個函數的原型的時候,另一個函數的原型也跟着增加,

所以就引出了我們的聖盃模式

聖盃模式

在這裏插入圖片描述
就是構建一個構造函數F,

F.prototype =Father.prototype
Son.prototype = new F();
Father.prototype.lastName = "Deng";
function Father() {}
function Son() {}
function inherit(Target, Origin){
    function F() {} //新建一個空的構造函數
    F.prototype = Origin.prototype;//讓這個空的構造函數(F)的原型指向原始函數(Father)的原型
    Target.prototype = new F();//讓要繼承函數(Son)的原型指向這個空函數的原型,這樣這個要繼承別人的函數添加自己原型屬性,也不會污染那個原始函數。
}
inherit(Son,Father);
var son = new Son()
var father = new Father();
//這樣就形成了Son繼承子F,F繼承自Father。

在這裏插入圖片描述

查看son的構造函數是Father(理應應該是Son),這裏是有一個過程的:
son.proto—>new F(),對象上沒有constructor ,
再往上找 new F().proto---->Father.prototype,
Father.prototype上有constructor,它指向Father,
這樣就指向紊亂了。需要給它歸位。如下:


// JavaScript Document
function Father() {}
function Son() {}
function inherit(Target, Origin){
    function F() {} 
    F.prototype = Origin.prototype;
    Target.prototype = new F();
    
    Target.prototype.constructor = Target //讓對象的constructor 屬性指向自己
}
inherit(Son,Father);
var son = new Son()
var father = new Father();

在這裏插入圖片描述

問題:如果把F.prototype = Origin.prototype;和 Target.prototype = new F();換個位置還能實現這種繼承模式嗎?

function inherit(Target, Origin){
    function F() {} 
    Target.prototype = new F();
    F.prototype = Origin.prototype;
}

這樣就不行了,因爲,new F()的是原來的那個原型,new完了再改讓它指向Origin.prototype已經晚了。

在看一個應用的變量的的小栗子:

function inherit(Target, Origin){
    function F() {} 
    F.prototype = Origin.prototype;
    Target.prototype = new F();    
    Target.prototype.constructor = Target
    Target.prototype.uber = Origin.prototype; 
}
var inherit = (function () {

    var F = function () {};
    return function (Target, Origin){
        F.prototype = Origin.prototype;
        Target.prototype = new F();
        Target.prototype.constructor = Target   
        Target.prototype.uber = Origin.prototype;
    }
}());

這兩種方式下面的一種是閉包的封裝,實現F函數的私有化
var F = function () {};這裏的F函數執行完之後就會銷燬,但是他傳遞原型斷開映射的功能以及實現,他就成爲了私有變量或私有函數
F函數本來就是用來過渡的
(建議以後的聖盃模式就寫成下面這種方式。)

對象枚舉

仿jquery封裝`連續調用`

var jq = {
    a : function () {
        console.log("aaa");
        return this;//在每個函數的末尾返回this對象,可以實現對象連續調用多個方法
        //因爲this就是誰調用這個函數,函數裏面的this就指向誰。
    },
    b : function () {
        console.log("bbb");
        return this;
    },
    c : function () {
        console.log("ccc");
        return this;
    }
}
jq.a().b().c();//aaa bbb ccc

對象訪問屬性

  • 對象.屬性–>obj.name
  • 對象[字符串形式的屬性名]---->obj[‘name’]
stu = {name:xm, age:18};
var age1 = stu.age;
var age2 = stu["age"];
alert(age1 == age2);//true

內部原理:
每當你訪問 obj.name 的時候,系統隱式的訪問的是 obj[‘name’];
直接使用這樣的方式obj[‘name’]更方便,因爲內部就是這樣執行的,訪問速度更快。

示例:實現輸入索引就輸出對應索引屬性名屬性的值(就是實現屬性名的拼接)

var q = {
    a1 :{name : "dong"},
    a2 :{name : "nan"},
    a3 :{name : "xi"},
    a4 :{name : "bei"},
    sayA :function (num){
        return this['a' + num];
        //實現屬性名的拼接,只能用對象['屬性名' + ...]的方式
    } 
}
console.log(q.sayA(1));//Object { name: "dong" }
console.log(q.sayA(2));//Object { name: "nan" }
console.log(q.sayA(3));//Object { name: "xi" }
console.log(q.sayA(4));//Object { name: "bei" }

對象的枚舉

例如給你一組數據,找出每一個數據就是遍歷枚舉

var obj = {
    name : '13',
    age : 89,
    sex : "male",
    height : 180,
    weight : 75
}
for(var prop in obj) {//通過對象的個數控制循環圈數
   console.log(prop + " " + typeof(prop));
   //name string
   //age string
   //sex string
   //height string
   //weight string
      
   console.log(obj.prop); //undefined undefined undefined undefined undefined
 //這個時候系統是把prop當做obj的一個屬性,因爲它沒有這個屬性所以會輸出undefined
   console.log(obj[prop]);
  
}

用obj[prop]就是正確的,這樣就會把它當做一個變量來看
不能加上obj[‘prop’],prop現在本來就是一個字符串

hasOwnProperty 方法

  • hasOwnProperty(),括號裏面傳進去的是要判斷屬性名的字符串形式,返回布爾值
  • hasOwnProperty就是過濾性掉原型上的屬性,留下只屬於自己的屬性
var obj = {
    name : '13',
    age : 89,
    sex : "male",
    height : 180,
    weight : 75,
    __proto__ : {//手動給原型上添加一個屬性
        lastName  : "deng"
  }
}

Object.prototype.abc = "123";
for (var prop in obj){
   for (var prop in obj){
   if(obj.hasOwnProperty(prop)){//過濾性掉原型上的屬性
    console.log(obj[prop]);//13 89 male 180 75
   }   
}
for (var prop in obj){
    //!hasOwnProperty
   if(!obj.hasOwnProperty(prop)){
    console.log(obj[prop]);//deng 123
   }   
}

in屬性(很少用)

  • in 只能判斷這個對象能不能訪問到這個屬性 ,包括原型上的。(父類的也就返回)
  • in 的語法格式就是前面就是這個屬性的字符串形式,不能直接用屬性名
console.log('height' in obj);//true
console.log('lastName' in obj);//true 
console.log(height in obj);//ReferenceError: height is not defined

注意最後一種情況,屬性一定要加雙引號

instanceof 屬性

function Person() {
}
var person = new Person();
// A對象是不是B構造函數構造出來的?
//看A對象的原型鏈上有沒有B的原型
// A instanceof B
console.log(person instanceof Person);//true
console.log(person instanceof Object);//true
console.log({} instanceof Object);//true
console.log(person instanceof Array);//false

區分數組還是對象,var arr = [] || {}?

方法一:判斷constructor方法是否一致
方法二:既然數組是一種對象,那麼數組的原型鏈上一定有對象的原型,而對象的原型鏈上沒有數組的原型,instanceof 方法
方法三:使用Object.prototype.toString.call([]),將[]數組按照Object的原型的toString的方法打印

var arr = [] || {};//區分是數組還是對象
//1,用constructor方法:[].constructor是function Array(), obj.constructor是 function Object()
//2,用instanceof方法:[] instanceof Array是true, obj instanceof Array是 false
//3,用toString,各個構造函數重寫了toString方法

Object.prototype.toString.call([]);
//誰調用this,this就是誰
Object.prototype.toString = function () {
    // 識別this
    // 返回相應的結果
}
// obj.toString();

命名空間

var name="bcd";
var init=(function(){
	var name="abc";
	function callName(){
		console.log(name);
		
	}
	return function(){
		callName();
	}
}())
init();

輸出結果是abc

在外面定義的name與內部name不互相影響,函數在外部調用內部函數函數形成了閉包,init()代表callname()功能,變量私有化不會污染全局變量。

習題穿插

第一題
Question: javaScript中定義函數的方式有_______, ______, _______.
1,函數聲明 2,函數的表達式 3,Function構造器

 var fn = new Function('var a = 10;console.log(a)');
fn();
 var fn1 = function(){
 var a  = 10 ;
 console.log(a);
      }
   funciton Function(){}  產生匿名的函數
   // var obj = new Object()

第二題

   var value = "web"
      function bar() {
        var value = "duyi";

        function foo() {
            console.log(value);
        }
        return foo;
    }
    bar()()//變成了 (function foo(){ console.log(value)})()
    

問輸出什麼
duyi

第三題
Question: 實現一個函數addCounter,用於返回累加n操作的函數

function add(a) {
    function sum(b) { // 使用閉包
        a = a + b; // 累加
        return sum;
    }
    sum.toString = function() { // 重寫toString()方法,返回sum值
        return a;
    }
    return sum; // 返回一個函數
}

在這裏插入圖片描述

習題:

在這裏插入圖片描述
輸出什麼?
function裏面變成表達式會立刻執行, 整個函數轉換成true,f已經是undefined,在執行x+=typeof f;這時候f已經沒得了,但惟獨放在typeof裏面不報錯,1+undefined就是undefined。
輸出undefined

this

this指向的幾種情況:

函數預編譯過程 this 指向 window

function test(c){
    // var this = Object.create(test.prototype);第一種最標準的隱式this的寫法
    //var this = {                              第二種方法
    //     __proto__ : test.prototype
    // }
    var a = 123;
    function b() {}
}
//預編譯階段生成AO對象,
//AO對象不僅有變量的聲明提升和函數聲明提升,還有arguments實參列表和this指向window
// AO {
//     arguments : [1],
//     this : window,
//     c : 1,
//     a : undefined,
//     b : function () {}
// }
 test(1);
 
 new test();

當new一個對象的時候,調用函數的時候,
在函數內部生成一個隱式的this對象(var this = Object.create(test.prototype);)去覆蓋AO裏面的this

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

打印this其實就是window,打印window和它是一樣的。
全局作用域裏 this 指向 window

在全局作用域也就是GO對象裏面也有一個this,它自然是指向window的。
在這裏插入圖片描述call/appy可以改變函數運行時的this指向
obj.function(), function裏面的this指向obj

var obj = {
    a : function () {
        console.log(this.name);
    },
    name : 'abc'
}
obj.a();//abc 

this筆試題

var name = "222";
var a = {
    name : "111",
    say  :function () {
        console.log(this.name);
    }
}
var fun = a.say;
fun()
a.say()
var b = {
    name : "333",
    say : function (fun) {
        fun();
    }
}
b.say(a.say);
b.say = a.say;
b.say();

分析:

var name = "222";
var a = {
    name : "111",
    say  :function () {
        console.log(this.name);
    }
}
var fun = a.say;//a.say代表函數的引用,代表函數體
fun();//就是把
// function () {
//         console.log(this.name);
//     }放到全局範圍內執行,也沒人調用它,所以this指向window,所以打印222

a.say()// 這個就是對象調用這個方法,this指向對象a,打印的就會是111
var b = {
    name : "333",
    say : function (fun) {
 
        //this---->b,333
        fun();
    }
}
b.say(a.say);//b.say,對象b調用它的say方法,現在b的say方法裏面的this指向b
//然後裏面放進去一個a.say,也就是把function () {
//         console.log(this.name);
//     }放到b的say函數裏面執行,這個函數執行只不過是在另外一個函數裏面,也沒人調用它,這個時候走的就是預編譯的環節,this它就指向window,打印出來的就是222

b.say = a.say;//把a.say放到b.say裏面,那就是var b = {
                                        //     name : "333",
                                        //     say : function () {
                                        //          console.log(this.name);
                                        //     }
                                        // }
b.say();//再調用b.say方法當然是this指向自己,打印333

arguments

  • arguments 是一個類似數組的對象, 對應於傳遞給函數的參數。
    找到最大的一個參數的值:
x = findMax(1, 123, 500, 115, 44, 88);
 
function findMax() {
    var i, max = arguments[0];
    
    if(arguments.length < 2) return max;
 
    for (i = 0; i < arguments.length; i++) {
        if (arguments[i] > max) {
            max = arguments[i];
        }
    }
    return max;
}

創建一個函數用來統計所有數值的和:

x = sumAll(1, 123, 500, 115, 44, 88);
 
function sumAll() {
    var i, sum = 0;
    for (i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}

arguments.callee

function test{
console.log(arguments.callee==test)
}
test();

打印結果是true
callee是arguments對象的屬性。在函數體內,它指向當前正在執行的函數。

實現10的階乘

var num=(function (n){
	if (n==1){
		return 1;
	}
	return n * arguments.callee(n-1);
}(10))
function test() {
    conso.log(arguments.callee);//打印test函數
    function demo () {
        conso.log(arguments.callee);//打印demo函數
    }
    demo();
}
test();

就是說在哪個函數裏面的arguments.callee就指向那個函數的引用。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章