<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          10種JavaScript代碼復(fù)用模式

          共 11267字,需瀏覽 23分鐘

           ·

          2020-09-25 10:00

          作者 |?湯姆大叔

          前言
          任何編程都提出代碼復(fù)用,否則話每次開發(fā)一個新程序或者寫一個新功能都要全新編寫的話,那就歇菜了。
          但是代碼復(fù)用也是有好要壞,今天這篇文章我們將針對代碼復(fù)用來進(jìn)行討論,里面一種介紹了10種模式,其中前面4種模式是推薦使用的,是推薦大家使用的模式,一般不會有什么問題。
          后面6種模式是要盡量避免使用的,因?yàn)楹竺?種模式或多或少會帶來一些問題。
          下面我們就進(jìn)入今天的內(nèi)容吧。

          模式1:原型繼承

          原型繼承是讓父對象作為子對象的原型,從而達(dá)到繼承的目的:
          function object(o) { function F() { }
          F.prototype = o; return new F();}
          // 要繼承的父對象var parent = { name: "Papa"};
          // 新對象var child = object(parent);
          // 測試console.log(child.name); // "Papa"

          // 父構(gòu)造函數(shù)function Person() { // an "own" property this.name = "Adam";}// 給原型添加新屬性Person.prototype.getName = function () { return this.name;};// 創(chuàng)建新personvar papa = new Person();// 繼承var kid = object(papa);console.log(kid.getName()); // "Adam"

          // 父構(gòu)造函數(shù)function Person() { // an "own" property this.name = "Adam";}// 給原型添加新屬性Person.prototype.getName = function () { return this.name;};// 繼承var kid = object(Person.prototype);console.log(typeof kid.getName); // "function",因?yàn)槭窃谠屠锒x的console.log(typeof kid.name); // "undefined", 因?yàn)橹焕^承了原型
          同時,ECMAScript5也提供了類似的一個方法叫做Object.create用于繼承對象,用法如下:
          /* 使用新版的ECMAScript 5提供的功能 */var child = Object.create(parent);
          var child = Object.create(parent, { age: { value: 2} // ECMA5 descriptor});console.log(child.hasOwnProperty("age")); // true
          而且,也可以更細(xì)粒度地在第二個參數(shù)上定義屬性:
          // 首先,定義一個新對象manvar man = Object.create(null);
          // 接著,創(chuàng)建包含屬性的配置設(shè)置// 屬性設(shè)置為可寫,可枚舉,可配置var config = { writable: true, enumerable: true, configurable: true};
          // 通常使用Object.defineProperty()來添加新屬性(ECMAScript5支持)// 現(xiàn)在,為了方便,我們自定義一個封裝函數(shù)var defineProp = function (obj, key, value) { config.value = value; Object.defineProperty(obj, key, config);}
          defineProp(man, 'car', 'Delorean');defineProp(man, 'dob', '1981');defineProp(man, 'beard', false);
          所以,繼承就這么可以做了:
          var driver = Object.create( man );defineProp (driver, 'topSpeed', '100mph');driver.topSpeed // 100mph
          但是有個地方需要注意,就是Object.create(null)創(chuàng)建的對象的原型為undefined,也就是沒有toStringvalueOf方法,所以alert(man);的時候會出錯,但alert(man.car);是沒問題的。

          模式2:復(fù)制所有屬性進(jìn)行繼承

          這種方式的繼承就是將父對象里所有的屬性都復(fù)制到子對象上,一般子對象可以使用父對象的數(shù)據(jù)。
          先來看一個淺拷貝的例子:
          /* 淺拷貝 */function extend(parent, child) { var i; child = child || {}; for (i in parent) { if (parent.hasOwnProperty(i)) { child[i] = parent[i]; } } return child;}
          var dad = { name: "Adam" };var kid = extend(dad);console.log(kid.name); // "Adam"
          var dad = { counts: [1, 2, 3], reads: { paper: true }};var kid = extend(dad);kid.counts.push(4);console.log(dad.counts.toString()); // "1,2,3,4"console.log(dad.reads === kid.reads); // true
          代碼的最后一行,你可以發(fā)現(xiàn)dad和kid的reads是一樣的,也就是他們使用的是同一個引用,這也就是淺拷貝帶來的問題。
          我們再來看一下深拷貝:
          /* 深拷貝 */function extendDeep(parent, child) { var i, toStr = Object.prototype.toString, astr = "[object Array]";
          child = child || {};
          for (i in parent) { if (parent.hasOwnProperty(i)) { if (typeof parent[i] === 'object') { child[i] = (toStr.call(parent[i]) === astr) ? [] : {}; extendDeep(parent[i], child[i]); } else { child[i] = parent[i]; } } } return child;}
          var dad = { counts: [1, 2, 3], reads: { paper: true }};var kid = extendDeep(dad);
          kid.counts.push(4);console.log(kid.counts.toString()); // "1,2,3,4"console.log(dad.counts.toString()); // "1,2,3"
          console.log(dad.reads === kid.reads); // falsekid.reads.paper = false;
          深拷貝以后,兩個值就不相等了,bingo!

          模式3:混合(mix-in)

          混入就是將一個對象的一個或多個(或全部)屬性(或方法)復(fù)制到另外一個對象,我們舉一個例子:
          function mix() { var arg, prop, child = {}; for (arg = 0; arg < arguments.length; arg += 1) { for (prop in arguments[arg]) { if (arguments[arg].hasOwnProperty(prop)) { child[prop] = arguments[arg][prop]; } } } return child;}
          var cake = mix( { eggs: 2, large: true }, { butter: 1, salted: true }, { flour: '3 cups' }, { sugar: 'sure!' } );
          console.dir(cake);
          mix函數(shù)將所傳入的所有參數(shù)的子屬性都復(fù)制到child對象里,以便產(chǎn)生一個新對象。
          那如何我們只想混入部分屬性呢?該個如何做?其實(shí)我們可以使用多余的參數(shù)來定義需要混入的屬性,例如mix(child,parent,method1,method2)這樣就可以只將parent里的method1和method2混入到child里。上代碼:
          // Car var Car = function (settings) { this.model = settings.model || 'no model provided'; this.colour = settings.colour || 'no colour provided';};
          // Mixinvar Mixin = function () { };Mixin.prototype = { driveForward: function () { console.log('drive forward'); }, driveBackward: function () { console.log('drive backward'); }};

          // 定義的2個參數(shù)分別是被混入的對象(reciving)和從哪里混入的對象(giving)function augment(receivingObj, givingObj) { // 如果提供了指定的方法名稱的話,也就是參數(shù)多余3個 if (arguments[2]) { for (var i = 2, len = arguments.length; i < len; i++) { receivingObj.prototype[arguments[i]] = givingObj.prototype[arguments[i]]; } } // 如果不指定第3個參數(shù),或者更多參數(shù),就混入所有的方法 else { for (var methodName in givingObj.prototype) { // 檢查receiving對象內(nèi)部不包含要混入的名字,如何包含就不混入了 if (!receivingObj.prototype[methodName]) { receivingObj.prototype[methodName] = givingObj.prototype[methodName]; } } }}
          // 給Car混入屬性,但是值混入'driveForward' 和 'driveBackward'*/augment(Car, Mixin, 'driveForward', 'driveBackward');
          // 創(chuàng)建新對象Carvar vehicle = new Car({ model: 'Ford Escort', colour: 'blue' });
          // 測試是否成功得到混入的方法vehicle.driveForward();vehicle.driveBackward();
          該方法使用起來就比較靈活了。

          模式4:借用方法

          一個對象借用另外一個對象的一個或兩個方法,而這兩個對象之間不會有什么直接聯(lián)系。不用多解釋,直接用代碼解釋吧:
          var one = { name: 'object', say: function (greet) { return greet + ', ' + this.name; }};
          // 測試console.log(one.say('hi')); // "hi, object"
          var two = { name: 'another object'};
          console.log(one.say.apply(two, ['hello'])); // "hello, another object"
          // 將say賦值給一個變量,this將指向到全局變量var say = one.say;console.log(say('hoho')); // "hoho, undefined"
          // 傳入一個回調(diào)函數(shù)callbackvar yetanother = { name: 'Yet another object', method: function (callback) { return callback('Hola'); }};console.log(yetanother.method(one.say)); // "Holla, undefined"
          function bind(o, m) { return function () { return m.apply(o, [].slice.call(arguments)); };}
          var twosay = bind(two, one.say);console.log(twosay('yo')); // "yo, another object"

          // ECMAScript 5給Function.prototype添加了一個bind()方法,以便很容易使用apply()和call()。
          if (typeof Function.prototype.bind === 'undefined') { Function.prototype.bind = function (thisArg) { var fn = this,slice = Array.prototype.slice,args = slice.call(arguments, 1); return function () { return fn.apply(thisArg, args.concat(slice.call(arguments))); }; };}
          var twosay2 = one.say.bind(two);console.log(twosay2('Bonjour')); // "Bonjour, another object"
          var twosay3 = one.say.bind(two, 'Enchanté');console.log(twosay3()); // "Enchanté, another object"

          模式5:默認(rèn)模式

          代碼復(fù)用大家常用的默認(rèn)模式,往往是有問題的,該模式使用Parent()的構(gòu)造函數(shù)創(chuàng)建一個對象,并且將該對象賦值給Child()的原型。我們看一下代碼:
          function inherit(C, P) { C.prototype = new P();}
          // 父構(gòu)造函數(shù)function Parent(name) { this.name = name || 'Adam';}// 給原型添加say功能Parent.prototype.say = function () { return this.name;};// Child構(gòu)造函數(shù)為空function Child(name) {}
          // 執(zhí)行繼承inherit(Child, Parent);
          var kid = new Child();console.log(kid.say()); // "Adam"
          var kiddo = new Child();kiddo.name = "Patrick";console.log(kiddo.say()); // "Patrick"
          // 缺點(diǎn):不能讓參數(shù)傳進(jìn)給Child構(gòu)造函數(shù)var s = new Child('Seth');console.log(s.say()); // "Adam"
          這種模式的缺點(diǎn)是Child不能傳進(jìn)參數(shù),基本上也就廢了。

          模式6:借用構(gòu)造函數(shù)

          該模式是Child借用Parent的構(gòu)造函數(shù)進(jìn)行apply,然后將child的this和參數(shù)傳遞給apply方法:
          // 父構(gòu)造函數(shù)function Parent(name) { this.name = name || 'Adam';}
          // 給原型添加say功能Parent.prototype.say = function () { return this.name;};
          // Child構(gòu)造函數(shù)function Child(name) { Parent.apply(this, arguments);}
          var kid = new Child("Patrick");console.log(kid.name); // "Patrick"
          // 缺點(diǎn):沒有從構(gòu)造函數(shù)上繼承say方法console.log(typeof kid.say); // "undefined"
          缺點(diǎn)也很明顯,say方法不可用,因?yàn)闆]有繼承過來。

          模式7:借用構(gòu)造函數(shù)并設(shè)置原型

          上述兩個模式都有自己的缺點(diǎn),那如何把兩者的缺點(diǎn)去除呢,我們來嘗試一下:
          // 父構(gòu)造函數(shù)function Parent(name) { this.name = name || 'Adam';}
          // 給原型添加say功能Parent.prototype.say = function () { return this.name;};
          // Child構(gòu)造函數(shù)function Child(name) { Parent.apply(this, arguments);}
          Child.prototype = new Parent();
          var kid = new Child("Patrick");console.log(kid.name); // "Patrick"console.log(typeof kid.say); // functionconsole.log(kid.say()); // Patrickconsole.dir(kid);delete kid.name;console.log(kid.say()); // "Adam"
          運(yùn)行起來,一切正常,但是有沒有發(fā)現(xiàn),Parent構(gòu)造函數(shù)執(zhí)行了兩次,所以說,雖然程序可用,但是效率很低。

          模式8:共享原型

          共享原型是指Child和Parent使用同樣的原型,代碼如下:
          function inherit(C, P) { C.prototype = P.prototype;}
          // 父構(gòu)造函數(shù)function Parent(name) { this.name = name || 'Adam';}
          // 給原型添加say功能Parent.prototype.say = function () { return this.name;};
          // Child構(gòu)造函數(shù)function Child(name) {}
          inherit(Child, Parent);
          var kid = new Child('Patrick');console.log(kid.name); // undefinedconsole.log(typeof kid.say); // functionkid.name = 'Patrick';console.log(kid.say()); // Patrickconsole.dir(kid);
          確定還是一樣,Child的參數(shù)沒有正確接收到。

          模式9:臨時構(gòu)造函數(shù)

          首先借用構(gòu)造函數(shù),然后將Child的原型設(shè)置為該借用構(gòu)造函數(shù)的實(shí)例,最后恢復(fù)Child原型的構(gòu)造函數(shù)。代碼如下:
          /* 閉包 */var inherit = (function () { var F = function () { }; return function (C, P) { F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; }} ());
          function Parent(name) { this.name = name || 'Adam';}
          // 給原型添加say功能Parent.prototype.say = function () { return this.name;};
          // Child構(gòu)造函數(shù)function Child(name) {}
          inherit(Child, Parent);
          var kid = new Child();console.log(kid.name); // undefinedconsole.log(typeof kid.say); // functionkid.name = 'Patrick';console.log(kid.say()); // Patrickvar kid2 = new Child("Tom");console.log(kid.say()); console.log(kid.constructor.name); // Childconsole.log(kid.constructor === Parent); // false
          問題照舊,Child不能正常接收參數(shù)。

          模式10:klass

          這個模式,先上代碼吧:
          var klass = function (Parent, props) {
          var Child, F, i;
          // 1. // 新構(gòu)造函數(shù) Child = function () { if (Child.uber && Child.uber.hasOwnProperty("__construct")) { Child.uber.__construct.apply(this, arguments); } if (Child.prototype.hasOwnProperty("__construct")) { Child.prototype.__construct.apply(this, arguments); } };
          // 2. // 繼承 Parent = Parent || Object; F = function () { }; F.prototype = Parent.prototype; Child.prototype = new F(); Child.uber = Parent.prototype; Child.prototype.constructor = Child;
          // 3. // 添加實(shí)現(xiàn)方法 for (i in props) { if (props.hasOwnProperty(i)) { Child.prototype[i] = props[i]; } }
          // return the "class" return Child;};
          var Man = klass(null, { __construct: function (what) { console.log("Man's constructor"); this.name = what; }, getName: function () { return this.name; }});
          var first = new Man('Adam'); // logs "Man's constructor"first.getName(); // "Adam"
          var SuperMan = klass(Man, { __construct: function (what) { console.log("SuperMan's constructor"); }, getName: function () { var name = SuperMan.uber.getName.call(this); return "I am " + name; }});
          var clark = new SuperMan('Clark Kent');clark.getName(); // "I am Clark Kent"
          console.log(clark instanceof Man); // trueconsole.log(clark instanceof SuperMan); // true
          怎么樣?看著是不是有點(diǎn)暈,說好點(diǎn),該模式的語法和規(guī)范擰得和別的語言一樣,你愿意用么?咳。。。

          總結(jié)

          以上就是我要給大家分享的10個代碼復(fù)用模式,前面4種模式推薦使用。后面6種模式盡量避免使用。雖然后面6種模式,在某種特殊情況下實(shí)現(xiàn)了某些功能,但是都存在各自的缺點(diǎn),所以一般情況,請大家要避免使用。
          本文完~
          參考:http://shichuan.github.com/javascript-patterns/#code-reuse-patterns

          瀏覽 46
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕精品一区久久久久 | 超碰人人操在线 | 啪啪网站免费看 | 日韩欧美中出 | jizz18视频 |