目錄
- 構造函數和原型
- 構造函數
- 實例成員和靜態成員
- 構造函數的問題
- 構造函數原型 prototype
- 對象原型 \_\_proto\_\_
- constructor 構造函數
- 構造函數、實例、原型對象三者之間的關系
- 原型鏈
- JavaScript 的成員查找機制(規則)
- 原型對象的this指向
- 擴展內置對象
- 繼承
- call()
- 借用構造函數繼承父類型屬性
- 借用原型對象繼承父類型方法
- 類的本質
- ES5中新增的方法
- 數組方法
- 字符串方法
- Object.defineProperty方法
- 對象方法
構造函數和原型
構造函數
構造函數是一種特殊的函數,主要用來初始化對象,即為對象成員變量賦初始值,它總與new一起使用。我們可以把對象中一些公共的屬性和方法抽取出來,然后封裝到這個函數里面。
值得注意的點:
- 構造函數用于創建某一類對象,其首字母要大寫
- 構造函數要和new一起使用才有意義
new在執行時會做如下四件事情:
- 在內存中創建一個新的空對象
- 讓this指向這個新的對象
- 執行構造函數里面的代碼,給這個新對象添加屬性和方法。
- 返回這個新對象(所以構造函數里面不需要return)
實例成員和靜態成員
構造函數中的屬性和方法稱為成員,JavaScript的構造函數中可以添加一些成員,可以在構造函數本身上添加,也可以在構造函數內部的this上添加。通過這兩種方式添加的成員,就分別稱為靜態成員和實例成員。
- 靜態成員:在構造函數本身添加的成員稱為靜態成員,只能由構造函數本身來訪問
- 實例成員:在構造函數內部創建的對象成員稱為實例成員,只能由實例化的對象來訪問
構造函數的問題
構造函數方法很好用,但是存在浪費內存的問題。
class Student{constructor(sname,sgender){this.sname = sname;this.sgender = sgender;this.sayHello = function(){console.log("你好我是"+this.sname+",我的性別是"+this.sgender);}}}
var s1 = new Student("張三","男");
var s2 = new Student("李四","女");
s1.sayHello();//你好我是張三,我的性別是男
s2.sayHello(); //你好我是李四,我的性別是女
上面的代碼中每一個實例化對象都會對應一個內存空間但是使用的是同樣的函數,存在內存浪費。
我們希望所有的對象使用同一個函數,這樣就比較節省內存,那要怎么做呢。
構造函數原型 prototype
構造函數通過原型分配的函數是所有對象所共享的
JavaScript規定,每一個構造函數都有一個prototype屬性,指向另一個對象。注意這個prototype就是一個對象,這個對象的所有屬性和方法,都會被構造函數所擁有。
function Student(sname, sgender) {this.sname = sname;this.sgender = sgender;
}Student.prototype.sayHello = function () {console.log("你好我是" + this.sname + ",我的性別是" + this.sgender);
}var s1 = new Student("張三", "男");
var s2 = new Student("李四", "女");
s1.sayHello(); // 你好我是張三,我的性別是男
s2.sayHello(); // 你好我是李四,我的性別是女
對象原型 __proto__
對象都會有一個屬性__proto__
指向構造函數的prototype原型對象,之所以我們對象可以使用構造函數prototype原型對象的屬性方法,就是因為對象有__proto__原型的存在。
- __proto__對象原型和原型對象prototype是等價的
- __proto__對象原型的意義就在于為對象的查找機制提供一個方向,或者說一條路線,但是它是一個非標準屬性,因此實際開發中,不可以使用這個屬性,它只是內部指向原型對象prototype
console.log(s1.__proto__ === Student.prototype); // true
constructor 構造函數
對象原型(__proto__)和構造函數(prototype)原型對象里面都有一個屬性constructor
屬性,constructor我們稱為構造函數,因為它指回構造函數本身。
constructor主要用于記錄該對象引用于哪個構造函數,它可以讓原型對象重新指向原來的構造函數。
如果我們修改了原來的原型對象,給原型對象賦值的是一個對象,則必須手動的利用construct指回原來的構造函數。
function Student(sname, sgender) {this.sname = sname;this.sgender = sgender;
}Student.prototype = {constructor: Student,// 修復構造函數指向問題sayHello: function () {console.log("你好我是" + this.sname + ",我的性別是" + this.sgender);},sayBye: function () {console.log("再見!");}
}
var s1 = new Student("張三", "男");
s1.sayHello(); // 你好我是張三,我的性別是男
s1.sayBye(); // 再見!
構造函數、實例、原型對象三者之間的關系
原型鏈
每個構造函數都有 prototype 屬性,該屬性指向其原型對象。原型對象的__proto__指向 Object.prototype,也就是 Object 的原型對象。Object.prototype 由 Object 構造函數創建,它是所有對象原型鏈的終點,其__proto__為 null,意味著原型鏈到此終止。
JavaScript 的成員查找機制(規則)
- 當訪問一個對象的屬性(包括方法)時,首先查找這個對象自身有沒有該屬性
- 如果沒有就查找它的原型(也就是__proto__指向的prototype原型對象)
- 如果還沒有就查找原型對象的原型(Object的原型對象)
- 以此類推一直找到Object為止(null)
- __proto__對象原型的意義就是在于為對象成員查找機制提供一個方向或者說一條路線
原型對象的this指向
通過如下的代碼可以得出結論:無論是構造函數中的this還是原型對象函數中的this指向的都是對象實例
function Student(sname, sgender) {this.sname = sname;this.sgender = sgender;
}var that;
Student.prototype.sayHello = function () {console.log("你好我是" + this.sname + ",我的性別是" + this.sgender);that = this;
}
var s1 = new Student("張三", "男");
// 在構造函數中,里面this指向的是對象實例s1
s1.sayHello(); // 你好我是張三,我的性別是男
console.log(that === s1); //true
// 原型對象函數里面的this指向的是對象實例s1
擴展內置對象
可以通過原型對象,對原來的內置對象進行擴展自定義的方法。比如給數組增加自定義求和的功能。
注意:數組和字符串內置對象不能給原型對象覆蓋操作Array.prototype={},只能是Array.prototype.xxx=function(){}的方式。
Array.prototype.sum = function () {var sum = 0;for (var i = 0; i < this.length; i++) {sum += this[i];}return sum;
}
var arr = [1, 2, 3, 4, 5];
console.log(arr.sum()); // 15
var arr1 = new Array(1, 2, 3, 4, 5);
console.log(arr1.sum()); // 15
繼承
ES6之前并沒有給我們提供extends繼承。我們可以通過構造函數+原型對象模擬實現繼承,被稱為組合繼承。
call()
調用這個函數,并修改函數運行時this指向
fun.call(thisArg,arg1,arg2,...)
- thisArg:當前調用函數this的指向對象
- arg1,arg2:傳遞的其他參數
function fn() {console.log("hello world");console.log(this);
}
var obj = {name: "obj"
};
// 1.call() 可以調用函數
fn.call(); // window{...}
// 2.call() 可以改變函數的this指向
fn.call(obj); // obj{name: "obj"}
借用構造函數繼承父類型屬性
核心原理:通過call()把父類型的this指向子類型的this,這樣就可以實現子類型繼承父類型的屬性。
//借用父構造函數繼承屬性
//1.父構造函數
function Parent(name, age) {//this指向父構造函數的實例對象this.name = name;this.age = age;
}
//2.子構造函數
function Child(name, age) {//this指向子構造函數的實例對象Parent.call(this, name, age); //借用父構造函數繼承屬性// Parent.apply(this, arguments); //借用父構造函數繼承屬性,使用apply可以傳入任意數量的參數
}
//3.創建子構造函數的實例對象
var child = new Child('小明', 18);
console.log(child.name); //小明
console.log(child.age); //18
借用原型對象繼承父類型方法
//1.父構造函數
function Parent(name, age) {this.name = name;this.age = age;
}
Parent.prototype.money = function () {console.log(100000);
}//2.子構造函數
function Child(name, age) {Parent.call(this, name, age); //借用父構造函數繼承屬性
}
// Child.prototype = Parent.prototype; //這樣直接賦值會有問題,如果修改了子原型對象,如果修改了子原型對象,父原型對象也會被修改
Child.prototype = new Parent(); //借用父構造函數的原型對象
//如果利用對象的形式修改了原型對象,別忘了利用constructor指回原來的構造函數
Child.prototype.constructor = Child;
//3.創建子構造函數的實例對象
var child = new Child('小明', 18);
console.log(child.name); //小明
console.log(child.age); //18
child.money(); //100000
類的本質
- class本質還是function
- 類的所有方法都定義在類的prototype屬性上
- 類創建的實例,里面也有__proto__指向類的prototype原型對象
- 所以ES6的類它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已
- 所以ES6的類其實就是語法糖
class Student{}
//1. 類的本質其實還是一個函數 我們也可以簡單的認為類就是構造函數的另外一種寫法
console.log(typeof Student);// "function"
//(1).類有原型對象prototype
console.log(Student.prototype);// {constructor: ?}
//(2).類原型對象prototype里面有constructor指向類本身
console.log(Student.prototype.constructor);// ? Student() { [native code] }
//(3).類可以通過原型對象添加方法
Student.prototype.sayHello = function(){console.log("Hello, I am a student.");
}
var s = new Student();
console.dir(s);
//(4)構造函數創建的實例對象有__proto__屬性指向類原型對象prototype
console.log(s.__proto__ === Student.prototype);
ES5中新增的方法
ES5中給我們新增了一些方法,可以很方便的操作數組或者字符串
數組方法
迭代(遍歷)方法:forEach()、map()、filter()、some()、every()
-
forEach()方法
- 用于遍歷數組中的每個元素
- 不會改變原數組
- 默認三個回調參數為value、index、array
- 示例:
[1,2,3].forEach(function(value){console.log(value)})
-
map()方法
- 對數組中的每個元素執行回調函數
- 返回一個新數組
- 示例:
[1,2,3].map(function(value){return value*2})
返回[2,4,6]
-
filter()方法
- 篩選數組中符合條件的元素
- 返回一個新數組
- 示例:
[1,2,3].filter(function(value){return value>1})
返回[2,3]
-
some()方法
- 檢測數組中是否有元素滿足條件
- 返回布爾值
- 示例:
[1,2,3].some(function(value){return value>2})
返回true
-
every()方法
- 檢測數組中的所有元素是否都滿足條件
- 返回布爾值
- 示例:
[1,2,3].every(function(value){return value>0})
返回true
字符串方法
trim()方法
從一個字符串的兩端刪除空白字符
Object.defineProperty方法
Object.defineProperty(obj, prop ,descriptor)
- obj:必需,目標對象
- prop:必需,需定義或修改的屬性的名字
- descriptor:必需,目標屬性所擁有的特性
第三個參數descriptor說明:以對象形式{ }書寫 - value:設置屬性的值,默認為undefined
- weitable:值是否可以重寫。true|false 默認為false
- enumerable:目標屬性是否可以被枚舉 true|false 默認為false
- configurable:目標屬性是否可以被刪除或是否可以再次修改特性 true|false 默認為false
var obj = {name: 'zhangsan',age: 20
};Object.defineProperty(obj, 'gender', {value: 'male',writable: true,enumerable: true,configurable: true
});console.log(obj); // {name: "zhangsan", age: 20, gender: "male"}
對象方法
- Object.keys()用于獲取對象自身所有的屬性
object.keys(obj)
- 效果類似for…in
- 返回一個由屬性名組成的數組