Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Prototype.js をコードリーディングして知る JavaScript の黒魔術

Prototype.js をコードリーディングして知る JavaScript の黒魔術

Seong Yong-ju

February 05, 2020
Tweet

More Decks by Seong Yong-ju

Other Decks in Programming

Transcript

  1. ⾃⼰紹介 よんじゅ Arch Linux と Emacs と Z-shell が好きなフリーランスエンジニア 最近

    Spacemacs から Doom Emacs に乗り換えた 9 歳のとき (2002年) に JavaScript を初めて触る Twitter: @sei40kr_sub
  2. Prototype.js Prototype JavaScript framework: a foundation for ambitious web user

    interfaces http://prototypejs.org JavaScript のフルスタックライブラリ (Yahoo UI や Closure Library に網羅範囲は 劣るが...) 最終更新⽇は 2015/09/22
  3. jQuery との違い Prototype.js jQuery 実装スタイル OOP & Prototype 拡張 jQuery

    以下に Function を⽣やす class 実装 Alex Arnell's Class Inheritance ベース なし Element 拡張 Prototype 拡張 要素をもつ配列をラップしたものに jQuery.fn をマージ ID から要素を取 得 $ なし セレクターから 要素を取得 $$ $
  4. Object.getInstanceOf , __proto__ 途中からインスタンス側からも Object.getPrototypeOf で参照できるようにな った ちなみに __proto__ は⾮推奨だが、このスライドでは説明の便宜上使う。

    function Class() {} var instance = new Class(); Object.getPrototypeOf(instance) === Class.prototype; // true instance.__proto__ === Class.prototype; // true
  5. すべての道は Object.prototype に通ず Class インスタンス instance のプロパティ prop を参照すると次の順に参照 される

    instance.prop instance.__proto__.prop instance.__proto__.__proto__.prop prototype ⾃体はほぼ全て Object なので、最終的には Object.prototype に たどり着く。 ただし Object.prototype.__proto__ は null であるため、循環参照は⾏われ ない。 function Class() {} Object.prototype = { prop: 'foo' }; var instance = new Class(); instance.prop; // 'foo'
  6. すべての道は Object.prototype に通ず この性質を利⽤しクラス継承の仕組みを実装することができる function ParentClass() {} function ChildClass() {}

    // prototype の prototype を変更していることに注意! Object.setPrototypeOf(ChildClass.prototype, ParentClass.prototype);
  7. prototype を使った⼩技 Function 内部の arguments は Array-like なオブジェクトだが Array ではないた

    め Array.prototype が使えない __proto__ を無理やり Array.prototype にしてしまうことで使えるメソッドも ある arguments.__proto__ = Array.prototype; arguments.shift(); その他に slice を無理やり使って Array に変換するテクニックもある var args = Array.prototype.slice.call(arguments); args.shift();
  8. Prototype 拡張の弊害 var array = [0, 1, 2]; Array.prototype.foo =

    'foo'; for (var key in array) { key; // <- 0, 1, 2, 'foo' }
  9. DONT_ENUM 属性 よくよく考えると built-in のメソッドは for-in しても列挙されない built-in のプロパティには for-in

    でも列挙されない属性 DONT_ENUM が付与されて いる V8 JavaScript Engine の src/property-details.h enum PropertyAttributes { DONT_ENUM = ::v8::DontEnum, };
  10. Class.create の仕様 クラスメソッドを定義したオブジェクトを引数として渡す initialize はコンストラクタとして扱われる var Animal = Class.create({ initialize:

    function(name, sound) { this.name = name; this.sound = sound; }, speak: function() { alert(this.name + " says: " + this.sound + "!"); } });
  11. Class.create の仕様 第⼀引数に別のクラスを渡すことによってそのクラスを親とする⼦クラスを⽣成 できる メソッドの第⼀引数の名前を $super にすると、それをスーパーメソッドとして 扱える。 var Snake

    = Class.create(Animal, { initialize: function($super, name) { $super(name, 'hissssssssss'); } }); var ringneck = new Snake("Ringneck"); ringneck.speak(); //-> alerts "Ringneck says: hissssssssss!"
  12. Class.create 1. 第⼀引数が Function であればそれを親クラスとみなす。他は定義するメソッドと みなす。 var parent = null,

    properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); 2. コンストラクタでは単純に initialize を呼び出す function klass() { this.initialize.apply(this, arguments); }
  13. Class.create prototype の prototype を親クラスの prototype にする if (parent) {

    subclass.prototype = parent.prototype; klass.prototype = new subclass; } つまり subclass は parent.prototype を prototype にもつオブジェクトを作るため の使い回しコンストラクタ。 ブラウザでサポートされていれば、次のいずれかのように書ける。 klass.prototype = Object.create(parent.prototype); Object.setPrototypeOf(klass.prototype, parent.prototype) klass.prototype.__proto__ = parent.prototype;
  14. Class.Methods.addMethods 既に DONT_ENUM 属性が付与されている built-in プロパティと同名のプロパティを定義 したときに for-in で列挙されないバグに対する workaround

    var IS_DONTENUM_BUGGY = (function(){ for (var p in { toString: 1 }) { if (p === 'toString') return false; } return true; })(); if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf"); }
  15. Function.prototype.toString Function を toString() するとソースコードが⽂字列で返る function f(x) { x; }

    f.toString() // 'function f(x) { x; }' ただし built-in な関数や Command Line API などの関数の中身は⾒れない Object.toString(); // 'function() { [native code] }' $$.toString(); // 'function() { [Command Line API] }'
  16. 例: 変数のスコープ var f = new Array(3); for (var i

    = 0; i < 3; i++) { f[i] = function() { return i; }; } f[0](); // 0 ではなく 2 が返る 上記のスクリプトでは、変数 i はループを実⾏したスコープを escape した後も ⽣存している ただし値はループ終了時の値になっている
  17. Class.Methods.addMethods 最後に toString() したときにラップする前の関数のソースコードを返すようにする (細かい...) value.valueOf = (function(method) { return

    function() { return method.valueOf.call(method); }; })(method); value.toString = (function(method) { return function() { return method.toString.call(method); }; })(method);