원래는 심플한 표현부터 깊은 표현까지 시리즈 마지막 글을 이어갈 예정이었는데, 최근 팀이 갑자기 바빠져서 그 어느때보다 바빠졌습니다! 하지만 표현을 좋아하는 친구들은 걱정하지 마세요. 제가 이미 쓰고 있으니까요 :) 직장에서 자바스크립트의 기본 원리는 다들 어느 정도 이해하고 있는 것 같아서 정리하는 시간을 좀 갖기로 했어요. 기본 지식을 모두와 공유합니다. 교육용 PPT는 추후 첨부하겠습니다. 처음에는 한 편의 글을 쓰려고 했으나, 계속 쓰다 보니 점점 더 많은 것을 알게 되어서 시리즈로 쓰기로 결심했습니다. 이 시리즈의 모든 내용은 Javascript의 기본을 포함하고 있으며, 화려한 내용은 없지만 이러한 기본적인 내용이 흥미로운 내용을 이해하는 데 도움이 될 것이라고 믿습니다.
이번 글은 꼭 알아야 할 자바스크립트 시리즈의 세 번째 글입니다. 자바스크립트가 객체지향 프로그래밍이라는 것을 주로 살펴봅니다. 주로 다음 내용을 포함합니다:
Javascript의 객체
객체란 무엇입니까
속성 탐색
객체 생성
팩토리 패턴
생성자 패턴
자세한 설명
함수 내
객체 메서드 내
생성자 내
호출 중 그리고 적용
바인드에서
dom 요소의 이벤트 핸들러 함수에서
프로토타입에 대한 자세한 설명
프로토타입이란
What 프로토타입 체인
프로토타입 체인을 사용하여 상속 구현
프로토타입 체인의 문제
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#에서는 객체가 Javascript의 키-값 쌍 집합인 경우 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
위의 순회 방법에는 프로토타입의 속성도 포함됩니다. 프로토타입이 무엇인지, 프로토타입과 인스턴스의 속성을 구별하는 방법에 대해 아래에서 설명하겠습니다.
객체 생성
실제로 위에서 객체를 생성하고 객체 생성에는 다음 두 가지 방법을 사용했습니다.
객체 인스턴스를 생성하려면 new를 사용하세요.
리터럴
위의 o는 첫 번째 방식으로 생성되었으며, o의 위치 속성은 리터럴 방식으로 생성되었습니다. 첫 번째 메서드에는 실제로 생성자 패턴이라는 이름이 있습니다. Object는 실제로 Object의 인스턴스를 생성하는 생성자이기 때문입니다. 생성자에 대해 여전히 명확하지 않다면 서둘러서 첫 번째 기사인 Type Basics Object and Object를 읽어보세요.
위의 두 가지 방법 외에도 객체를 생성하는 몇 가지 방법을 살펴보겠습니다.
Factory 패턴
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');
This There 즉, 함수 내부에 Object 인스턴스를 생성하는 것입니다. 이 인스턴스는 생성자 createPerson과 아무 관련이 없습니다.
이 객체는 new Object()를 사용하여 내부적으로 생성했기 때문에 Object의 인스턴스입니다. 따라서 그것이 어떤 함수의 인스턴스인지 알고 싶다면 불가능합니다.
생성자 패턴
팩토리 패턴은 객체 인식 문제를 해결하지는 않지만 Object()는 실제로 함수이지만 앞에 새 항목을 추가하면 생각해볼 수 있습니다. it , 이는 우리를 위해 Object 인스턴스를 생성하는 생성자가 됩니다. 그런 다음 다른 함수 앞에 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
자세한 설명
이것도 자바스크립트에서는 아주 마법같은 객체라고 할 수 있습니다. 네, 객체입니다. 이전 기사 Scope 및 Scope Chain에서 변수 객체에 대해 이야기했는데, 변수 객체는 현재 실행 환경에서 어떤 속성과 함수에 액세스할 수 있는지를 결정합니다. 이것을 변수 객체라고 생각하면 됩니다. 앞서 말했듯이 가장 큰 실행 환경은 전역 실행 환경이고, 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中文网!