javascript 內置了許多 function 函數(){...}javascript
js 執行首先就會執行本身內置的函數定義 (function Function、function Object)html
對象的繼承java
大部分面向對象的編程語言,都是經過「類」(class)實現對象的繼承。c++
傳統上,JavaScript 語言的繼承不經過 class,而是經過「原型對象」(prototype)實現,稱之爲 JavaScript 的原型鏈繼承面試
JavaScript 繼承機制的設計思想就是,原型對象 prototype 的全部屬性和方法,都能被實例對象共享編程
ES6 引入了 class 語法數組
function Cat(name, color) { this.name = name; this.color = color; this.bar = function () { console.log('喵喵'); }; } var cat1 = new Cat('大毛', '白色'); var cat2 = new Cat('二毛', '黑色'); cat1.meow === cat2.meow // false
cat1
和 cat2
是同一個構造函數的兩個實例,它們都具備 bar 方法。因爲 bar 方法是生成在每一個實例對象上面,因此兩個實例就生成了兩次。瀏覽器
prototype
屬性,指向一個對象。
function Animal(name) { this.name = name; } Animal.prototype.color = 'white'; var cat1 = new Animal('大毛'); var cat2 = new Animal('二毛'); cat1.color // 'white' cat2.color // 'white'
讀取屬性/方法,沿着原型鏈找安全
設置屬性/ 方法,只會查看和影響自身編程語言
全部函數都具備 prototype 顯式原型屬性,指向一個對象____原型對象
全部對象都是某個構造函數的實例,都擁有 __proto__隱式原型屬性
注意:
constructor 構造函數____等於函數自己
__proto__ 隱式原型屬性____指向 該對象的構造函數的原型對象 prototype (即 隱式原型屬性 指向 上一級對象的原型對象)
即 同一構造函數的 全部實例對象 都有一個 隱式原型 指向同一個原型對象
全部函數都是 function Function(){...} 的實例
/* 面試題 */ var A = function() { } A.prototype.n = 1 var b = new A() A.prototype = { // 改變的 只是一個地址值,而不會改變 真正對象 的存在(b 始終指向那個 原始的 prototype 對象) n: 2, m: 3 } var c = new A() console.log(b.n, b.m, c.n, c.m)
function Constr() {} var x = new Constr(); var y = new x.constructor(); y instanceof Constr; // true
這使得在實例方法中,調用自身的構造函數成爲可能
Constr.prototype.createCopy = function () { return new this.constructor(); };
function Foo() {} var f = new Foo(); f.constructor.name // "Foo"
全部 函數 都是 function Function(){} 的實例對象,
包括 Object 的構造函數 的 __proto__ 都指向 Function 的原型對象
console.log(Object instanceof Function); // true console.log(Object instanceof Object); // true console.log(Function instanceof Function); // true console.log(Function instanceof Object); // true
var v = new Vehicle(); v instanceof Vehicle; // true
實例對象 instanceOf 構造函數
v instanceof Vehicle // 等同於 Vehicle.prototype.isPrototypeOf(v)
null
對象。這時,instanceof
判斷會失真
var obj = Object.create(null); typeof obj; // "object" Object.create(null) instanceof Object; // false
所以,只要一個對象的原型不是null,instanceof運算符的判斷就不會失真。
var x = [1, 2, 3]; var y = {}; x instanceof Array // true y instanceof Object // true
注意,instanceof 運算符只能用於對象,不適用原始類型的值。
function Fubar (foo, bar) { if (this instanceof Fubar) { this._foo = foo; this._bar = bar; } else { return new Fubar(foo, bar); } }
讓一個構造函數繼承另外一個構造函數,是很是常見的需求。這能夠分紅兩步實現
function Sub(value) { Super.call(this); Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub; this.name = value; Sub.prototype.method = '...'; }
另一種寫法是 Sub.prototype
等於一個父類實例,可是子類會具備父類實例的方法。有時,這可能不是咱們須要的,因此不推薦使用這種寫法
Sub.prototype = new Super();
ClassB.prototype.print = function() { ClassA.prototype.print.call(this); // some code }
這就等於繼承了父類A的 print 方法
/**** 旨在實現封裝的前提下,最少佔用內存 ****/ // 封裝 父類 function Parent(name, age){ this.name = name; this.age = age; }; Parent.prototype.setName = function(name){ this.name = name; }; Parent.prototype.setAge = function(age){ this.age = age; }; // 封裝 子類 function Child(name, age){ Parent.call(this, name, age); // 繼承父類的屬性 this.isCrying = false; }; Child.prototype = new Parent(); // 繼承父類的方法 Child.prototype.constructor = Child; // 修正構造器指向
function M1() {
this.hello = 'hello';
}
function M2() {
this.world = 'world';
}
function S() {
M1.call(this);
// 繼承 M1
S.prototype = Object.create(M1.prototype);
M2.call(this);
// 繼承鏈上加入 M2
Object.assign(S.prototype, M2.prototype);
// 指定構造函數
S.prototype.constructor = S;
}
var s = new S();
s.hello // 'hello'
s.world // 'world'
子類 S 同時繼承了父類 M1 和 M2 。這種模式又稱爲 Mixin(混入)
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });
function StringBuilder() { var buffer = []; this.add = function (str) { buffer.push(str); }; this.toString = function () { return buffer.join(''); }; }
function StringBuilder() { this._buffer = []; StringBuilder.prototype = { constructor: StringBuilder, add: function (str) { this._buffer.push(str); }, toString: function () { return this._buffer.join(''); } }; }
以上代碼將私有變量放入實例對象中,好處是看上去更天然,可是它的私有變量能夠從外部讀寫,不是很安全
var module1 = (function () { var age = 0; var getAge = function () { return this.age; }; var setAge = function (age) { this.age = age; }; return { getAge : getAge, setAge : setAge }; })();
使用上面的寫法,外部代碼沒法直接獲取內部的_count
變量。
var module1 = (function (mod){ mod.m3 = function () { //... }; return mod; })(module1);
上面的代碼爲 module1 模塊添加了一個新方法 m3(),而後返回新的 module1 模塊。
var module1 = (function (mod) { //... return mod; })(window.module1 || {});
與"放大模式"相比,「寬放大模式」就是「當即執行函數」的參數能夠是空對象
var module1 = (function ($, YAHOO) { //... })(jQuery, YAHOO);
上面的module1
模塊須要使用 jQuery 庫和 YUI 庫,就把這兩個庫(實際上是兩個模塊)看成參數輸入module1
。
這樣作除了保證模塊的獨立性,還使得模塊之間的依賴關係變得明顯
(function($, window, document) { function go(num) { } function handleEvents() { } function initialize() { } function dieCarouselDie() { } //attach to the global scope window.finalCarousel = { init : initialize, destroy : dieCouraselDie } })( jQuery, window, document );
上面代碼中,finalCarousel 對象輸出到全局,對外暴露 init() 和 destroy() 接口,
內部方法 go()、handleEvents()、initialize()、dieCarouselDie() 都是外部沒法調用的。