今回は前回に引き続き、JSプロトタイプとプロトタイプチェーンについて、注意点を含めて見ていきましょう。
7. 関数オブジェクト (前の知識ポイントを確認してください)
すべての関数オブジェクトのプロトは、空の関数 (空の関数) である Function.prototype を指します
Number.__proto__ === Function.prototype // true Number.constructor == Function //true Boolean.__proto__ === Function.prototype // true Boolean.constructor == Function //true String.__proto__ === Function.prototype // true String.constructor == Function //true // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Object.__proto__ === Function.prototype // true Object.constructor == Function // true // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Function.__proto__ === Function.prototype // true Function.constructor == Function //true Array.__proto__ === Function.prototype // true Array.constructor == Function //true RegExp.__proto__ === Function.prototype // true RegExp.constructor == Function //true Error.__proto__ === Function.prototype // true Error.constructor == Function //true Date.__proto__ === Function.prototype // true Date.constructor == Function //true
ビルドされた関数がありますJavaScript の -in 合計 12 個の (組み込み) コンストラクター/オブジェクトがあります (JSON は ES5 で新たに追加されました)。アクセス可能なコンストラクターは 8 つです。 Global などの残りの部分には直接アクセスできません。引数は関数が呼び出されたときに JS エンジンによってのみ作成されます。Math と JSON はオブジェクトの形式で存在し、new を必要としません。それらのプロトは Object.prototype です。以下の通りです
Math.__proto__ === Object.prototype // true Math.construrctor == Object // true JSON.__proto__ === Object.prototype // true JSON.construrctor == Object //true
上記の関数オブジェクトにはカスタムのものも確かに含まれています。以下の通り
rreeeこれはどういう意味ですか?
** ルート コンストラクターの Object や Function 自体も含め、すべてのコンストラクターは Function.prototype から来ています。すべてのコンストラクターは Function.prototype のプロパティとメソッドを継承します。 length、call、apply、bind など**
(最初の文を理解する必要があります。次のセクションで 2 番目の文について引き続き説明します。最初に穴を掘ります:))
Function.prototype も唯一のものです。 typeof XXX.prototype は関数のプロトタイプです。他のコンストラクターのプロトタイプはオブジェクトです (理由はセクション 3 で説明されています)。以下のとおりです (再確認):
// 函数声明 function Person() {} // 函数表达式 var Perosn = function() {} console.log(Person.__proto__ === Function.prototype) // true console.log(Man.__proto__ === Function.prototype) // true
ああ、これは空の関数 console.log(Function.prototype) であることも上で述べましたが、見てください (注意してください。これについては次のセクションでもう一度説明します) )
わかりました。すべてのコンストラクター (組み込みおよびカスタムを含む) の __proto__ は Function.prototype です。では、Function.prototype の __proto__ は誰ですか?
JavaScript の関数も第一級市民であることを皆さんも聞いたことがあると思いますが、これをどのように示すことができますか?以下に示すように
console.log(typeof Function.prototype) // function console.log(typeof Object.prototype) // object console.log(typeof Number.prototype) // object console.log(typeof Boolean.prototype) // object console.log(typeof String.prototype) // object console.log(typeof Array.prototype) // object console.log(typeof RegExp.prototype) // object console.log(typeof Error.prototype) // object console.log(typeof Date.prototype) // object console.log(typeof Object.prototype) // object
これは、すべてのコンストラクターも通常の JS オブジェクトであり、コンストラクターに属性を追加/削除できることを示しています。同時に、Object.prototype のすべてのメソッド (toString、valueOf、hasOwnProperty など) も継承します。 (最初の文も理解しておく必要があります。次のセクションで 2 番目の文について引き続き説明します。穴を掘る必要はありません。それでも同じ穴です;))
Object.prototype のプロトは誰ですか最後に?
Object.prototype.__proto__ === null // true
は先頭に到達しており、null です。 (今すぐ読んで、第 5 章を振り返ってください。理解できましたか?)
8. プロトタイプ
ECMAScript コアによって定義されたすべてのプロパティの中で、最も興味深いものはプロトタイプ プロパティです。 ECMAScript の参照型の場合、プロトタイプは、すべてのインスタンス メソッドが格納される実際の場所です。つまり、toString() や valueOf() などのメソッドは実際にはプロトタイプ名で保存されますが、それぞれのオブジェクトのインスタンスを通じてアクセスされます。
——『JavaScript 高度なプログラミング』第 3 版 P116
JS には次のような組み込みメソッドがあることがわかっています。
オブジェクトは、constructor/toString()/valueOf() やその他のメソッドを使用できます。配列では、map ()/filter()/reducer() などのメソッドを使用できます。
Numbers では、parseInt()/parseFloat() などのメソッドを使用できます。 ? ?
関数を作成するとき:
var person = new Object()
Object のすべてのメソッドを継承します。プロトタイプの
オブジェクトには上記のプロパティとメソッドがあります。
それで、 Person.constructor または Person.hasOwnProperty を使用できます。
配列を作成するとき:
var num = new Array()
Array。
なぜこれが空の配列なのか? ? ? ES5 によって提供される新しいメソッド Object.getOwnPropertyNamesを使用して、プロトティ内のプロパティを除くすべてのプロパティ名 (列挙不可能なプロパティを含む) を取得し、配列を返すことができます:
var arrayAllKeys = Array.prototype; // [] 空数组// 只得到 arrayAllKeys 这个对象里所有的属性名(不会去找 arrayAllKeys.prototype 中的属性)console.log(Object.getOwnPropertyNames(arrayAllKeys));
/* 输出:
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push",
"concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach",
"some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight",
"entries", "keys", "copyWithin", "find", "findIndex", "fill"]
*/
这样你就明白了随便声明一个数组,它为啥能用那么多方法了。
细心的你肯定发现了Object.getOwnPropertyNames(arrayAllKeys) 输出的数组里并没有 constructor/hasOwnPrototype等对象的方法(你肯定没发现)。
但是随便定义的数组也能用这些方法
var num = [1];console.log(num.hasOwnPrototype()) // false (输出布尔值而不是报错)
Why ???
因为Array.prototype 虽然没这些方法,但是它有原型对象(__proto__):
// 上面我们说了 Object.prototype 就是一个普通对象。 Array.prototype.__proto__ == Object.prototype
所以 Array.prototype 继承了对象的所有方法,当你用num.hasOwnPrototype()时,JS 会先查一下它的构造函数 (Array) 的原型对象 Array.prototype 有没有有hasOwnPrototype()方法,没查到的话继续查一下 Array.prototype 的原型对象 Array.prototype.__proto__有没有这个方法。
当我们创建一个函数时:
var f = new Function("x","return x*x;"); //当然你也可以这么创建 f = function(x){ return x*x } console.log(f.arguments) // arguments 方法从哪里来的? console.log(f.call(window)) // call 方法从哪里来的? console.log(Function.prototype) // function() {} (一个空的函数) console.log(Object.getOwnPropertyNames(Function.prototype)); /* 输出 ["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply"] */
我们再复习第八小节这句话:
所有函数对象proto都指向 Function.prototype,它是一个空函数(Empty function)
嗯,我们验证了它就是空函数。不过不要忽略前半句。我们枚举出了它的所有的方法,所以所有的函数对象都能用,比如:
九. 复习一下
第八小节我们总结了:
所有函数对象的 __proto__ 都指向 Function.prototype,它是一个空函数(Empty function)
但是你可别忘了在第三小节我们总结的:
所有对象的 __proto__ 都指向其构造器的 prototype
我们下面再复习下这句话。
先看看 JS 内置构造器:
var obj = {name: 'jack'} var arr = [1,2,3] var reg = /hello/g var date = new Date var err = new Error('exception') console.log(obj.__proto__ === Object.prototype) // true console.log(arr.__proto__ === Array.prototype) // true console.log(reg.__proto__ === RegExp.prototype) // true console.log(date.__proto__ === Date.prototype) // true console.log(err.__proto__ === Error.prototype) // true
再看看自定义的构造器,这里定义了一个 Person:
function Person(name) { this.name = name; } var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true
p 是 Person 的实例对象,p 的内部原型总是指向其构造器 Person 的原型对象 prototype。
每个对象都有一个 constructor 属性,可以获取它的构造器,因此以下打印结果也是恒等的:
function Person(name) { this.name = name } var p = new Person('jack') console.log(p.__proto__ === p.constructor.prototype) // true
上面的Person没有给其原型添加属性或方法,这里给其原型添加一个getName方法:
function Person(name) { this.name = name } // 修改原型 Person.prototype.getName = function() {} var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // true
可以看到p.__proto__与Person.prototype,p.constructor.prototype都是恒等的,即都指向同一个对象。
如果换一种方式设置原型,结果就有些不同了:
function Person(name) { this.name = name } // 重写原型Person.prototype = { getName: function() {} } var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // false
这里直接重写了 Person.prototype(注意:上一个示例是修改原型)。输出结果可以看出p.__proto__仍然指向的是Person.prototype,而不是p.constructor.prototype。
这也很好理解,给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。如下:
var p = {} console.log(Object.prototype) // 为一个空的对象{} console.log(p.constructor === Object) // 对象直接量方式定义的对象其constructor为Objectconsole.log(p.constructor.prototype === Object.prototype) // 为true, 不解释(๑ˇ3ˇ๑)
十. 原型链(再复习一下:)
下面这个例子你应该能明白了!
function Person(){ }var person1 = new Person(); console.log(person1.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype) //true console.log(Object.prototype.__proto__) //null Person.__proto__ == Function.prototype; //true console.log(Function.prototype)// function(){} (空函数) var num = new Array()console.log(num.__proto__ == Array.prototype) // true console.log( Array.prototype.__proto__ == Object.prototype) // true console.log(Array.prototype) // [] (空数组) console.log(Object.prototype.__proto__) //null console.log(Array.__proto__ == Function.prototype)// true
疑点解惑:
Object.__proto__ === Function.prototype // true
Object 是函数对象,是通过new Function()创建的,所以Object.__proto__指向Function.prototype。(参照第八小节:「所有函数对象的__proto__都指向Function.prototype」)
Function.__proto__ === Function.prototype // true
Function 也是对象函数,也是通过new Function()创建,所以Function.__proto__指向Function.prototype。
自己是由自己创建的,好像不符合逻辑,但仔细想想,现实世界也有些类似,你是怎么来的,你妈生的,你妈怎么来的,你姥姥生的,……类人猿进化来的,那类人猿从哪来,一直追溯下去……,就是无,(NULL生万物)
正如《道德经》里所说“无,名天地之始”。
Function.prototype.__proto__ === Object.prototype //true
其实这一点我也有点困惑,不过也可以试着解释一下。
Function.prototype是个函数对象,理论上他的__proto__应该指向 Function.prototype,就是他自己,自己指向自己,没有意义。
JS一直强调万物皆对象,函数对象也是对象,给他认个祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保证原型链能够正常结束。
十一 总结
原型和原型链是JS实现继承的一种模型。
原型链的形成是真正是靠__proto__ 而非prototype
要深入理解这句话,我们再举个例子,看看前面你真的理解了吗?
var animal = function(){}; var dog = function(){}; animal.price = 2000; dog.prototype = animal; var tidy = new dog(); console.log(dog.price) //undefined console.log(tidy.price) // 2000
这里解释一下:
var dog = function(){}; dog.prototype.price = 2000; var tidy = new dog(); console.log(tidy.price); // 2000 console.log(dog.price); //undefined var dog = function(){}; var tidy = new dog(); tidy.price = 2000; console.log(dog.price); //undefined
这个明白吧?想一想我们上面说过这句话:
实例(tidy)和 原型对象(dog.prototype)存在一个连接。不过,要明确的真正重要的一点就是,这个连接存在于实例(tidy)与构造函数的原型对象(dog.prototype)之间,而不是存在于实例(tidy)与构造函数(dog)之间。
聪明的你肯定想通了吧 :)
以上がJSプロトタイプとプロトタイプチェーンの詳しい解説(3)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。