#創作は真似事から始まる
わりとよく聞く言葉だとは思いますが、世の中に存在する全ての作られたものは模倣から始まっていると思います。完全オリジナルなんてないのです。多分。
幼年期に絵を書いていた人は恐らくドラえもんやアンパンマンの落書きから始まって、そこから色んな絵を描き、成長と共に色んなアレンジや技法を知り、自分のものにしたり。
小さい頃ピアノを習ってた人だって、いきなりオリジナル曲を作れる人は稀有なんではないでしょうか。恐らく。
#Let's 写経
例えばjavascriptを学習するとき、分厚い教本を買って小難しいカタカナなどを乗り越えながら手を動かしつつ学習する人もいると思うのですが(私がそうです)、個人的には自分に向いてないと思いました。
まず構文がわからん。
「え、なにこの書き方?」
みたいなのを見つけても説明がなかったりします。
ネットで調べようにも調べ方がわからなかったり。
知識を吸収しようにも教本だけではもどかしく、「え、なにこれ」が発生した場合解決するまで次のステップへ進めない(それができる人もいるのでしょうけども)。
というわけで、私みたいにじゃあ教本に頼りっぱなしではなく自分でコードを書きまくって覚えちまおうって人に向けたい話が今回伝えたいことです。Let's 写経。
#どうするの?
GitHubでもなんでもいいので、オープンソースのコードを拾ってきます。
難易度はばらつきがあると思いますが、自分ならこれを解読しながら模写できそうと思えるのがいいかも。
思えるものがなかったらとりあえず片っ端からやっていきましょう。
例えば
var utils = {
wrap: function(el, className) {
if (!el) {
return;
}
var wrapper = document.createElement('div');
wrapper.className = className;
el.parentNode.insertBefore(wrapper, el);
el.parentNode.removeChild(el);
wrapper.appendChild(el);
},
addClass: function(el, className) {
if (!el) {
return;
}
if (el.classList) {
el.classList.add(className);
} else {
el.className += ' ' + className;
}
},
removeClass: function(el, className) {
if (!el) {
return;
}
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
},
hasClass: function(el, className) {
if (el.classList) {
return el.classList.contains(className);
} else {
return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
}
return false;
},
// ex Transform
// ex TransitionTimingFunction
setVendor: function(el, property, value) {
if (!el) {
return;
}
el.style[property.charAt(0).toLowerCase() + property.slice(1)] = value;
el.style['webkit' + property] = value;
el.style['moz' + property] = value;
el.style['ms' + property] = value;
el.style['o' + property] = value;
},
trigger: function(el, event, detail = null) {
if (!el) {
return;
}
let customEvent = new CustomEvent(event, {
detail: detail
});
el.dispatchEvent(customEvent);
},
Listener: {
uid: 0
},
on: function(el, events, fn) {
if (!el) {
return;
}
events.split(' ').forEach(event => {
var _id = el.getAttribute('lg-event-uid') || '';
utils.Listener.uid++;
_id += '&' + utils.Listener.uid;
el.setAttribute('lg-event-uid', _id);
utils.Listener[event + utils.Listener.uid] = fn;
el.addEventListener(event.split('.')[0], fn, false);
});
},
off: function(el, event) {
if (!el) {
return;
}
var _id = el.getAttribute('lg-event-uid');
if (_id) {
_id = _id.split('&');
for (var i = 0; i < _id.length; i++) {
if (_id[i]) {
var _event = event + _id[i];
if (_event.substring(0, 1) === '.') {
for (var key in utils.Listener) {
if (utils.Listener.hasOwnProperty(key)) {
if (key.split('.').indexOf(_event.split('.')[1]) > -1) {
el.removeEventListener(key.split('.')[0], utils.Listener[key]);
el.setAttribute('lg-event-uid', el.getAttribute('lg-event-uid').replace('&' + _id[i], ''));
delete utils.Listener[key];
}
}
}
} else {
el.removeEventListener(_event.split('.')[0], utils.Listener[_event]);
el.setAttribute('lg-event-uid', el.getAttribute('lg-event-uid').replace('&' + _id[i], ''));
delete utils.Listener[_event];
}
}
}
}
},
};
↑こういうコードを
var utils = {//utilsオブジェクト作成
wrap: function(el,className) { //wrap作成
if (!el) {//elがtrueでなかった場合
return; //undefinedを返す
}
var wrapper = document.createElement('div'); //divタグを生成
wrapper.className = className; //生成されたdivタグのclassNameが引数のclassNameとなる
el.parentNode.insertBefore(wrapper,el);//wrapper(div)をelの前に挿入
el.parentNOde.removeChild(el);//elを削除
wrapper.appendChild(el);//wrapperの子要素としてelを追加
},
addClass: function(el, className) { //class追加
if (!el) {//elがtrueでなかった場合
return;//undefinedを返す
}
if (el.classList) {// el.classListがtrueならば
el.classList.add(className);//elのclassにclassNameを追加
} else {
el.className += ' ' + className;//それ以外は「"elのクラス名" className」という文字列を生成
}
},
removeClass: function(el, className) { //class削除
if (!el) {//elがtrueでなかった場合
return;//undefinedを返す
}
if (el.classList) {//el.classListがtrueならば
el.classList.remove(className); //elのclassからclassNameを削除
} else {//それ以外のときはマッチする全てのクラス名を空白に
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');//それ以外のときはマッチする全てのクラス名を空白に
}
},
hasClass: function(el, className) {//class判定
if (el.classList) {//el.classListがtrueならば
return el.classList.contains(className); //classListの中身をマッチさせた結果を返す
} else { //それ以外の時はclassNameをマッチさせた結果を返す
return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
}
return false;//falseを返す
},
// 動きを与える
// transitionでのタイミング操作の関数
setVendor: function(el,property,value) {//ベンダープレフィックス付与
if(!el) {//elがtrueでなかった場合
return;//undefinedを返す
}
el.style[property.charAt(0).toLowerCase() + property.slice(1)] = value;//頭文字が大文字のものを小文字に
el.style['webkit' + property] = value; //webkit付与
el.style['moz' + property] = value; //moz付与
el.style['ms' + property] = value; //ms付与
el.style['o' + property] = value; //o付与
},
trigger: function(el, event ,detail = null) {//event発火
if(!el) {
return;
}
let customEvent = new CustomEvent(event, {
detail: detail
});
el.dispatchEvent(customEvent);//CustomEventを発生させる
},
Listener: {
uid: 0
},
on: function(el, events, fn) {
if(!el) {
return;
}
events.split(' ').forEach(event => {//eventsを配列に置換し、foreachで繰り返し処理
var _id = el.getAttribure('lg-event-uid') || '';//elの属性にlg-event-uidが含まれていれば_idに代入、それ以外は空の値を代入
utils.Listenr.uid++; //uidに1を追加していく
_id += utils.Listener.uid;// _idにuidを追加していく
el.setAttribute('lg-event-uid',_id);// elのlg-event-uid属性に_idの値を付与
utils.Listener[event + utils.Listener.uid] = fn; //utils.Listenerオブジェクトにevent+uidの数値というプロパティを追加し、値をfnにする
el.addEventListener(event.split('.')[0],fn,false);//eventsの1番目のイベントがハンドラとなり、fnを実行。そのイベントは伝播する。
});
},
off: function(el, event) {
if (!el) {
return;
}
var _id = el.getAttribure('lg-event-uid');
if (_id) {//_idがtureだったら
_id = _id.split('&');//_idを&単位で分割し配列にする
for (var i = 0; i < _id.length; i++) { //_idの個数分だけiの数値を増加
if (_id[i]) {//_id配列がtrueだったら
var _event = event + _id[i];//_eventにeventと_id配列の数値を足したものを代入
if (_event.substring(0, 1) === '.') {//_event文字列の頭文字が.だったら
for (var key in utils.Listener) { //utils.Listenerの値を反復
if (utils.Listener.hasOwnProperty(key)) { //Listenerがkeyをもっていれば
if (key.split('.').indexOf(_event.split('.')[1]) > -1) {//keyを.で分割した配列の中に、_eventを.で分割した配列の2番目の値が一致したものが-1より
el.removeEventListener(key.split('.')[0], utils.Listener[key]); //keyを.で分割した配列の最初の値のイベントで、utils.Listenerのプロパティを削除していく
el.setAttribute('lg-event-uid', el.getAttribure('lg-event-uid').replace('&' + _id[i], ''));//elのlg-event-uid属性にlg-event-uidの&とidを削除した文字列の値をセット
delete utils.Listener[key]; //utilsのListennerの中のkeyプロパティを削除
}
}
}
} else {//_id配列がfalseだったら
el.removeEventListener(_event.split('.')[0], utils.Listener[_event]);//eventsの1番目のイベントがハンドラとなり、utils.Listener[_event]を削除。
el.setAttribute('lg-event-uid', el.getAttribute('lg-event-uid').replace('&' + _id[i], '')); //elのlg-event-uid属性にlg-event-uidの&とidを削除した文字列の値をセット
delete utils.Listener[_event]; //utilsのListennerの中の_eventプロパティを削除
}
}
}
}
}
};
↑こう書いていきます。
もちろんコピペはNGで、元のソースを見ながら全て手書きで書いていきます。
エディター上で両ソースを見れる状態にしておき、オリジナルを見ながら模写していくってのがやりやすいかもしれないです。
#結局写経したらどうなるの?
例えば見慣れない構文を見つけたとき、
この書き方はどういう機能を意味してるのか?
この処理はなにを意味しているのか?
そもそもこの記号何?
とか色々疑問が湧いてきます。
自分の手で書き写して、見慣れない構文はその都度調べて、既にある機能を自分の手で作り上げて行くのです。関数の書き方、効率的な処理の書き方、よくわからない記号の意味がじわじわわかったりしてきます。
楽器で例えるなら、ギターの練習の際基礎練はほどほどに既成曲ばっかり弾きまくって上達していくといった感じでしょうか。当然基礎は大事ですが、人によってはガシガシ手を動かした方が吸収できる人もいるようです。自分にあった学習の仕方があるとは思いますが、教本は補助教材としてわからないことがあったときに確認するようにして、あとはひたすら書く、というのが性に合ってる人はハマると思います。