元々は、簡単な表現から深い表現までシリーズの最後の記事の続きを書くつもりでしたが、最近チームは急に忙しくなり、これまで以上に忙しくなりました。でも、式が好きな友人の皆さん、心配しないでください。私はすでにそれを書いています:) 職場では、誰もがあちこちで Javascript の基本原則を少しは理解していることがわかったので、これらを整理するのに時間を費やすことにしました。基礎知識を身につけ、みんなで共有しましょう。 研修用のPPTは後ほど添付いたします。最初は1記事のつもりでしたが、書いていくうちに記事が増えてきたのでシリーズで書くことにしました。このシリーズの内容はすべて Javascript の基礎に関するものであり、派手なものはありませんが、これらの基本的な事項が面白さを理解するのに役立つと思います。
この記事は、知っておくべき Javascript シリーズの 3 番目の記事であり、主に Javascript がどのようにオブジェクト指向プログラミングであるかについて説明します。主に以下の内容です:
JavaScriptのオブジェクト
オブジェクトとは
プロパティのトラバース
オブジェクトの作成
ファクトリパターン
コンストラクターパターン
これについての詳細な説明
関数内
オブジェクトメソッド内
コンストラクター内
callとapply内
bind内
dom要素イベントハンドラー関数内
プロトタイプの詳しい説明
プロトタイプとは
プロトタイプチェーンとは
プロトタイプチェーンを使用して継承を実装する
プロトタイプチェーン内の質問
JavaScript のオブジェクト
オブジェクトとは何ですか
JavaScript のオブジェクトは、C# の Dictionary
基本値 (文字列、数値、ブール値、null、未定義)
オブジェクト
関数
var o = new Object(); o["name"] = "jesse"; //基本值作为对象属性 o["location"] = { //对象作为对象属性 "city": "Shanghai", "district":"minhang" }; // 函数 作为对象属性 o["sayHello"] = function () { alert("Hello, I am "+ this.name + " from " + this.location.city); } o.sayHello();
プロパティのトラバース
C# foreach を使用して Dictionary
for (var p in o) { alert('name:'+ p + ' type:' + typeof o[p] ); } // name:name type:string // name:location type:object // name:sayHello type:function
上記のトラバースメソッドにはプロトタイプの属性も含まれます。プロトタイプとは何か、およびプロトタイプとインスタンスの属性を区別する方法については以下で説明します。
オブジェクトの作成
実際に上記でオブジェクトを作成しましたが、次の2つの方法でオブジェクトを作成しました。
new を使用して Object のインスタンスを作成します。
リテラル
上記の o は最初の方法で作成され、o の location 属性はリテラルな方法で作成されました。最初のメソッドには実際にはコンストラクター パターンという名前が付いています。これは、Object が実際には Object のインスタンスを生成するコンストラクターであるためです。コンストラクターについてまだよくわからない場合は、急いで最初の記事「型の基本オブジェクトとオブジェクト」を読んでください。
上記の 2 つの方法に加えて、オブジェクトを作成するいくつかの方法も見てみましょう:
ファクトリ パターン
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson('Jesse', 29, 'Software Engineer'); var person2 = createPerson('Carol', 27, 'Designer');
このモードで作成されたオブジェクトには問題があります。関数内で Object のインスタンスを作成しましたが、これはコンストラクター createperson とは何の関係もありません。
このオブジェクトは内部で new Object() を使用して作成したため、Object のインスタンスです。したがって、それがどの関数のインスタンスであるかを知りたいとしても、それは不可能です。
コンストラクターパターン
ファクトリパターンはオブジェクトの識別の問題を解決しませんが、Object()は実際には関数ですが、その前にnewを追加すると、コンストラクターがインスタンスを作成するようになります。私たちにとってのオブジェクト。次に、他の関数の前に new を追加して、この関数のインスタンスを生成することもできます。これがいわゆるコンストラクター パターンです。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var p1 = new Person('Jesse', 18, 'coder'); alert(p1 instanceof Person); // true
これについて詳しく説明します
これは、JavaScript では非常に魔法のオブジェクトとみなすことができます。はい、これはオブジェクトです。変数オブジェクトについては、前の記事「スコープ」と「スコープ チェーン」で説明しました。変数オブジェクトは、現在の実行環境でアクセスできるプロパティと関数を決定します。この変数オブジェクトは、ある程度まで使用できます。最大の実行環境はグローバル実行環境であり、window はグローバル実行環境の変数オブジェクトであると前述しました。したがって、グローバル環境で this===window を使用すると、true が返されます。
グローバル実行環境に加えて、関数である別の実行環境についても説明しました。各関数には this オブジェクトがありますが、それらが表す値が異なる場合があり、主にこの関数の呼び出し元によって決定されます。次のシナリオを見てみましょう:
function
function f1(){ return this; } f1() === window; // global object
因为当前的函数在全局函数中运行,所以函数中的this对象指向了全局变量对象,也就是window。这种方式在严格模式下会返回undefined。
对象方法
var o = { prop: 37, f: function() { return this.prop; } }; console.log(o.f()); // logs 37
在对象方法中,this对象指向了当前这个实例对象。注意: 不管这个函数在哪里什么时候或者怎么样定义,只要它是一个对象实例的方法,那么它的this都是指向这个对象实例的。
var o = { prop: 37 }; var prop = 15; function independent() { return this.prop; } o.f = independent; console.log(independent()); // logs 15 console.log(o.f()); // logs 37
区别:上面的函数independent如果直接执行,this是指向全局执行环境,那么this.prop是指向我们的全局变量prop的。但是如果将independent设为对象o的一个属性,那么independent中的this就指向了这个实例,同理this.prop就变成了对象o的prop属性。
构造函数
我们上面讲到了用构造函数创建对象,其实是利用了this的这种特性。在构造函数中,this对象是指向这个构造函数实例化出来的对象。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; } var p1 = new Person('Jesse', 18, 'coder'); var p2 = new Person('Carol',16,'designer');
当我们实例化Person得到p1的时候,this指向p1。而当我们实例化Person得到p2的时候,this是指向p2的。
利用call和apply
当我们用call和apply去调用某一个函数的时候,这个函数中的this对象会被绑定到我们指定的对象上。而call和apply的主要区别就是apply要求传入一个数组作为参数列表。
function add(c, d) { return this.a + this.b + c + d; } var o = { a: 1, b: 3 }; // 第一个参数会被绑定成函数add的this对象 add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 // 第二个参数是数组作为arguments传入方法add add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
在bind方法中
bind方法是 存在于function的原型中的 Function.prototype.bind,也就是说所有的function都会有这个方法。但我们调用某一个方法的bind的时候,会产生一个和原来那个方法一样的新方法,只不过this是指向我们传得bind的第一个参数。
function f() { return this.a; } var g = f.bind({ a: "azerty" }); console.log(g()); // azerty var o = { a: 37, f: f, g: g }; console.log(o.f(), o.g()); // 37, azerty
在dom元素事件处理器中
在事件处理函数中,我们的this是指向触发这个事件的dom元素的。
HTML代码
<html> <body> <div id="mydiv" style="width:400px; height:400px; border:1px solid red;"></div> <script type="text/javascript" src="essence.js"></script> </body> </html>
JavaScript代码
function click(e) { alert(this.nodeName); } var myDiv = document.getElementById("mydiv"); myDiv.addEventListener('click', click, false);
当我们点击页面那个div的时候,毫无疑问,它是会显示DIV的。
详解prototype
prototype即原型,也是Javascrip中一个比较重要的概念。在说原型之前呢,我们需要回顾一下之前的构造函数模式。在我们用构造函数去创建对象的时候主要是利用了this的特性。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; } var p1 = new Person('Jesse', 18, 'coder'); var p2 = new Person('Carol', 17, 'designer');
我们上面还讲到了当用Person实例化p1的时候Person中的this是指向p1的,当实例化p2的时候呢,this是指向p2的。那也就是说,p1和p2中的sayName虽然起到了同样的作用,但是实际上他们并非是一个函数。
也就是说他们内存堆中是存在多份拷贝的,而不是在栈中引用地址的拷贝。先不说这符不符合面向对象的思想,至少这对于内存来说也是一种浪费。而解决办法就是我们要讨论的原型。
什么是原型
在Javascript中的每一个函数,都会有一个原型对象,这个原型对象和我们普通的对象没有区别。只不过默认会有一个constructor属性指向这个函数。 同时,所有这个函数的实例都会有一个引用指向这个原型对象。如果不太清楚,那就看看下面这张图吧:
以上就是构造函数,构造函数原型,以及实例之间的关系。以我们的Person构造函数为例,所有Person的实例(p1,p2)都舒服一个prototype属性指向了Person构造函数prototype对象。如此一来,我们就可以把方法写在原型上,那么我们所有的实例就会访问同一个方法了。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; Person.prototype.sayName = function () { alert(this.name); } } var p1 = new Person('Jesse', 18, 'coder'); var p2 = new Person('Carol', 17, 'designer'); alert(p1.sayName == p2.sayName); // true
什么是原型链
大家还记得作用域链么?如果不记得,请自觉到第二篇中去复习(作用域和作用域链)。简单的来说,我们在一个执行环境中访问某个变量的时候如果当前这个执行环境中不存在这个变量,那么会到这个执行环境的包含环境也就是它的外层去找这个变量,外层还找不到那就再外一层,一直找到全局执行环境为止,这就是作用域链。而原型链有点类型,只不过场景换到了我们的对象实例中,如果我在一个实例中找某一个属性,这个实例中没有,那就会到它的原型中去找。记住,我们上面说了,原型也是一个对象,它也有自己的原型对象,所以就行成了一个链,实例自己的原型中找不到,那就到原型的原型对象中去找,一直向上延伸到Object的原型对象,默认我们创建的函数的原型对象它自己的原型对象是指向Object的原型对象的,所以这就是为什么我们可以在我们的自定义构造函数的实例上调用Object的方法(toString, valueOf)。
利用原型实现继承
其实我们上面已经讲了继承在Javascript中的实现,主要就是依靠原型链来实现的。所有的实例是继承自object就是因为在默认情况下,我们所有创建函数的原型对象的原型都指向了object对象。同理,我们可以定义自己的继承关系。
function Person(name, age, job) { this.name = name; this.age = age; } Person.prototype.sayName = function () { alert(this.name); } function Coder(language){ this.language = language; } Coder.prototype = new Person(); //将 Coder 的原型指向一个Person实例实现继Person Coder.prototype.code = function () { alert('I am a '+ this.language +' developer, Hello World!'); } function Designer() { } Designer.prototype = new Person(); //将 Desiger 的原型指向一个Person实例实现继Person Designer.prototype.design = function () { alert('其实我只是一个抠图工而已。。。。'); } var coder = new Coder('C#'); coder.name = 'Jesse'; coder.sayName(); //Jesse coder.code(); // I am a C# developer, Hello World! var designer = new Designer(); designer.name = 'Carol'; designer.sayName(); // Carol designer.design(); // 其实我只是一个抠图工而已。。。。
原型链中的问题
由于原型对象是以引用的方式保存的,所以我们在赋值的时候要特别注意,一不小心就有可能把之前赋的值给赋盖了。比如上面的代码中,我们先写原型方法,再实现继承,那我们的原型方法就没有了。
function Coder(language){ this.language = language; } Coder.prototype.code = function () { alert('I am a '+ this.language +' developer, Hello World!'); } Coder.prototype = new Person(); //这里会覆盖上面所有的原型属性和方法 var coder = new Coder('C#'); coder.name = 'Jesse'; coder.sayName(); coder.code(); // 这里会报错,找不到code方法。
这样三篇文章都完成了
更多Javascript基础回顾之(三) js面向对象相关文章请关注PHP中文网!