js5 関数の高度な機能 オブジェクトプロパティの設定 プロトタイプ, 継承プロトタイプ, 継承、クラス
問題一覧
1
this だけでなく、引数もバインドすることが可能です。これはめったにされませんが、便利な場合があります。 bind の完全な構文は次の通りです: let bound = func.bind(context, [arg1], [arg2], ...); context を this とし、関数の開始引数をバインドすることができます。 function mul(a, b) { return a * b; } let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10 カリー化!! これは 部分関数アプリケーション と呼ばれ、既存のパラメータのいくつかを固定にすることで新しい関数を作成します
2
bindは第一引数のcontextは必須なので、 使えない! クロージャを利用することによって、部分関数化を行う。 function partial(func, ...argsBound) { return function(...args) { // (*) return func.call(this, ...argsBound, ...args); } } // Usage: let user = { firstName: "John", say(time, phrase) { alert(`[${time}] ${this.firstName}: ${phrase}!`); } }; // 固定時間で部分メソッドを追加 user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Something like: // [10:00] John: Hello!
3
解答: undefined. bind の結果は別のオブジェクトです。それは test プロパティを持っていません。 バインドして返される関数はラップされるのでカスタムプロパティはアクセスできなくなる。
4
アロー関数内部ではthisを持たない。 アロー関数外部と同様のthisの振る舞いとなる。 例えば、それを使ってオブジェクトメソッドの内側を反復することができます。: let group = { title: "Our Group", students: ["John", "Pete", "Alice"], showList() { this.students.forEach( student => alert(this.title + ': ' + student) ); } }; group.showList(); argumentsもおなじ
5
できない。 this を持たないことによる制限らしい。
6
外部のレキシカル環境からthisが参照されている
7
bindは関数のバインドしたバージョンを作成する アロー関数はバインドを作成せず、ただthisをもたないだけ
8
writable – true の場合、変更可能です。それ以外の場合は読み取り専用です。 enumerable – true の場合、ループで列挙されます。それ以外の場合は列挙されません。 configurable – true の場合、プロパティを削除したり属性の変更ができます。
9
メソッド Object.getOwnPropertyDescriptor で、プロパティの 完全な 情報を参照することができます。 構文は次の通りです: let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); obj 情報を取得するオブジェクトです。 propertyName プロパティ名です。 返却値はいわゆる “プロパティディスクリプタ” オブジェクトと呼ばれます。: それは値とすべてのフラグを含んでいます。 例: let user = { name: "John" }; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* プロパティディスクリプタ: { "value": "John", "writable": true, "enumerable": true, "configurable": true } */
10
すべてtrueで作成される。
11
Object.defineProperty でフラグの変更ができます。 構文: Object.defineProperty(obj, propertyName, descriptor) obj, propertyName 処理するオブジェクトとプロパティです。 descriptor 適用するプロパティディスクリプタです。 プロパティが存在する場合、defineProperty はそのフラグを更新します。存在しない場合は指定された値とフラグでプロパティを作ります。 その場合に、もしフラグが指定されていなければ false とみなされます。 例えば、ここではプロパティ name はすべて false のフラグで作られます。: let user = {}; Object.defineProperty(user, "name", { value: "John" }); let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": "John", "writable": false, "enumerable": false, "configurable": false } */ “通常の方法で” 作成された user.name と上記を比較してください。今すべてのフラグは false です。このようにしたくなければ、descriptor で true をセットするのがよいでしょう。
12
書き込みするとエラーになる。 let user = { name: "John" }; Object.defineProperty(user, "name", { writable: false }); user.name = "Pete"; // Error: Cannot assign to read only property 'name'...
13
組み込みの関数同様、for..in ループで列挙されなくなります。 例えば、以下のようにtoStringメソッドを定義した場合、for inループでtoStringも出力対象となってしまう。 列挙されたくなければ、toStringプロパティに対してenumerableをfalseで設定してやると良い let user = { name: "John", toString() { return this.name; } }; Object.defineProperty(user, "toString", { enumerable: false }); // これで toString は消えました: for (let key in user) alert(key); // name 列挙可能でないプロパティは Object.keys からも除外されます。: alert(Object.keys(user)); // name
14
変更できないプロパティは削除したり変更することができません 例えば、Math.PI は書き込み不可で、列挙不可であり、変更不可です: let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": 3.141592653589793, "writable": false, "enumerable": false, "configurable": false } */ したがって、プログラマは Math.PI の値を変えることも上書きすることもできません。 Math.PI = 3; // Error // delete Math.PI もまた動作しません Math.PI を再度 writable にすることもできません。: // Error, configurable: false なので Object.defineProperty(Math, "PI", { writable: true }); Math.PI についてはは何もできません。 変更不可なプロパティの作成は一方通行であり、それを defineProperty 戻すことはできません。 注意: configurable: false はプロパティフラグの変更や削除を禁止しますが、値を変更することは可能です
15
一度に多くのプロパティが定義できるメソッド Object.defineProperties(obj, descriptors)もあります。 構文は次の通りです: Object.defineProperties(obj, { prop1: descriptor1, prop2: descriptor2 // ... }); 例えば: Object.defineProperties(user, { name: { value: "John", writable: false }, surname: { value: "Smith", writable: false }, // ... }); なので、一度に多くのプロパティをセットできます。
16
一度にすべてのプロパティのディスクリプタを取得するには、Object.getOwnPropertyDescriptors(obj) が使用できます。
17
Object.getOwnPropertyDescriptors(obj) と Object.defineProperties と合わせて、“フラグを意識して” オブジェクトをクローンする方法として使うことができます。: let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); 通常、オブジェクトをクローンするとき、次のようにプロパティをコピーするために代入を使います。: for (let key in user) { clone[key] = user[key] } …ですが、これはフラグはコピーしません
18
シンボリックプロパティもとれる
19
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { [this.name, this.surname] = value.split(" "); } }; // set fullName は指定された値で実行されます user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper 結果、“仮想” プロパティ fullName を持っており、これは読み書き可能です。
20
アクセサプロパティには、value も writable もありませんが、代わりに、get と set があります。 したがって、アクセサディスクリプタには次のものがあります: get – 引数なしの関数で、プロパティが読まれたときに動作します。 set – 1つの引数をもつ関数で、プロパティがセットされたときに呼ばれます。 enumerable – データプロパティと同じです。 configurable – データプロパティと同じです。 let user = { name: "John", surname: "Smith" }; Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { [this.name, this.surname] = value.split(" "); } }); alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ↑enumerableがfalseで作られたので列挙されない!
21
Getter/setter は、“実際の” プロパティ値のラッパーとして使用することで、それらをより詳細に制御することができます。 例えば、user で短すぎる名前を禁止したい場合、name を特別なプロパティ _name に格納することができます。そして、setter で値をフィルタします。: let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; } }; user.name = "Pete"; alert(user.name); // Pete user.name = ""; // Name is too short...
22
プロパティフラグがfalseで生成されるため、裏プロパティ見たいな意味あいになる。
23
オブジェクトは、[Prototype]] を持っており、それは null または別のオブジェクトを参照します。 そのオブジェクトは “プロトタイプ” と呼ばれます。
24
object からプロパティを読んだときに存在しない場合、JavaScriptは自動的にプロトタイプからそれを取得します。プログラミングではこのようなことを “プロトタイプ継承” と呼びます。 プロパティはプロトタイプチェーンから! 変数はレキシカルのチェーンから!
25
__proto__ を使う方法です: let animal = { eats: true }; let rabbit = { jumps: true }; rabbit.__proto__ = animal; // (*) // 今、rabbit で両方のプロパティを見つけることができます: alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true __proto__はそもそもプロトタイプのもちもののアクセサメソッドなのでセットされた場合は、プロトタイプのsetterが呼ばれ、[[prototype]]内部プロパティを変更してくれるっぽい?
26
プロトタイプの値がnullになるまで続くチェーン構造のこと
27
参照を循環させることはできません。JavaScriptは、循環するように __proto__ を割り当てようとするとエラーになります。 __proto__ の値はオブジェクトまたは null になります。プリミティブのような、それ以外のすべての値は無視されます。 明白かもしれませんが、1つの [[Prototype]] しか存在しません。 オブジェクトは2つの他のものから継承することはできません。
28
オブジェクトは [[Prototype]] プロパティを持っていて、それがプロトタイプ、つまり継承元となる。 __proto__プロパティはオブジェクト自身にもってるわけではなく [[Prototype]]の参照先のプロトタイプが持っているアクセサメソッドのこと。 同一ではないことに注意
29
admin.fullName = "Alice Cooper"; によって、プロトタイプからセッターメソッドが動く。 セッターメソッドの内容は、 set fullName(value) { [this.name, this.surname] = value.split(" "); }, なので、adminオブジェクトのプロパティにname surnameが追加される! よって、 alert(admin.fullName); でもゲッター関数が呼ばれ、 get fullName() { return `${this.name} ${this.surname}`; } }; Alice Cooper"が出力される alert(user.fullName); はJohn Smithが出力される
30
対象になる let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; // Object.keys は実親のキーだけを返します alert(Object.keys(rabbit)); // jumps // for..in は実親と継承したキー両方をループします for(let prop in rabbit) alert(prop); // jumps, eats
31
組み込みのメソッド obj.hasOwnProperty(key)が利用できます。これは obj が key という名前のプロパティをもつ場合(継承したものは含みません)、true を返します。 従って、継承したプロパティをフィルタする(あるいはそれらについて何かをする)ことが可能です。: let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; for(let prop in rabbit) { let isOwn = rabbit.hasOwnProperty(prop); if (isOwn) { alert(`Our: ${prop}`); // Our: jumps } else { alert(`Inherited: ${prop}`); // Inherited: eats } } ちなみにin演算子は継承したやつも含める
32
チェーンをみることで、メソッドは Object.prototype.hasOwnProperty で提供されていることがわかります。つまり、継承されたものです。 …ですが、なぜ hasOwnProperty は継承したプロパティもリストする for..in ループで eats や jumps のように登場しないのでしょう。 答えはシンプルです。これは列挙不可だからです。Object.prototype の他のすべてのプロパティと同様に、enumerable:false フラグを持ちます。そして、for..in は列挙可能なプロパティのみをリストします。そのため、Object.prototype のプロパティは列挙されません。
33
ならない。 Object.keys, Object.values などのほとんどの他のキー/バリュー取得メソッドは、継承したプロパティを無視します。 これらはオブジェクト自身の操作のみ行います。プロトタイプからのプロパティは考慮され ません。
34
実行コンテキストはspeedyそのものだが、プロパティを所持していないので、プロトタイプチェーンにより、親プロトタイプのstomachを参照し、更新してしまう。 つまり、ハムスター両者は同じ胃袋を共有している。 これを避けるためには、 ハムスター両方にstomachプロパティを各自作成してやるか、 eatメソッドの中で、以下のように代入すれば、各オブジェクトのプロパティが作られるので、避けることができる。 // this.stomach.push の代わりに this.stomach に代入する this.stomach = [food];
35
obj = new Object() Object は組み込みのオブジェクトコンストラクタ関数です。
36
Objectはコンストラクタ関数そのもの。 今はクラスともいう。 new Objectや{}で作成したオブジェクトはObjectクラスのインスタンス。 Objectコンストラクタ関数から作成されている。 であってるかな。。?
37
オブジェクトのプロトタイプ。 ほぼ全てのオブジェクトのプロトタイプの元祖。 toStringなどのオブジェクトの組み込みメソッドはこのプロトタイプが有している。 ちなみにObject.__proto__は空のメソッドで非推奨らしい。 インスタンスされたobj.__proto__と同一。 let obj = {}; alert(obj.__proto__ === Object.prototype); // true alert(obj.toString === obj.__proto__.toString); //true alert(obj.toString === Object.prototype.toString); //true
38
それぞれのプロトタイプに組み込み関数を有している。 例えば、配列 [1, 2, 3] を作るとき、デフォルトの new Array() コンストラクタが内部で使われます。なので、配列データは新しいオブジェクトに書き込まれ、Array.prototype はそのプロトタイプとなり、メソッドを提供します。これは非常にメモリ効率が良いです。 スペックでは、すべての組み込みのプロトタイプは先頭に Object.prototype を持っています。なので、“すべてはオブジェクトを継承している” という人もいます。 let arr = [1, 2, 3]; // Array.prototype から継承している? alert( arr.__proto__ === Array.prototype ); // true // 次に Object.prototype からは継承している? alert( arr.__proto__.__proto__ === Object.prototype ); // true // そしてトップの null alert( arr.__proto__.__proto__.__proto__ ); // null
39
覚えている通り、それらはオブジェクトではありません。しかし、それらのプロパティへアクセスをしようとした場合、組み込みのコンストラクタ String, Number, Boolean を使った一時的なラッパーオブジェクトが作られます。それらはメソッドを提供し、消えます。 それらのオブジェクトは我々には見えない形で作られ、ほとんどのエンジンはそれらを最適化しますが、スペックではこのように正確に説明されています。それらのオブジェクトのメソッドもまた String.prototype, Number.prototype や Boolean.prototype として利用可能なものとしてプロトタイプに存在します。
40
ネイティブプロトタイプは変更することができます。例えば、もしあるメソッドを String.prototype に追加した場合、それはすべての文字列で利用可能になります。: String.prototype.show = function() { alert(this); }; "BOOM!".show(); // BOOM! ただ推奨されていない。 要約すると、ネイティブプロトタイプの変更が恒常化されると、複数のライブラリ間でコンフリクトが起きやすいからとのこと。
41
ポリフィル ポリフィルは JavaScript のスペックには存在するが、特定のJavaScript エンジンではまだサポートされていないメソッドの代わりを用意することを指す用語です。 手動で実装し、組み込みのプロトタイプにそれを取り込むことができます。 例: if (!String.prototype.repeat) { // もしこのようなメソッドがない場合 // prototype に追加します String.prototype.repeat = function(n) { // 文字列を n 回繰り返す // 実際、このコードはこれより複雑になります // "n" の負の値に対するエラーのスロー // 完全なアルゴリズムは仕様にあります return new Array(n + 1).join(this); }; } alert( "La".repeat(3) ); // LaLaLa
42
1 プロトタイプのメソッドをオブジェクトのプロパティへ代入する。 let obj = { 0: "Hello", 1: "world!", length: 2, }; obj.join = Array.prototype.join; alert( obj.join(',') ); // Hello,world! 組み込みの join メソッドの内部アルゴリズムは、正しいインデックスと length プロパティのみを考慮するため、これは機能します。オブジェクトがたしかに配列かどうかはチェックしません。多くの組み込みメソッドはこのようになっています。 2 オブジェクトのプロトタイプをArray.prototypeに設定する。 ですが、obj が既に別のオブジェクトを継承していた場合にはこれは不可能です。一度に1つのオブジェクトしか継承できません。
43
オブジェクト自身はデータのみ。配列アイテム、オブジェクトプロパティ、日付 インスタンスはデータのみという考え方でよさそう
44
任意引数のラッパー関数を返す。 ラッパーかんすうでは、ms秒待機したあと、実行コンテキスト(f)の関数を実行したい。 また実行方法に着目すると f.defer(1000)(1, 2); // shows 3 after 1 sec f.defer(1000)の部分まではメソッド実行で その帰ってきた関数に対し(1, 2)で、関数実行を行っていることに注目する。 そのため、返却する関数内ではthisの扱いがundefinedになることを認識する。 よって以下のように定義できる。 Function.prototype.defer = function(ms) { //返却する関数では実行コンテキストの関数をthisで参照できないので、外部変数にとっておく。 let f = this; return function(...args) { // 外部変数にとっておいた関数を実行する。f(…args)でも問題なく動くが、メソッド実行にも対応するためapplyメソッドを利用する setTimeout(() => f.apply(this, args), ms); } }; // check it function f(a, b) { alert( a + b ); } f.defer(1000)(1, 2); // shows 3 after 1 sec f.defer(1000)で帰ってきた関数にたいして (1, 2)で実行している。 そのとき関数実行となっておりthisは失われている。 今回はthisは失われててもよい。 ただの関数を実行したいだけなので。 オブジェクトメソッドとして実行する場合は、f.defer(1000);を実行した後、戻り値の関数をオブジェクトのプロパティに登録し、そこから呼ぶとオブジェクトメソッドとしてちゃんと動く設計になっている。
45
Object.getPrototypeOf(obj) – obj の [[Prototype]] を返します。 Object.setPrototypeOf(obj, proto) – obj の [[Prototype]] に proto をセットします。 let animal = { eats: true }; // animal をプロトタイプとして新しいオブジェクトを作成する let rabbit = Object.create(animal); alert(rabbit.eats); // true alert(Object.getPrototypeOf(rabbit) === animal); // rabbit のプロトタイプを取得 Object.setPrototypeOf(rabbit, {}); // rabbit のプロトタイプを {} に変更
46
Object.create(proto[, descriptors]) – 与えられた proto を [[Prototype]] として、また任意のプロパティディスクリプタで空のオブジェクトを作ります。 let animal = { eats: true }; let rabbit = Object.create(animal, { jumps: { value: true } }); alert(rabbit.jumps); // true
47
コピー対象のプロトタイプとディスクリプタを取得しオブジェクト作成する。 let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); これは継承もプロパティフラグもシンボルもコピーされる
48
速度が重要な場合は、既存のオブジェクトの [[Prototype]] を変更しないでください 通常はオブジェクト作成時に一度だけ設定を行い、変更はしません。rabbit は animal から継承しており、それは変更しません。 また、JavaScriptエンジンは高度に最適化されています。Object.setPrototypeOf または obj.__proto__= で “その場で” プロトタイプを変更することは、可能ですがとても遅い操作になります。 そのため、何をしようとしているかわかっている場合、または JavaScript の速度が全く問題にならない場合以外は避けてください。
49
__proto__ はオブジェクトのプロパティではないです。 Object.prototype のアクセサプロパティです。: なので、もし obj.__proto__ が読み込まれたり代入された場合、該当の getter/setter がそのプロトタイプから呼ばれ、それは [[Prototype]] の取得/設定をします。 最初に言ったとおり、__proto__ は [[Prototype]] にアクセスする方法であり、[[Prototype]] 自身ではありません。
50
プロトタイプがnullで作成されたオブジェクトのこと。 let obj = Object.create(null); alert(obj); // Error (no toString) もちろん組み込み関数は使えない。
51
連想配列のオブジェクトとしてシンプルに運用したい場合 ほとんどのオブジェクトに関連したメソッドは Object.keys(obj) のように Object.something(...) であることに注目してください。それらはプロトタイプにはないので、このようなオブジェクトで機能し続けます。: let chineseDictionary = Object.create(null); chineseDictionary.hello = "ni hao"; chineseDictionary.bye = "zai jian"; alert(Object.keys(chineseDictionary)); // hello,bye toStrngとかはつかえなくなるので影響はどんなもんかわからん
52
ユーザがキーとして "__proto__" を入力する可能性があり、その場合エラーが発生し、通常は予期せぬ結果になるでしょう。 以下例のように、キー情報に__proto__が指定されてしまい、プリミティブ値を設定したとしても、その処理はシステムにより無視されてしまうのでバグを生みやすいらしい let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // [object Object], "some value" ではありません! ここでは、ユーザが __proto__ を入力した場合、その代入は無視されます! それは驚くことではありません。__proto__ プロパティは特別です: それはオブジェクトまたは null であり、文字列はプロトタイプにはなれません。 しかし、このような振る舞いを実装するつもりはありませんでした。私たちはキー/値ペアを格納したいですが、キー名が "__proto__" の場合は正しく保存されませんでした。なので、これはバグです。 ようわからんがプロトタイプのアクセサメソッドが動いた結果、プリミティブは弾かれるかんじか?
53
ただのキーとして扱われる! なぜならプロトタイプが存在しないということは、アクセサメソッドである__proto__も存在しないため セッターがないから、キーとして作れられる
54
コンストラクタ関数のprototypeプロパティを参照し、prototypeに対しプロパティを追加する。 function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert(this.name); }; let rabbit = new Rabbit("Rabbit"); インスタンスのプロトタイプ、 つまりRabbit.prototype にはインスタンスメソッドを追加できる。
55
class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // 使い方はこんなかんじ let user = new User("John"); user.sayHi();
56
クラスは、クラス内で定義したコンストラクタを含め、 コンストラクタ関数そのものとなる。 また、 クラス.prototypeのもちものとなり、コンストラクタとして登録される。 ↑の動作は通常のコンストラクタ関数であっても、コンストラクタ関数名.prototypeにコンストラクタとして登録されている。 クラスはつまり関数オブジェクトである。 クラスで定義したメソッドは そのクラス.prototypeへのメソッド設定を行う!
57
クラス宣言の結果となる User と言う名前の関数を作成します。 関数の中身は constructor メソッドです(メソッドがない場合は空と想定します)。 // 証拠: User は function です alert(typeof User); // function User.prototype に、sayHi などのクラスメソッドを格納します。
58
1 Userクラスの型はfunction 2 true 正確には User はプロトタイプの constructor メソッド 3 sayHiメソッドのコードが表示される 4 constructor, sayHi ユーザのプロトタイプのプロパティが表示される
59
いえない。 理由は 1 まず、class で生成された関数は特別な内部プロパティ [[IsClassConstructor]]: true でラベル付けされています このプロパティをチェックすることによって、以下の組み込み関数の動作が変更される。 クラスはnewなしで呼び出されないように制御されている。 class User { constructor() {} } alert(typeof User); // function User(); // Error: クラスのコンストラクタ User は `new` なしで呼び出せません 文字列変換するとclass…と出力されるようになる。 alert(User); // class User { ... } 2 クラス メソッドは列挙不可です クラス定義は、"prototype" にあるすべてのメソッドに対して enumerable フラグを false にセットします。 オブジェクトを for...in するとき、通常はクラスメソッドは必要ないのでこれは便利です。 3 クラスは常に use strict です クラス構造の中のコードはすべて自動で strict モードです。 などの機能が用意されているため!
60
する。 関数と同じように、クラスも別の式の中で定義し、渡したり、返却したり代入することができます。 これはクラス式の例です。: let User = class { sayHi() { alert("Hello"); } };
61
クラス内部で、該当クラスを表現する際に利用する 名前付き関数と同様、クラスも名前を持つことができます。 クラス式に名前がある場合、そのクラス内部でのみ見えます: // "名前付きクラス式" // (スペックにはこのような用語はありませんが、名前付き関数式と同じです) let User = class MyClass { sayHi() { alert(MyClass); // MyClass の名前はクラスの内部でのみ見えます } }; new User().sayHi(); // 動作します, MyClass の定義を表示 alert(MyClass); // error, MyClass の名前はクラスの外からは見えません
62
できる。 マジで関数と同じ扱いだとおもっておく。 function makeClass(phrase) { // クラス定義とその返却 return class { sayHi() { alert(phrase); } }; } // 新しいクラスを作成 let User = makeClass("Hello"); new User().sayHi(); // Hello
63
アクセサメソッドは User.prototype に getter / setter を作成することで機能している。 そのため また、コンストラクタでの設定に関しても インスタンスにnameプロパティは存在しないので、プロトタイプからセッターが探され、 セッターが起動する this.name = name;の処理はセッターをキックするだけなので注意。 結果的にはインスタンスにはnameプロパティではなく _nameプロパティが設定されることになる!
64
これは括弧 [...] を使用した計算されたメソッド名の例です。 class User { ['say' + 'Hi']() { alert("Hello"); } } new User().sayHi(); このような特徴は、リテラルオブジェクトに似ているので、覚えやすいと思います。
65
“クラスフィールド” は任意のプロパティが追加できる構文です。 変数ではないことに留意!! また書き方もオブジェクトリテラルとは違い key:value的な書き方ではないことに注意! 例えば、class User に name プロパティを追加しましょう。 class User { name = "John"; sayHi() { alert(`Hello, ${this.name}!`); } } new User().sayHi(); // Hello, John! つまり、宣言の中で、" = " と記述するだけです。
66
メソッドはプロトタイプのほうで管理されるが、 クラスフィールドはプロトタイプでは管理されず、個々のオブジェクト(インスタンス)にセットされる。
67
複雑な式も動作する。 動作するタイミングはインスタンズ時となる。
68
現在の構文だとクラスメソッドとして扱われ、プロトタイプに登録される。 以下のように書き換えると、クラスフィールドとして扱われる! click = () => { alert(this.value); } クラスフィールドはそれぞれのオブジェクト(インスタンス)のプロパティとなることから インスタンス生成時にアロー関数が評価され関数オブジェクトが生成される。 そのタイミングで、外側のthisとは、 外部のレキシカルであることから、 つまりインスタンスそのものを指す。 →詳しくはこの問題集の最後の問題を参照 よって以下のようなクラスフィールドを定義し、渡すとthisを失わない クラスフィールドは別の、すばらしい構文を提供します: class Button { constructor(value) { this.value = value; } click = () => { alert(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // hello
69
class MyClass { prop = value; // プロパティ constructor(...) { // コンストラクタ // ... } method(...) {} // メソッド get something(...) {} // getter set something(...) {} // setter [Symbol.iterator]() {} // 計算された名前のメソッド (ここではシンボル) // ... }
70
Rabbit.prototype.[[Prototype]] を Animal.prototype にセットします。 ↑プロトタイプチェーン形成 そのため、Rabbit.prototype でメソッドが見つからない場合には、JavaScript は Animal.prototype から取得します。 後問では静的プロパティの継承も扱う。
71
任意の式は指定できる! ただ返却値はクラスとかコンストラクタ関数である必要がある。 例えば、親クラスを生成する関数呼び出します: function f(phrase) { return class { sayHi() { alert(phrase) } } } ↑関数でクラスをかえすことで、クロージャみたいなことができる!! class User extends f("Hello") {} new User().sayHi(); // Hello ここでは、 class User は f("Hello") の結果を継承しています。
72
メソッドのオーバーライド
73
通常は親メソッドを完全に置き換えるのではなく、その上に機能の微調整または拡張を行うことを望みます。メソッド内で何かをしますが、その前後または処理中に親メソッドを呼び出します。 クラスはそのために "super" キーワードを提供しています。 super.method(...) は親メソッドを呼び出します。 例えば、うさぎ(rabbit)が止まったとき自動的に隠れさせます。: class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stopped.`); } } class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } stop() { super.stop(); // 親の stop 呼び出し this.hide(); // その後隠す } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stopped. White rabbit hides!
74
super(...) は親のコンストラクタを呼び出します(コンストラクタの内側でのみ)。
75
superはクラスメソッドである関数を評価時にホームオブジェクトを関数にバインドしており、そこから辿ることで実現している。 しかしながら、上記問題の通りクラスメソッドの中の関数に関しては、ホームオブジェクトがundefinedであるので、superを辿れずエラーとなってしまう。 アロー関数ではホームオブジェクトをアロー関数外側のレキシカルから取得するので、 以下のようにすれば問題なく動く class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // 1秒後、親の stop を実行 } }
76
クラスが別のクラスを拡張し、constructor を持たない場合、次のような constructor 暗黙的に生成されます。 class Rabbit extends Animal { // 独自のコンストラクタを持たないクラスを拡張するために生成されます constructor(...args) { super(...args); } }
77
オーバーライドしたコンストラクタは、 thisを利用する前に親側のコンストラクタを呼び出さなければならない。 つまり、1番最初に親側をつくれってこと なので以下はエラーとなる class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { this.speed = 0; this.name = name; this.earLength = earLength; } // ... } // 動作しません! let rabbit = new Rabbit("White Rabbit", 10); // Error: this は定義されていません ただしくは以下の通り class Rabbit extends Animal { constructor(name, earLength) { super(name); this.earLength = earLength; } // ... } // 今は問題ありません let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10
78
javascript内部で、親のコンストラクタを呼び出すことを期待されているから。 またその実装は 内部プロパティによって制御されており、 newキーワードなどの振る舞いに影響を与えている。 子のコンストラクタ関数は特別な内部プロパティ [[ConstructorKind]]:"derived" という、 特別な内部のラベルがつく このラベルがつくとnewの振る舞いが変更される。 通常の場合: 関数が new で実行される際、空のオブジェクトを作成し、 this に割り当てます。 派生コンストラクタが実行される場合: 親のコンストラクタを呼び出すことを期待します。 そのため、superは必須であり、なければ this =オブジェクトが作成されないままエラーとなる。
79
クラスフィールドもオーバライドできる。 ちなみに親側のプロパティとか子側のプロパティとかの概念はない。 インスタンス生成したら、親側も子側も 一緒くたにされる。 なのでクラスフィールドのオーバーライドはただ上塗りしたるだけ
80
子供のインスタンス生成時であっても、 親側の処理を先におこなうため、 親側のコンストラクタ処理では、 子側のフィールドはまだ評価されておらず、 当然オーバーライドもできていない。 子供側のフィールドを 子クラスをインスタンスする際の 実行順は以下の通り 子のコンストラクタで親コンストラクタsuper()を実行 →親のクラスフィールド評価→親のコンストラクタ評価→子のクラスフィールド評価→ 子のコンストラクタの続きを実行 これはjavascriptのみの言語仕様! クラスフィールドのオーバライドは極力おこなわず、プロトタイプのもちものであるセッターやゲッターを利用すれば、 子側でアクセサメソッドを定義していても親コンストラクタ処理時にアクセサメソッドは優先的に処理される。
81
両方ともanimalが出力される! 前問の説明などが全て。 以下は前問の説明を見ていない時の思考となる。 Rabbit には自身のコンストラクタはないので、Animal コンストラクタが呼ばれます。 そのとき親コンストラクタは常にオーバーライドされたものではなく、自身のフィールド値を利用します。 thisだからrabbitオブジェクトのnameが参照されそうにみえるのになぜ。。 以下のようなメソッドのオーバーライドではちゃんとrabbitオブジェクトのメソッドが動く!。 class Animal { showName() { // this.name = 'animal' の代わり alert('animal'); } constructor() { this.showName(); // alert(this.name); の代わり } } class Rabbit extends Animal { showName() { alert('rabbit'); } } new Animal(); // animal new Rabbit(); // rabbit なぜ。。。
82
親コンストラクタを処理しているタイミングで子クラスのクラスフィールドが初期化されてないため。 子クラスのクラスフィールドは次のタイミングで初期化されます: 子クラスのコンストラクタでsuper()を読んだ直後 ちなみに親クラスのクラスフィールドはコンストラクタの前に初期化される。
83
this.__proto__.eat.call(this); // (**) は、rabbit.eat.call(longEar)となる。 上記によって、以下が呼び出される eat() { // ...bounce around rabbit-style 親 (animal) メソッドを呼び出す this.__proto__.eat.call(this); // (*) } this.__proto__.eat.call(this); // (*) では、rabbit.eat.call(longEar)となる。 よって無限ループとなってしまう
84
孫→子→親のといった継承時に、 thisは実行コンテキストを示すので、this.__proto__の参照先は孫も子も変わらない。 孫側で実行すると子を示し 子側で実行すると子のままで親は示さないといった状況になってしまう。 これが無限ループを生んでしまう。 よって、正確に親のプロトタイプを参照できないという問題点がある。 つまり、このままではsuperの実装がうまくできない!!
85
JavaScriptはもう1つ関数のための特別な内部プロパティを追加しています: [[HomeObject]] です。 [[HomeObject]] プロパティは 関数がクラスメソッドまたはオブジェクトメソッドとして指定されたときに設定され、そのオブジェクトそのものになります。 let longEar = { __proto__: rabbit, name: "Long Ear", eat() { // [[HomeObject]] == longEar super.eat(); } }; そして、super はこれを使って親のプロトタイプとメソッドメソッドを解決しました。 longEar.eat のようなメソッドは、その [[HomeObject]] を知っており、そのプロトタイプから親のメソッドを取得します。this を使用することなく。 結構前の問題であったとおり // Unexpected super setTimeout(function() { super.stop() }, 1000); でsuperが参照できないのは、 ホームオブジェクトが失われるからだと思う
86
javascriptのオブジェクトは、バインドされない、つまり束縛されない=自由であるという原則がある。 自由であるということは、オブジェクトのコピーや渡し捜査が容易であるということ。 しかし[[HomeObject]] はメソッドそのものに束縛されるため、その原則に反する [[HomeObject]] は変更できないため、このバインドは永遠です。 [[HomeObject]] が使用される言語での唯一の場所が super です。 したがって、メソッドが super を使用しない場合、メソッドは以前として自由とみなせ、オブジェクト間でコピーできます。
87
superが固定化されるため、superが実行コンテキストの親をささず、固定化された違う親をみつけてしまう。 実行コンテキストをtreeとして呼び出した時、superが[[homeObject]]をもとに算出されており、また固定化されているため plantオブジェクトがsuperではなく、Animalを呼び出してしまっている。 これはsuperを利用せずにthis.__proto__を使ってやれば、防げそう。
88
クラスと単純なオブジェクト両方で定義されたメソッドに対して定義されています。 しかし、オブジェクトの場合、メソッドは指定された方法で正確に指定されなければなりません。: "method: function()" ではなく method() として指定する必要があります。 クラスのときクラスフィールドとして定義したインスタンスメソッドについては多分ホームオブジェクトの設定はされなさそー 我々にとってこの違いは本質的ではないかもしれませんが、JavaScript にとっては重要です。 下の例では、上の例との比較のために非メソッド構文を使っています。[[HomeObject]] プロパティはセットされず、継承は動作しません。: let animal = { eat: function() { // 短縮構文: eat() {...} にする必要があります // ... } }; let rabbit = { __proto__: animal, //このかきかただと、[[homeObject]]が設定されない!! eat: function() { super.eat(); } }; rabbit.eat(); // super 呼び出しエラー([[HomeObject]] が無いため)
89
コピーした時に、[[homeObject]]がバインドされた状態なので、superがコピー後の実行コンテキスト通りの振舞いにならず、バインドされた親を参照してしまうので、 コピーに気をつける
90
違いは画像の通り 知っている通り、 “extends” 構文は2つのプロトタイプを設定します。: それは、インスタンスで利用されるメソッド用と 静的なメソッド用の継承となる。 コンストラクタ関数の "prototype" 間(メソッド用) インスタンス→Rabbit.prototype→extends 指定の.prototype(Objectを継承しない場合は、例えばアニマルプロトタイプとかがラビットプロトタイプの親となる。) コンストラクタ関数自身の間(静的メソッド用) コンストラクタ関数自身はFunctionクラスのインスタンスなので、 コンストラクタ関数→extends指定のコンストラクタ関数→Function.prototype 我々のケースでは、class Rabbit extends Object では次を意味します: class Rabbit extends Object {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true また、extends を使わない場合 class Rabbit は2つ目の参照を持ちません。 比較してみてください: class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) // エラー、Rabbit にこのような関数はありません alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error ↑これはObjectクラスがもつ静的メソッド 画像をみるとObjectのプロトタイプはFunction.prototypeとなっている。 これはObjectクラスはコンストラクタ関数のオブジェクトだからそうなる。 ラビットコンストラクタ関数に関しては、 関数オブジェクトといえるので、 継承してなければ、親はFunction.prototypeとなる。 継承していればそのオブジェクトが親になる。 また、ラビットオブジェクトに関しては、 ラビットのプロトタイプが親となり、 ラビットのプロトタイプの親のまた親は、根源であるObject.prototypeとなる。 インスタンス用のプロトタイプ継承と静的メソッド用のコンストラクタ関数継承の二つの設定が行われると覚えること!
91
1 オブジェクトリテラルの中は key:value, ←カンマ区切り みたいな構文のみ受け付けている。 トップレベルでは変数宣言など、処理はかけない。 関数宣言はできず、関数式のみ、value部分に定義できる。 bio() {...}みたいな構文は bio:function() {…} の略である。 例 const person = { name: ["Bob", "Smith"], age: 32, bio() { console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`); }, introduceSelf() { console.log(`Hi! I'm ${this.name[0]}.`); }, }; 2 コンストラクタ関数で作成するインスタンス これは関数内なので処理がかける。 つまり、関数宣言も可能→関数内関数 ただ、コンストラクタ関数の中で関数宣言しても結局は、this.key = 関数名としてプロパティ登録することの利用用途しかなさそう。 コンストラクタ関数はthisオブジェクトを作りそれに対し設定してthisを返す。 なのでthis.key = value;でインスタンスのプロパティを作成する。 関数をインスタンスのプロパティとして登録する場合は、 this.key = 関数式って感じ。 function User(name) { // this = {}; (暗黙) // this へプロパティを追加 this.name = name; this.isAdmin = false; // return this; (暗黙) } 3 クラスから作成されるインスタンス クラスフィールドで、プロパティ作成する prop = value; また、コンストラクタ内でも同様に this.key = value;で可能。 クラスフィールド→コンストラクタの順で動くので、 クラスフィールドでは初期値などの初期化処理 また、初期化を行わないでも、明示的な宣言としても使える。 コンストラクタでは、初期化処理はかかず、引数をうけとった時の設定作業を行う。 コンストラクタ含むクラスメソッドに関しては プロトタイプのもちものとなる。 またクラスフィールドとしてもメソッドを登録できるので、それはインスタンスの持ち物となる。 class Person { name; //明示的な宣言として使える constructor(name) { this.name = name; } // クラスフィールドに定義したメソッド = インスタンスメソッド greeting = () => { console.log(`${this.name}さん、こんにちは`) } // クラスに定義したメソッド = プロトタイプメソッド callName() { console.log(this.name); } }
92
アロー関数はthisをもたず外側のthisを参照する。 では、上記問題の時の外側とはなにか? 普通に考えれば、外側はボタンクラスなので、ボタンクラスのthisってなんやねん。 となってしまう。 ただこれを頭の中で オブジェクトリテラルに置き換えてみてほしい。 全てのインスタンスはオブジェクトリテラルのシンタックスシュガーと考えてもよさそうだ。 class Button { constructor(value) { this.value = value; } click = () => { alert(this.value); } } これをnewキーワードでオブジェクトを作成すると下記のようなオブジェクトが生成される。 { value:value click:() => { alert(this.value); } } つまり、アロー関数でのthisとは newキーワードで生成したオブジェクトを指すこととなる。 ※クラスのもちものではない! さらに、アロー関数はコーディング上の外部のレキシカルではなく、 アロー関数が評価され、関数オブジェクトとして生成されたタイミングでの外部のレキシカル環境のthisを指すともいえる。 外部レキシカルからとってきているので、 thisのクロージャみたいな感じの認識だと思う。 →バインドされていると考えることができる。 なので、その関数をどこに渡そうが thisは失われてたないのである!! これは、コンストラクタ関数で登録したインスタンスメソッドのアロー関数内のthisも同様のことがいえる。
93
リテラル const a= { firstName: firstName, lastName: lastName, get fullName() { return this.firstName + " " + this.lastName; }, }; コンストラクタ関数 Object.definePropertyを使うしかない! // コンストラクタ関数 function Person3(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; Object.defineProperty( this, "fullName", { get: function () { return this.firstName + " " + this.lastName; } }); } クラス いつものかんじ
94
オブジェクト.constructor.name でとれる。 プロトタイプのもちもの コンストラクタ関数でも同じとこに格納されてる オブジェクト instanceof クラス」(真偽値) で、オブジェクトがクラスまたはそのサブクラスのインスタンスか、が分かる。左辺をプリミティブ値にすると, いつでも false になる https://www.nslabs.jp/js-class-name-of-an-object.rhtml
95
多分一緒の意味合いぽい。 でも typeOf はプリミティブ以外は関数とオブジェクトしか区別してない。 オブジェクトの中でもクラスはたくさんある。それらは型ともいえるけど上記の通り typeOfでは区別できない。 instanceOf ならクラス(プロトタイプチェーンも含む)の区別ができる。
96
for inなら プロトタイプチェーンも辿ってプロパティを出してくれる。 ただし列挙不可や、シンボルプロパティとかはとれない。 クラスで定義したクラスメソッド(プロトタイプメソッド)は列挙不可に設定されるので、微妙か。。? Object.getOwnPropertyDescriptors(obj)はシンボルや列挙不可も含めてプロパティのディスクリプタを返してくれる。 プロトタイプチェーンは辿らない。 Reflect.ownKeys(obj)てのもある この返値は、Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)) と同じです。 このどっちかかなぁ。。 組み合わせて使うか? https://qiita.com/Quest_love33/items/ef16f7e6bfcd4ac3f4b3
その他
その他
谷峻輔 · 12問 · 2年前その他
その他
12問 • 2年前LWC
LWC
谷峻輔 · 100問 · 2年前LWC
LWC
100問 • 2年前LWC2
LWC2
谷峻輔 · 28問 · 2年前LWC2
LWC2
28問 • 2年前Javascript
Javascript
谷峻輔 · 100問 · 2年前Javascript
Javascript
100問 • 2年前javascript2
javascript2
谷峻輔 · 58問 · 2年前javascript2
javascript2
58問 • 2年前js1 JavaScript の基礎
js1 JavaScript の基礎
谷峻輔 · 92問 · 2年前js1 JavaScript の基礎
js1 JavaScript の基礎
92問 • 2年前js2 コード品質、オブジェクト基本、データ型
js2 コード品質、オブジェクト基本、データ型
谷峻輔 · 100問 · 2年前js2 コード品質、オブジェクト基本、データ型
js2 コード品質、オブジェクト基本、データ型
100問 • 2年前js3 データ型
js3 データ型
谷峻輔 · 100問 · 2年前js3 データ型
js3 データ型
100問 • 2年前js4 データ型、関数の高度な機能
js4 データ型、関数の高度な機能
谷峻輔 · 100問 · 2年前js4 データ型、関数の高度な機能
js4 データ型、関数の高度な機能
100問 • 2年前js6 クラス Promise, async/await
js6 クラス Promise, async/await
谷峻輔 · 100問 · 2年前js6 クラス Promise, async/await
js6 クラス Promise, async/await
100問 • 2年前js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
谷峻輔 · 96問 · 2年前js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
96問 • 2年前js8 その他
js8 その他
谷峻輔 · 57問 · 2年前js8 その他
js8 その他
57問 • 2年前Javaとは〜
Javaとは〜
谷峻輔 · 83問 · 2年前Javaとは〜
Javaとは〜
83問 • 2年前Reactクイックスタート
Reactクイックスタート
谷峻輔 · 15問 · 2年前Reactクイックスタート
Reactクイックスタート
15問 • 2年前デザインパターン
デザインパターン
谷峻輔 · 5問 · 2年前デザインパターン
デザインパターン
5問 • 2年前承認申請
承認申請
谷峻輔 · 16問 · 2年前承認申請
承認申請
16問 • 2年前アプリケーションビルダー2
アプリケーションビルダー2
谷峻輔 · 100問 · 2年前アプリケーションビルダー2
アプリケーションビルダー2
100問 • 2年前アプリケーションビルダー3
アプリケーションビルダー3
谷峻輔 · 100問 · 2年前アプリケーションビルダー3
アプリケーションビルダー3
100問 • 2年前アプリケーションビルダー4
アプリケーションビルダー4
谷峻輔 · 100問 · 2年前アプリケーションビルダー4
アプリケーションビルダー4
100問 • 2年前アプリケーションビルダー5
アプリケーションビルダー5
谷峻輔 · 94問 · 2年前アプリケーションビルダー5
アプリケーションビルダー5
94問 • 2年前アプリケーションビルダー6
アプリケーションビルダー6
谷峻輔 · 100問 · 2年前アプリケーションビルダー6
アプリケーションビルダー6
100問 • 2年前アプリケーションビルダー7
アプリケーションビルダー7
谷峻輔 · 100問 · 2年前アプリケーションビルダー7
アプリケーションビルダー7
100問 • 2年前アプリケーションビルダー8
アプリケーションビルダー8
谷峻輔 · 98問 · 2年前アプリケーションビルダー8
アプリケーションビルダー8
98問 • 2年前アプリケーションビルダー9
アプリケーションビルダー9
谷峻輔 · 70問 · 2年前アプリケーションビルダー9
アプリケーションビルダー9
70問 • 2年前フロー
フロー
谷峻輔 · 42問 · 2年前フロー
フロー
42問 • 2年前レポート、ダッシュボード
レポート、ダッシュボード
谷峻輔 · 60問 · 2年前レポート、ダッシュボード
レポート、ダッシュボード
60問 • 2年前SalesCloud
SalesCloud
谷峻輔 · 18問 · 2年前SalesCloud
SalesCloud
18問 • 2年前標準機能
標準機能
谷峻輔 · 72問 · 2年前標準機能
標準機能
72問 • 2年前宣言的開発
宣言的開発
谷峻輔 · 8問 · 2年前宣言的開発
宣言的開発
8問 • 2年前数式、人力規則
数式、人力規則
谷峻輔 · 18問 · 2年前数式、人力規則
数式、人力規則
18問 • 2年前アクセスレベル
アクセスレベル
谷峻輔 · 63問 · 2年前アクセスレベル
アクセスレベル
63問 • 2年前データ型変換
データ型変換
谷峻輔 · 22問 · 2年前データ型変換
データ型変換
22問 • 2年前オブジェクト、項目
オブジェクト、項目
谷峻輔 · 91問 · 2年前オブジェクト、項目
オブジェクト、項目
91問 • 2年前アプリ、タブ
アプリ、タブ
谷峻輔 · 92問 · 2年前アプリ、タブ
アプリ、タブ
92問 • 2年前自動化
自動化
谷峻輔 · 17問 · 2年前自動化
自動化
17問 • 2年前組織、開発モデル
組織、開発モデル
谷峻輔 · 65問 · 2年前組織、開発モデル
組織、開発モデル
65問 • 2年前ボタン、アクション
ボタン、アクション
谷峻輔 · 69問 · 2年前ボタン、アクション
ボタン、アクション
69問 • 2年前インテグレーション
インテグレーション
谷峻輔 · 40問 · 2年前インテグレーション
インテグレーション
40問 • 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
谷峻輔 · 46問 · 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
46問 • 2年前標準オブジェクト
標準オブジェクト
谷峻輔 · 42問 · 2年前標準オブジェクト
標準オブジェクト
42問 • 2年前ServiceCloud
ServiceCloud
谷峻輔 · 16問 · 2年前ServiceCloud
ServiceCloud
16問 • 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
谷峻輔 · 41問 · 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
41問 • 2年前データインポート、エクスポート
データインポート、エクスポート
谷峻輔 · 3回閲覧 · 12問 · 2年前データインポート、エクスポート
データインポート、エクスポート
3回閲覧 • 12問 • 2年前Chatter
Chatter
谷峻輔 · 21問 · 2年前Chatter
Chatter
21問 • 2年前承認申請
承認申請
谷峻輔 · 15問 · 2年前承認申請
承認申請
15問 • 2年前フロー
フロー
谷峻輔 · 44問 · 2年前フロー
フロー
44問 • 2年前レポート、ダッシュボード
レポート、ダッシュボード
谷峻輔 · 65問 · 2年前レポート、ダッシュボード
レポート、ダッシュボード
65問 • 2年前SalesCloud
SalesCloud
谷峻輔 · 18問 · 2年前SalesCloud
SalesCloud
18問 • 2年前標準機能
標準機能
谷峻輔 · 72問 · 2年前標準機能
標準機能
72問 • 2年前宣言的開発
宣言的開発
谷峻輔 · 8問 · 2年前宣言的開発
宣言的開発
8問 • 2年前アクセスレベル
アクセスレベル
谷峻輔 · 52問 · 2年前アクセスレベル
アクセスレベル
52問 • 2年前数式、人力規則
数式、人力規則
谷峻輔 · 17問 · 2年前数式、人力規則
数式、人力規則
17問 • 2年前データ型変換
データ型変換
谷峻輔 · 22問 · 2年前データ型変換
データ型変換
22問 • 2年前オブジェクト、項目
オブジェクト、項目
谷峻輔 · 89問 · 2年前オブジェクト、項目
オブジェクト、項目
89問 • 2年前アプリ、タブ
アプリ、タブ
谷峻輔 · 100問 · 2年前アプリ、タブ
アプリ、タブ
100問 • 2年前自動化
自動化
谷峻輔 · 18問 · 2年前自動化
自動化
18問 • 2年前組織、開発モデル
組織、開発モデル
谷峻輔 · 60問 · 2年前組織、開発モデル
組織、開発モデル
60問 • 2年前ボタン、アクション
ボタン、アクション
谷峻輔 · 69問 · 2年前ボタン、アクション
ボタン、アクション
69問 • 2年前インテグレーション
インテグレーション
谷峻輔 · 40問 · 2年前インテグレーション
インテグレーション
40問 • 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
谷峻輔 · 44問 · 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
44問 • 2年前標準オブジェクト
標準オブジェクト
谷峻輔 · 32問 · 2年前標準オブジェクト
標準オブジェクト
32問 • 2年前ServiceCloud
ServiceCloud
谷峻輔 · 17問 · 2年前ServiceCloud
ServiceCloud
17問 • 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
谷峻輔 · 18問 · 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
18問 • 2年前データインポート、エクスポート
データインポート、エクスポート
谷峻輔 · 11問 · 2年前データインポート、エクスポート
データインポート、エクスポート
11問 • 2年前Chatter
Chatter
谷峻輔 · 21問 · 2年前Chatter
Chatter
21問 • 2年前一時
一時
谷峻輔 · 89問 · 2年前一時
一時
89問 • 2年前一時2
一時2
谷峻輔 · 29問 · 2年前一時2
一時2
29問 • 2年前js9 ドキュメント
js9 ドキュメント
谷峻輔 · 80問 · 2年前js9 ドキュメント
js9 ドキュメント
80問 • 2年前問題一覧
1
this だけでなく、引数もバインドすることが可能です。これはめったにされませんが、便利な場合があります。 bind の完全な構文は次の通りです: let bound = func.bind(context, [arg1], [arg2], ...); context を this とし、関数の開始引数をバインドすることができます。 function mul(a, b) { return a * b; } let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10 カリー化!! これは 部分関数アプリケーション と呼ばれ、既存のパラメータのいくつかを固定にすることで新しい関数を作成します
2
bindは第一引数のcontextは必須なので、 使えない! クロージャを利用することによって、部分関数化を行う。 function partial(func, ...argsBound) { return function(...args) { // (*) return func.call(this, ...argsBound, ...args); } } // Usage: let user = { firstName: "John", say(time, phrase) { alert(`[${time}] ${this.firstName}: ${phrase}!`); } }; // 固定時間で部分メソッドを追加 user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Something like: // [10:00] John: Hello!
3
解答: undefined. bind の結果は別のオブジェクトです。それは test プロパティを持っていません。 バインドして返される関数はラップされるのでカスタムプロパティはアクセスできなくなる。
4
アロー関数内部ではthisを持たない。 アロー関数外部と同様のthisの振る舞いとなる。 例えば、それを使ってオブジェクトメソッドの内側を反復することができます。: let group = { title: "Our Group", students: ["John", "Pete", "Alice"], showList() { this.students.forEach( student => alert(this.title + ': ' + student) ); } }; group.showList(); argumentsもおなじ
5
できない。 this を持たないことによる制限らしい。
6
外部のレキシカル環境からthisが参照されている
7
bindは関数のバインドしたバージョンを作成する アロー関数はバインドを作成せず、ただthisをもたないだけ
8
writable – true の場合、変更可能です。それ以外の場合は読み取り専用です。 enumerable – true の場合、ループで列挙されます。それ以外の場合は列挙されません。 configurable – true の場合、プロパティを削除したり属性の変更ができます。
9
メソッド Object.getOwnPropertyDescriptor で、プロパティの 完全な 情報を参照することができます。 構文は次の通りです: let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); obj 情報を取得するオブジェクトです。 propertyName プロパティ名です。 返却値はいわゆる “プロパティディスクリプタ” オブジェクトと呼ばれます。: それは値とすべてのフラグを含んでいます。 例: let user = { name: "John" }; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* プロパティディスクリプタ: { "value": "John", "writable": true, "enumerable": true, "configurable": true } */
10
すべてtrueで作成される。
11
Object.defineProperty でフラグの変更ができます。 構文: Object.defineProperty(obj, propertyName, descriptor) obj, propertyName 処理するオブジェクトとプロパティです。 descriptor 適用するプロパティディスクリプタです。 プロパティが存在する場合、defineProperty はそのフラグを更新します。存在しない場合は指定された値とフラグでプロパティを作ります。 その場合に、もしフラグが指定されていなければ false とみなされます。 例えば、ここではプロパティ name はすべて false のフラグで作られます。: let user = {}; Object.defineProperty(user, "name", { value: "John" }); let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": "John", "writable": false, "enumerable": false, "configurable": false } */ “通常の方法で” 作成された user.name と上記を比較してください。今すべてのフラグは false です。このようにしたくなければ、descriptor で true をセットするのがよいでしょう。
12
書き込みするとエラーになる。 let user = { name: "John" }; Object.defineProperty(user, "name", { writable: false }); user.name = "Pete"; // Error: Cannot assign to read only property 'name'...
13
組み込みの関数同様、for..in ループで列挙されなくなります。 例えば、以下のようにtoStringメソッドを定義した場合、for inループでtoStringも出力対象となってしまう。 列挙されたくなければ、toStringプロパティに対してenumerableをfalseで設定してやると良い let user = { name: "John", toString() { return this.name; } }; Object.defineProperty(user, "toString", { enumerable: false }); // これで toString は消えました: for (let key in user) alert(key); // name 列挙可能でないプロパティは Object.keys からも除外されます。: alert(Object.keys(user)); // name
14
変更できないプロパティは削除したり変更することができません 例えば、Math.PI は書き込み不可で、列挙不可であり、変更不可です: let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": 3.141592653589793, "writable": false, "enumerable": false, "configurable": false } */ したがって、プログラマは Math.PI の値を変えることも上書きすることもできません。 Math.PI = 3; // Error // delete Math.PI もまた動作しません Math.PI を再度 writable にすることもできません。: // Error, configurable: false なので Object.defineProperty(Math, "PI", { writable: true }); Math.PI についてはは何もできません。 変更不可なプロパティの作成は一方通行であり、それを defineProperty 戻すことはできません。 注意: configurable: false はプロパティフラグの変更や削除を禁止しますが、値を変更することは可能です
15
一度に多くのプロパティが定義できるメソッド Object.defineProperties(obj, descriptors)もあります。 構文は次の通りです: Object.defineProperties(obj, { prop1: descriptor1, prop2: descriptor2 // ... }); 例えば: Object.defineProperties(user, { name: { value: "John", writable: false }, surname: { value: "Smith", writable: false }, // ... }); なので、一度に多くのプロパティをセットできます。
16
一度にすべてのプロパティのディスクリプタを取得するには、Object.getOwnPropertyDescriptors(obj) が使用できます。
17
Object.getOwnPropertyDescriptors(obj) と Object.defineProperties と合わせて、“フラグを意識して” オブジェクトをクローンする方法として使うことができます。: let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); 通常、オブジェクトをクローンするとき、次のようにプロパティをコピーするために代入を使います。: for (let key in user) { clone[key] = user[key] } …ですが、これはフラグはコピーしません
18
シンボリックプロパティもとれる
19
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { [this.name, this.surname] = value.split(" "); } }; // set fullName は指定された値で実行されます user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper 結果、“仮想” プロパティ fullName を持っており、これは読み書き可能です。
20
アクセサプロパティには、value も writable もありませんが、代わりに、get と set があります。 したがって、アクセサディスクリプタには次のものがあります: get – 引数なしの関数で、プロパティが読まれたときに動作します。 set – 1つの引数をもつ関数で、プロパティがセットされたときに呼ばれます。 enumerable – データプロパティと同じです。 configurable – データプロパティと同じです。 let user = { name: "John", surname: "Smith" }; Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { [this.name, this.surname] = value.split(" "); } }); alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ↑enumerableがfalseで作られたので列挙されない!
21
Getter/setter は、“実際の” プロパティ値のラッパーとして使用することで、それらをより詳細に制御することができます。 例えば、user で短すぎる名前を禁止したい場合、name を特別なプロパティ _name に格納することができます。そして、setter で値をフィルタします。: let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; } }; user.name = "Pete"; alert(user.name); // Pete user.name = ""; // Name is too short...
22
プロパティフラグがfalseで生成されるため、裏プロパティ見たいな意味あいになる。
23
オブジェクトは、[Prototype]] を持っており、それは null または別のオブジェクトを参照します。 そのオブジェクトは “プロトタイプ” と呼ばれます。
24
object からプロパティを読んだときに存在しない場合、JavaScriptは自動的にプロトタイプからそれを取得します。プログラミングではこのようなことを “プロトタイプ継承” と呼びます。 プロパティはプロトタイプチェーンから! 変数はレキシカルのチェーンから!
25
__proto__ を使う方法です: let animal = { eats: true }; let rabbit = { jumps: true }; rabbit.__proto__ = animal; // (*) // 今、rabbit で両方のプロパティを見つけることができます: alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true __proto__はそもそもプロトタイプのもちもののアクセサメソッドなのでセットされた場合は、プロトタイプのsetterが呼ばれ、[[prototype]]内部プロパティを変更してくれるっぽい?
26
プロトタイプの値がnullになるまで続くチェーン構造のこと
27
参照を循環させることはできません。JavaScriptは、循環するように __proto__ を割り当てようとするとエラーになります。 __proto__ の値はオブジェクトまたは null になります。プリミティブのような、それ以外のすべての値は無視されます。 明白かもしれませんが、1つの [[Prototype]] しか存在しません。 オブジェクトは2つの他のものから継承することはできません。
28
オブジェクトは [[Prototype]] プロパティを持っていて、それがプロトタイプ、つまり継承元となる。 __proto__プロパティはオブジェクト自身にもってるわけではなく [[Prototype]]の参照先のプロトタイプが持っているアクセサメソッドのこと。 同一ではないことに注意
29
admin.fullName = "Alice Cooper"; によって、プロトタイプからセッターメソッドが動く。 セッターメソッドの内容は、 set fullName(value) { [this.name, this.surname] = value.split(" "); }, なので、adminオブジェクトのプロパティにname surnameが追加される! よって、 alert(admin.fullName); でもゲッター関数が呼ばれ、 get fullName() { return `${this.name} ${this.surname}`; } }; Alice Cooper"が出力される alert(user.fullName); はJohn Smithが出力される
30
対象になる let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; // Object.keys は実親のキーだけを返します alert(Object.keys(rabbit)); // jumps // for..in は実親と継承したキー両方をループします for(let prop in rabbit) alert(prop); // jumps, eats
31
組み込みのメソッド obj.hasOwnProperty(key)が利用できます。これは obj が key という名前のプロパティをもつ場合(継承したものは含みません)、true を返します。 従って、継承したプロパティをフィルタする(あるいはそれらについて何かをする)ことが可能です。: let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; for(let prop in rabbit) { let isOwn = rabbit.hasOwnProperty(prop); if (isOwn) { alert(`Our: ${prop}`); // Our: jumps } else { alert(`Inherited: ${prop}`); // Inherited: eats } } ちなみにin演算子は継承したやつも含める
32
チェーンをみることで、メソッドは Object.prototype.hasOwnProperty で提供されていることがわかります。つまり、継承されたものです。 …ですが、なぜ hasOwnProperty は継承したプロパティもリストする for..in ループで eats や jumps のように登場しないのでしょう。 答えはシンプルです。これは列挙不可だからです。Object.prototype の他のすべてのプロパティと同様に、enumerable:false フラグを持ちます。そして、for..in は列挙可能なプロパティのみをリストします。そのため、Object.prototype のプロパティは列挙されません。
33
ならない。 Object.keys, Object.values などのほとんどの他のキー/バリュー取得メソッドは、継承したプロパティを無視します。 これらはオブジェクト自身の操作のみ行います。プロトタイプからのプロパティは考慮され ません。
34
実行コンテキストはspeedyそのものだが、プロパティを所持していないので、プロトタイプチェーンにより、親プロトタイプのstomachを参照し、更新してしまう。 つまり、ハムスター両者は同じ胃袋を共有している。 これを避けるためには、 ハムスター両方にstomachプロパティを各自作成してやるか、 eatメソッドの中で、以下のように代入すれば、各オブジェクトのプロパティが作られるので、避けることができる。 // this.stomach.push の代わりに this.stomach に代入する this.stomach = [food];
35
obj = new Object() Object は組み込みのオブジェクトコンストラクタ関数です。
36
Objectはコンストラクタ関数そのもの。 今はクラスともいう。 new Objectや{}で作成したオブジェクトはObjectクラスのインスタンス。 Objectコンストラクタ関数から作成されている。 であってるかな。。?
37
オブジェクトのプロトタイプ。 ほぼ全てのオブジェクトのプロトタイプの元祖。 toStringなどのオブジェクトの組み込みメソッドはこのプロトタイプが有している。 ちなみにObject.__proto__は空のメソッドで非推奨らしい。 インスタンスされたobj.__proto__と同一。 let obj = {}; alert(obj.__proto__ === Object.prototype); // true alert(obj.toString === obj.__proto__.toString); //true alert(obj.toString === Object.prototype.toString); //true
38
それぞれのプロトタイプに組み込み関数を有している。 例えば、配列 [1, 2, 3] を作るとき、デフォルトの new Array() コンストラクタが内部で使われます。なので、配列データは新しいオブジェクトに書き込まれ、Array.prototype はそのプロトタイプとなり、メソッドを提供します。これは非常にメモリ効率が良いです。 スペックでは、すべての組み込みのプロトタイプは先頭に Object.prototype を持っています。なので、“すべてはオブジェクトを継承している” という人もいます。 let arr = [1, 2, 3]; // Array.prototype から継承している? alert( arr.__proto__ === Array.prototype ); // true // 次に Object.prototype からは継承している? alert( arr.__proto__.__proto__ === Object.prototype ); // true // そしてトップの null alert( arr.__proto__.__proto__.__proto__ ); // null
39
覚えている通り、それらはオブジェクトではありません。しかし、それらのプロパティへアクセスをしようとした場合、組み込みのコンストラクタ String, Number, Boolean を使った一時的なラッパーオブジェクトが作られます。それらはメソッドを提供し、消えます。 それらのオブジェクトは我々には見えない形で作られ、ほとんどのエンジンはそれらを最適化しますが、スペックではこのように正確に説明されています。それらのオブジェクトのメソッドもまた String.prototype, Number.prototype や Boolean.prototype として利用可能なものとしてプロトタイプに存在します。
40
ネイティブプロトタイプは変更することができます。例えば、もしあるメソッドを String.prototype に追加した場合、それはすべての文字列で利用可能になります。: String.prototype.show = function() { alert(this); }; "BOOM!".show(); // BOOM! ただ推奨されていない。 要約すると、ネイティブプロトタイプの変更が恒常化されると、複数のライブラリ間でコンフリクトが起きやすいからとのこと。
41
ポリフィル ポリフィルは JavaScript のスペックには存在するが、特定のJavaScript エンジンではまだサポートされていないメソッドの代わりを用意することを指す用語です。 手動で実装し、組み込みのプロトタイプにそれを取り込むことができます。 例: if (!String.prototype.repeat) { // もしこのようなメソッドがない場合 // prototype に追加します String.prototype.repeat = function(n) { // 文字列を n 回繰り返す // 実際、このコードはこれより複雑になります // "n" の負の値に対するエラーのスロー // 完全なアルゴリズムは仕様にあります return new Array(n + 1).join(this); }; } alert( "La".repeat(3) ); // LaLaLa
42
1 プロトタイプのメソッドをオブジェクトのプロパティへ代入する。 let obj = { 0: "Hello", 1: "world!", length: 2, }; obj.join = Array.prototype.join; alert( obj.join(',') ); // Hello,world! 組み込みの join メソッドの内部アルゴリズムは、正しいインデックスと length プロパティのみを考慮するため、これは機能します。オブジェクトがたしかに配列かどうかはチェックしません。多くの組み込みメソッドはこのようになっています。 2 オブジェクトのプロトタイプをArray.prototypeに設定する。 ですが、obj が既に別のオブジェクトを継承していた場合にはこれは不可能です。一度に1つのオブジェクトしか継承できません。
43
オブジェクト自身はデータのみ。配列アイテム、オブジェクトプロパティ、日付 インスタンスはデータのみという考え方でよさそう
44
任意引数のラッパー関数を返す。 ラッパーかんすうでは、ms秒待機したあと、実行コンテキスト(f)の関数を実行したい。 また実行方法に着目すると f.defer(1000)(1, 2); // shows 3 after 1 sec f.defer(1000)の部分まではメソッド実行で その帰ってきた関数に対し(1, 2)で、関数実行を行っていることに注目する。 そのため、返却する関数内ではthisの扱いがundefinedになることを認識する。 よって以下のように定義できる。 Function.prototype.defer = function(ms) { //返却する関数では実行コンテキストの関数をthisで参照できないので、外部変数にとっておく。 let f = this; return function(...args) { // 外部変数にとっておいた関数を実行する。f(…args)でも問題なく動くが、メソッド実行にも対応するためapplyメソッドを利用する setTimeout(() => f.apply(this, args), ms); } }; // check it function f(a, b) { alert( a + b ); } f.defer(1000)(1, 2); // shows 3 after 1 sec f.defer(1000)で帰ってきた関数にたいして (1, 2)で実行している。 そのとき関数実行となっておりthisは失われている。 今回はthisは失われててもよい。 ただの関数を実行したいだけなので。 オブジェクトメソッドとして実行する場合は、f.defer(1000);を実行した後、戻り値の関数をオブジェクトのプロパティに登録し、そこから呼ぶとオブジェクトメソッドとしてちゃんと動く設計になっている。
45
Object.getPrototypeOf(obj) – obj の [[Prototype]] を返します。 Object.setPrototypeOf(obj, proto) – obj の [[Prototype]] に proto をセットします。 let animal = { eats: true }; // animal をプロトタイプとして新しいオブジェクトを作成する let rabbit = Object.create(animal); alert(rabbit.eats); // true alert(Object.getPrototypeOf(rabbit) === animal); // rabbit のプロトタイプを取得 Object.setPrototypeOf(rabbit, {}); // rabbit のプロトタイプを {} に変更
46
Object.create(proto[, descriptors]) – 与えられた proto を [[Prototype]] として、また任意のプロパティディスクリプタで空のオブジェクトを作ります。 let animal = { eats: true }; let rabbit = Object.create(animal, { jumps: { value: true } }); alert(rabbit.jumps); // true
47
コピー対象のプロトタイプとディスクリプタを取得しオブジェクト作成する。 let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); これは継承もプロパティフラグもシンボルもコピーされる
48
速度が重要な場合は、既存のオブジェクトの [[Prototype]] を変更しないでください 通常はオブジェクト作成時に一度だけ設定を行い、変更はしません。rabbit は animal から継承しており、それは変更しません。 また、JavaScriptエンジンは高度に最適化されています。Object.setPrototypeOf または obj.__proto__= で “その場で” プロトタイプを変更することは、可能ですがとても遅い操作になります。 そのため、何をしようとしているかわかっている場合、または JavaScript の速度が全く問題にならない場合以外は避けてください。
49
__proto__ はオブジェクトのプロパティではないです。 Object.prototype のアクセサプロパティです。: なので、もし obj.__proto__ が読み込まれたり代入された場合、該当の getter/setter がそのプロトタイプから呼ばれ、それは [[Prototype]] の取得/設定をします。 最初に言ったとおり、__proto__ は [[Prototype]] にアクセスする方法であり、[[Prototype]] 自身ではありません。
50
プロトタイプがnullで作成されたオブジェクトのこと。 let obj = Object.create(null); alert(obj); // Error (no toString) もちろん組み込み関数は使えない。
51
連想配列のオブジェクトとしてシンプルに運用したい場合 ほとんどのオブジェクトに関連したメソッドは Object.keys(obj) のように Object.something(...) であることに注目してください。それらはプロトタイプにはないので、このようなオブジェクトで機能し続けます。: let chineseDictionary = Object.create(null); chineseDictionary.hello = "ni hao"; chineseDictionary.bye = "zai jian"; alert(Object.keys(chineseDictionary)); // hello,bye toStrngとかはつかえなくなるので影響はどんなもんかわからん
52
ユーザがキーとして "__proto__" を入力する可能性があり、その場合エラーが発生し、通常は予期せぬ結果になるでしょう。 以下例のように、キー情報に__proto__が指定されてしまい、プリミティブ値を設定したとしても、その処理はシステムにより無視されてしまうのでバグを生みやすいらしい let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // [object Object], "some value" ではありません! ここでは、ユーザが __proto__ を入力した場合、その代入は無視されます! それは驚くことではありません。__proto__ プロパティは特別です: それはオブジェクトまたは null であり、文字列はプロトタイプにはなれません。 しかし、このような振る舞いを実装するつもりはありませんでした。私たちはキー/値ペアを格納したいですが、キー名が "__proto__" の場合は正しく保存されませんでした。なので、これはバグです。 ようわからんがプロトタイプのアクセサメソッドが動いた結果、プリミティブは弾かれるかんじか?
53
ただのキーとして扱われる! なぜならプロトタイプが存在しないということは、アクセサメソッドである__proto__も存在しないため セッターがないから、キーとして作れられる
54
コンストラクタ関数のprototypeプロパティを参照し、prototypeに対しプロパティを追加する。 function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert(this.name); }; let rabbit = new Rabbit("Rabbit"); インスタンスのプロトタイプ、 つまりRabbit.prototype にはインスタンスメソッドを追加できる。
55
class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // 使い方はこんなかんじ let user = new User("John"); user.sayHi();
56
クラスは、クラス内で定義したコンストラクタを含め、 コンストラクタ関数そのものとなる。 また、 クラス.prototypeのもちものとなり、コンストラクタとして登録される。 ↑の動作は通常のコンストラクタ関数であっても、コンストラクタ関数名.prototypeにコンストラクタとして登録されている。 クラスはつまり関数オブジェクトである。 クラスで定義したメソッドは そのクラス.prototypeへのメソッド設定を行う!
57
クラス宣言の結果となる User と言う名前の関数を作成します。 関数の中身は constructor メソッドです(メソッドがない場合は空と想定します)。 // 証拠: User は function です alert(typeof User); // function User.prototype に、sayHi などのクラスメソッドを格納します。
58
1 Userクラスの型はfunction 2 true 正確には User はプロトタイプの constructor メソッド 3 sayHiメソッドのコードが表示される 4 constructor, sayHi ユーザのプロトタイプのプロパティが表示される
59
いえない。 理由は 1 まず、class で生成された関数は特別な内部プロパティ [[IsClassConstructor]]: true でラベル付けされています このプロパティをチェックすることによって、以下の組み込み関数の動作が変更される。 クラスはnewなしで呼び出されないように制御されている。 class User { constructor() {} } alert(typeof User); // function User(); // Error: クラスのコンストラクタ User は `new` なしで呼び出せません 文字列変換するとclass…と出力されるようになる。 alert(User); // class User { ... } 2 クラス メソッドは列挙不可です クラス定義は、"prototype" にあるすべてのメソッドに対して enumerable フラグを false にセットします。 オブジェクトを for...in するとき、通常はクラスメソッドは必要ないのでこれは便利です。 3 クラスは常に use strict です クラス構造の中のコードはすべて自動で strict モードです。 などの機能が用意されているため!
60
する。 関数と同じように、クラスも別の式の中で定義し、渡したり、返却したり代入することができます。 これはクラス式の例です。: let User = class { sayHi() { alert("Hello"); } };
61
クラス内部で、該当クラスを表現する際に利用する 名前付き関数と同様、クラスも名前を持つことができます。 クラス式に名前がある場合、そのクラス内部でのみ見えます: // "名前付きクラス式" // (スペックにはこのような用語はありませんが、名前付き関数式と同じです) let User = class MyClass { sayHi() { alert(MyClass); // MyClass の名前はクラスの内部でのみ見えます } }; new User().sayHi(); // 動作します, MyClass の定義を表示 alert(MyClass); // error, MyClass の名前はクラスの外からは見えません
62
できる。 マジで関数と同じ扱いだとおもっておく。 function makeClass(phrase) { // クラス定義とその返却 return class { sayHi() { alert(phrase); } }; } // 新しいクラスを作成 let User = makeClass("Hello"); new User().sayHi(); // Hello
63
アクセサメソッドは User.prototype に getter / setter を作成することで機能している。 そのため また、コンストラクタでの設定に関しても インスタンスにnameプロパティは存在しないので、プロトタイプからセッターが探され、 セッターが起動する this.name = name;の処理はセッターをキックするだけなので注意。 結果的にはインスタンスにはnameプロパティではなく _nameプロパティが設定されることになる!
64
これは括弧 [...] を使用した計算されたメソッド名の例です。 class User { ['say' + 'Hi']() { alert("Hello"); } } new User().sayHi(); このような特徴は、リテラルオブジェクトに似ているので、覚えやすいと思います。
65
“クラスフィールド” は任意のプロパティが追加できる構文です。 変数ではないことに留意!! また書き方もオブジェクトリテラルとは違い key:value的な書き方ではないことに注意! 例えば、class User に name プロパティを追加しましょう。 class User { name = "John"; sayHi() { alert(`Hello, ${this.name}!`); } } new User().sayHi(); // Hello, John! つまり、宣言の中で、" = " と記述するだけです。
66
メソッドはプロトタイプのほうで管理されるが、 クラスフィールドはプロトタイプでは管理されず、個々のオブジェクト(インスタンス)にセットされる。
67
複雑な式も動作する。 動作するタイミングはインスタンズ時となる。
68
現在の構文だとクラスメソッドとして扱われ、プロトタイプに登録される。 以下のように書き換えると、クラスフィールドとして扱われる! click = () => { alert(this.value); } クラスフィールドはそれぞれのオブジェクト(インスタンス)のプロパティとなることから インスタンス生成時にアロー関数が評価され関数オブジェクトが生成される。 そのタイミングで、外側のthisとは、 外部のレキシカルであることから、 つまりインスタンスそのものを指す。 →詳しくはこの問題集の最後の問題を参照 よって以下のようなクラスフィールドを定義し、渡すとthisを失わない クラスフィールドは別の、すばらしい構文を提供します: class Button { constructor(value) { this.value = value; } click = () => { alert(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // hello
69
class MyClass { prop = value; // プロパティ constructor(...) { // コンストラクタ // ... } method(...) {} // メソッド get something(...) {} // getter set something(...) {} // setter [Symbol.iterator]() {} // 計算された名前のメソッド (ここではシンボル) // ... }
70
Rabbit.prototype.[[Prototype]] を Animal.prototype にセットします。 ↑プロトタイプチェーン形成 そのため、Rabbit.prototype でメソッドが見つからない場合には、JavaScript は Animal.prototype から取得します。 後問では静的プロパティの継承も扱う。
71
任意の式は指定できる! ただ返却値はクラスとかコンストラクタ関数である必要がある。 例えば、親クラスを生成する関数呼び出します: function f(phrase) { return class { sayHi() { alert(phrase) } } } ↑関数でクラスをかえすことで、クロージャみたいなことができる!! class User extends f("Hello") {} new User().sayHi(); // Hello ここでは、 class User は f("Hello") の結果を継承しています。
72
メソッドのオーバーライド
73
通常は親メソッドを完全に置き換えるのではなく、その上に機能の微調整または拡張を行うことを望みます。メソッド内で何かをしますが、その前後または処理中に親メソッドを呼び出します。 クラスはそのために "super" キーワードを提供しています。 super.method(...) は親メソッドを呼び出します。 例えば、うさぎ(rabbit)が止まったとき自動的に隠れさせます。: class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stopped.`); } } class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } stop() { super.stop(); // 親の stop 呼び出し this.hide(); // その後隠す } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stopped. White rabbit hides!
74
super(...) は親のコンストラクタを呼び出します(コンストラクタの内側でのみ)。
75
superはクラスメソッドである関数を評価時にホームオブジェクトを関数にバインドしており、そこから辿ることで実現している。 しかしながら、上記問題の通りクラスメソッドの中の関数に関しては、ホームオブジェクトがundefinedであるので、superを辿れずエラーとなってしまう。 アロー関数ではホームオブジェクトをアロー関数外側のレキシカルから取得するので、 以下のようにすれば問題なく動く class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // 1秒後、親の stop を実行 } }
76
クラスが別のクラスを拡張し、constructor を持たない場合、次のような constructor 暗黙的に生成されます。 class Rabbit extends Animal { // 独自のコンストラクタを持たないクラスを拡張するために生成されます constructor(...args) { super(...args); } }
77
オーバーライドしたコンストラクタは、 thisを利用する前に親側のコンストラクタを呼び出さなければならない。 つまり、1番最初に親側をつくれってこと なので以下はエラーとなる class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { this.speed = 0; this.name = name; this.earLength = earLength; } // ... } // 動作しません! let rabbit = new Rabbit("White Rabbit", 10); // Error: this は定義されていません ただしくは以下の通り class Rabbit extends Animal { constructor(name, earLength) { super(name); this.earLength = earLength; } // ... } // 今は問題ありません let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10
78
javascript内部で、親のコンストラクタを呼び出すことを期待されているから。 またその実装は 内部プロパティによって制御されており、 newキーワードなどの振る舞いに影響を与えている。 子のコンストラクタ関数は特別な内部プロパティ [[ConstructorKind]]:"derived" という、 特別な内部のラベルがつく このラベルがつくとnewの振る舞いが変更される。 通常の場合: 関数が new で実行される際、空のオブジェクトを作成し、 this に割り当てます。 派生コンストラクタが実行される場合: 親のコンストラクタを呼び出すことを期待します。 そのため、superは必須であり、なければ this =オブジェクトが作成されないままエラーとなる。
79
クラスフィールドもオーバライドできる。 ちなみに親側のプロパティとか子側のプロパティとかの概念はない。 インスタンス生成したら、親側も子側も 一緒くたにされる。 なのでクラスフィールドのオーバーライドはただ上塗りしたるだけ
80
子供のインスタンス生成時であっても、 親側の処理を先におこなうため、 親側のコンストラクタ処理では、 子側のフィールドはまだ評価されておらず、 当然オーバーライドもできていない。 子供側のフィールドを 子クラスをインスタンスする際の 実行順は以下の通り 子のコンストラクタで親コンストラクタsuper()を実行 →親のクラスフィールド評価→親のコンストラクタ評価→子のクラスフィールド評価→ 子のコンストラクタの続きを実行 これはjavascriptのみの言語仕様! クラスフィールドのオーバライドは極力おこなわず、プロトタイプのもちものであるセッターやゲッターを利用すれば、 子側でアクセサメソッドを定義していても親コンストラクタ処理時にアクセサメソッドは優先的に処理される。
81
両方ともanimalが出力される! 前問の説明などが全て。 以下は前問の説明を見ていない時の思考となる。 Rabbit には自身のコンストラクタはないので、Animal コンストラクタが呼ばれます。 そのとき親コンストラクタは常にオーバーライドされたものではなく、自身のフィールド値を利用します。 thisだからrabbitオブジェクトのnameが参照されそうにみえるのになぜ。。 以下のようなメソッドのオーバーライドではちゃんとrabbitオブジェクトのメソッドが動く!。 class Animal { showName() { // this.name = 'animal' の代わり alert('animal'); } constructor() { this.showName(); // alert(this.name); の代わり } } class Rabbit extends Animal { showName() { alert('rabbit'); } } new Animal(); // animal new Rabbit(); // rabbit なぜ。。。
82
親コンストラクタを処理しているタイミングで子クラスのクラスフィールドが初期化されてないため。 子クラスのクラスフィールドは次のタイミングで初期化されます: 子クラスのコンストラクタでsuper()を読んだ直後 ちなみに親クラスのクラスフィールドはコンストラクタの前に初期化される。
83
this.__proto__.eat.call(this); // (**) は、rabbit.eat.call(longEar)となる。 上記によって、以下が呼び出される eat() { // ...bounce around rabbit-style 親 (animal) メソッドを呼び出す this.__proto__.eat.call(this); // (*) } this.__proto__.eat.call(this); // (*) では、rabbit.eat.call(longEar)となる。 よって無限ループとなってしまう
84
孫→子→親のといった継承時に、 thisは実行コンテキストを示すので、this.__proto__の参照先は孫も子も変わらない。 孫側で実行すると子を示し 子側で実行すると子のままで親は示さないといった状況になってしまう。 これが無限ループを生んでしまう。 よって、正確に親のプロトタイプを参照できないという問題点がある。 つまり、このままではsuperの実装がうまくできない!!
85
JavaScriptはもう1つ関数のための特別な内部プロパティを追加しています: [[HomeObject]] です。 [[HomeObject]] プロパティは 関数がクラスメソッドまたはオブジェクトメソッドとして指定されたときに設定され、そのオブジェクトそのものになります。 let longEar = { __proto__: rabbit, name: "Long Ear", eat() { // [[HomeObject]] == longEar super.eat(); } }; そして、super はこれを使って親のプロトタイプとメソッドメソッドを解決しました。 longEar.eat のようなメソッドは、その [[HomeObject]] を知っており、そのプロトタイプから親のメソッドを取得します。this を使用することなく。 結構前の問題であったとおり // Unexpected super setTimeout(function() { super.stop() }, 1000); でsuperが参照できないのは、 ホームオブジェクトが失われるからだと思う
86
javascriptのオブジェクトは、バインドされない、つまり束縛されない=自由であるという原則がある。 自由であるということは、オブジェクトのコピーや渡し捜査が容易であるということ。 しかし[[HomeObject]] はメソッドそのものに束縛されるため、その原則に反する [[HomeObject]] は変更できないため、このバインドは永遠です。 [[HomeObject]] が使用される言語での唯一の場所が super です。 したがって、メソッドが super を使用しない場合、メソッドは以前として自由とみなせ、オブジェクト間でコピーできます。
87
superが固定化されるため、superが実行コンテキストの親をささず、固定化された違う親をみつけてしまう。 実行コンテキストをtreeとして呼び出した時、superが[[homeObject]]をもとに算出されており、また固定化されているため plantオブジェクトがsuperではなく、Animalを呼び出してしまっている。 これはsuperを利用せずにthis.__proto__を使ってやれば、防げそう。
88
クラスと単純なオブジェクト両方で定義されたメソッドに対して定義されています。 しかし、オブジェクトの場合、メソッドは指定された方法で正確に指定されなければなりません。: "method: function()" ではなく method() として指定する必要があります。 クラスのときクラスフィールドとして定義したインスタンスメソッドについては多分ホームオブジェクトの設定はされなさそー 我々にとってこの違いは本質的ではないかもしれませんが、JavaScript にとっては重要です。 下の例では、上の例との比較のために非メソッド構文を使っています。[[HomeObject]] プロパティはセットされず、継承は動作しません。: let animal = { eat: function() { // 短縮構文: eat() {...} にする必要があります // ... } }; let rabbit = { __proto__: animal, //このかきかただと、[[homeObject]]が設定されない!! eat: function() { super.eat(); } }; rabbit.eat(); // super 呼び出しエラー([[HomeObject]] が無いため)
89
コピーした時に、[[homeObject]]がバインドされた状態なので、superがコピー後の実行コンテキスト通りの振舞いにならず、バインドされた親を参照してしまうので、 コピーに気をつける
90
違いは画像の通り 知っている通り、 “extends” 構文は2つのプロトタイプを設定します。: それは、インスタンスで利用されるメソッド用と 静的なメソッド用の継承となる。 コンストラクタ関数の "prototype" 間(メソッド用) インスタンス→Rabbit.prototype→extends 指定の.prototype(Objectを継承しない場合は、例えばアニマルプロトタイプとかがラビットプロトタイプの親となる。) コンストラクタ関数自身の間(静的メソッド用) コンストラクタ関数自身はFunctionクラスのインスタンスなので、 コンストラクタ関数→extends指定のコンストラクタ関数→Function.prototype 我々のケースでは、class Rabbit extends Object では次を意味します: class Rabbit extends Object {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true また、extends を使わない場合 class Rabbit は2つ目の参照を持ちません。 比較してみてください: class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) // エラー、Rabbit にこのような関数はありません alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error ↑これはObjectクラスがもつ静的メソッド 画像をみるとObjectのプロトタイプはFunction.prototypeとなっている。 これはObjectクラスはコンストラクタ関数のオブジェクトだからそうなる。 ラビットコンストラクタ関数に関しては、 関数オブジェクトといえるので、 継承してなければ、親はFunction.prototypeとなる。 継承していればそのオブジェクトが親になる。 また、ラビットオブジェクトに関しては、 ラビットのプロトタイプが親となり、 ラビットのプロトタイプの親のまた親は、根源であるObject.prototypeとなる。 インスタンス用のプロトタイプ継承と静的メソッド用のコンストラクタ関数継承の二つの設定が行われると覚えること!
91
1 オブジェクトリテラルの中は key:value, ←カンマ区切り みたいな構文のみ受け付けている。 トップレベルでは変数宣言など、処理はかけない。 関数宣言はできず、関数式のみ、value部分に定義できる。 bio() {...}みたいな構文は bio:function() {…} の略である。 例 const person = { name: ["Bob", "Smith"], age: 32, bio() { console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`); }, introduceSelf() { console.log(`Hi! I'm ${this.name[0]}.`); }, }; 2 コンストラクタ関数で作成するインスタンス これは関数内なので処理がかける。 つまり、関数宣言も可能→関数内関数 ただ、コンストラクタ関数の中で関数宣言しても結局は、this.key = 関数名としてプロパティ登録することの利用用途しかなさそう。 コンストラクタ関数はthisオブジェクトを作りそれに対し設定してthisを返す。 なのでthis.key = value;でインスタンスのプロパティを作成する。 関数をインスタンスのプロパティとして登録する場合は、 this.key = 関数式って感じ。 function User(name) { // this = {}; (暗黙) // this へプロパティを追加 this.name = name; this.isAdmin = false; // return this; (暗黙) } 3 クラスから作成されるインスタンス クラスフィールドで、プロパティ作成する prop = value; また、コンストラクタ内でも同様に this.key = value;で可能。 クラスフィールド→コンストラクタの順で動くので、 クラスフィールドでは初期値などの初期化処理 また、初期化を行わないでも、明示的な宣言としても使える。 コンストラクタでは、初期化処理はかかず、引数をうけとった時の設定作業を行う。 コンストラクタ含むクラスメソッドに関しては プロトタイプのもちものとなる。 またクラスフィールドとしてもメソッドを登録できるので、それはインスタンスの持ち物となる。 class Person { name; //明示的な宣言として使える constructor(name) { this.name = name; } // クラスフィールドに定義したメソッド = インスタンスメソッド greeting = () => { console.log(`${this.name}さん、こんにちは`) } // クラスに定義したメソッド = プロトタイプメソッド callName() { console.log(this.name); } }
92
アロー関数はthisをもたず外側のthisを参照する。 では、上記問題の時の外側とはなにか? 普通に考えれば、外側はボタンクラスなので、ボタンクラスのthisってなんやねん。 となってしまう。 ただこれを頭の中で オブジェクトリテラルに置き換えてみてほしい。 全てのインスタンスはオブジェクトリテラルのシンタックスシュガーと考えてもよさそうだ。 class Button { constructor(value) { this.value = value; } click = () => { alert(this.value); } } これをnewキーワードでオブジェクトを作成すると下記のようなオブジェクトが生成される。 { value:value click:() => { alert(this.value); } } つまり、アロー関数でのthisとは newキーワードで生成したオブジェクトを指すこととなる。 ※クラスのもちものではない! さらに、アロー関数はコーディング上の外部のレキシカルではなく、 アロー関数が評価され、関数オブジェクトとして生成されたタイミングでの外部のレキシカル環境のthisを指すともいえる。 外部レキシカルからとってきているので、 thisのクロージャみたいな感じの認識だと思う。 →バインドされていると考えることができる。 なので、その関数をどこに渡そうが thisは失われてたないのである!! これは、コンストラクタ関数で登録したインスタンスメソッドのアロー関数内のthisも同様のことがいえる。
93
リテラル const a= { firstName: firstName, lastName: lastName, get fullName() { return this.firstName + " " + this.lastName; }, }; コンストラクタ関数 Object.definePropertyを使うしかない! // コンストラクタ関数 function Person3(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; Object.defineProperty( this, "fullName", { get: function () { return this.firstName + " " + this.lastName; } }); } クラス いつものかんじ
94
オブジェクト.constructor.name でとれる。 プロトタイプのもちもの コンストラクタ関数でも同じとこに格納されてる オブジェクト instanceof クラス」(真偽値) で、オブジェクトがクラスまたはそのサブクラスのインスタンスか、が分かる。左辺をプリミティブ値にすると, いつでも false になる https://www.nslabs.jp/js-class-name-of-an-object.rhtml
95
多分一緒の意味合いぽい。 でも typeOf はプリミティブ以外は関数とオブジェクトしか区別してない。 オブジェクトの中でもクラスはたくさんある。それらは型ともいえるけど上記の通り typeOfでは区別できない。 instanceOf ならクラス(プロトタイプチェーンも含む)の区別ができる。
96
for inなら プロトタイプチェーンも辿ってプロパティを出してくれる。 ただし列挙不可や、シンボルプロパティとかはとれない。 クラスで定義したクラスメソッド(プロトタイプメソッド)は列挙不可に設定されるので、微妙か。。? Object.getOwnPropertyDescriptors(obj)はシンボルや列挙不可も含めてプロパティのディスクリプタを返してくれる。 プロトタイプチェーンは辿らない。 Reflect.ownKeys(obj)てのもある この返値は、Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)) と同じです。 このどっちかかなぁ。。 組み合わせて使うか? https://qiita.com/Quest_love33/items/ef16f7e6bfcd4ac3f4b3