Javascript基础回顾之(三) js面向对象


Posted in Javascript onJanuary 31, 2017

本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者那里的一知半解,所以决定先花一些时间整理一下这些基础知识和大家分享。 后面会附上培训用的PPT。刚开始是打算写一篇的,但是后来写着写着就发现越来越多,所以决定还是写一个系列吧。本系列所有内容都是涉及Javascript基础的,没有时髦的玩意儿,但是我相信这些基础的东西会有助于你理解那些有趣的东西的。

  • Javascript基础回顾之(一) 类型
  • Javascript基础回顾 (二) 作用域
  • Javascript基础回顾之(三) 面向对象 

本篇是你必须知道的Javascript系列第三篇,我们主要来看看Javascript是如何面向对象的编程的。主要涉及以下内容 :

  • Javascript中的对象
    • 什么是对象
    • 遍历属性
  • 创建对象
    • 工厂模式
    • 构造函数模式
  • 详解this
    • 在函数中
    • 在对象方法中
    • 在构造函数中
    • 在call和apply中
    • 在bind中
    • 在dom元素事件处理函数中
  • 详解prototype
    • 什么是原型
    • 什么是原型链
    • 利用原型链实现继承
    • 原型链中的问题

Javascript中的对象

什么是对象

我们可以把Javascript中对象理解为一组无序的键值对,就好像C#中的Dictionary<string,Object>一样。Key是属性的名称,而value可以为以下3种类型:

  1. 基本值(string, number, boolean, null, undefined)
  2. 对象
  3. 函数
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<string,Object>进行遍历的,如果说对象在Javascript中是一组键值对的话,那我们如何进行遍历呢?

for (var p in o) {
  alert('name:'+ p + 
     ' type:' + typeof o[p]
    );
}
// name:name type:string
// name:location type:object
// name:sayHello type:function

上面的这种遍历方式会把原型中的属性也包括进来,关于什么是原型,以及如何区分原型和实例中的属性我们下面会讲到。

创建对象

其实在上面我们已经创建了一个对象,并且使用了以下两种创建对象的方式。

  1. 利用new创建一个Object的实例。
  2. 字面量

我们上面的o是用第一种方式创建的,而o中的location属性则是用字面量的方式创建的。而第一种方式其实也有一种名字叫做构造函数模式,因为Object实际上是一个构造函数,为我们产生了一个Object的实例。如果对于构造函数这一块还有不清楚的话,赶紧去看我的第一篇 类型基础Object与object吧。

除了以上两种方式以外,我们一些创建对象的方式,我们也来一起看一下:

工厂模式

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是没有任何关系的。

Javascript基础回顾之(三) js面向对象

因为我在内部用new Object()来创建了这个对象,所以它是Object的实例。所以如果我们想知道它是具体哪个function的实例,那就不可能了。

构造函数模式

工厂模式没有解决对象识别的问题,但是我们可以想一下,Object()实际上也是一个函数,只不过当我在它前面加上一个new的时候,它就变成了一个构造函数为我们产生一个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

详解this

this在Javascript中也可以算是一个很神奇对象,没错this是一个对象。我们在上一篇作用域和作用域链中讲到了变量对象,变量对象决定了在当前的执行环境中有哪些属性和函数是可以被访问到的,从某种程度上来说我们就可以把this看作是这个变量对象。我们之前提到了最大的执行环境是全局执行环境,而window就是全局执行环境中的变量对象,那么我们在全局环境中this===window是会返回true的。

Javascript基础回顾之(三) js面向对象

除了全局执行环境以外,我们还提到了另外一种执行环境,也就是函数。每一个函数都有一个this对象,但有时候他们所代表的值是不一样的,主要是这个函数的调用者来决定的。我们来看一下以下几种场景:

函数

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的。

Javascript基础回顾之(三) js面向对象

详解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基础回顾之(三) js面向对象

也就是说他们内存堆中是存在多份拷贝的,而不是在栈中引用地址的拷贝。先不说这符不符合面向对象的思想,至少这对于内存来说也是一种浪费。而解决办法就是我们要讨论的原型。

什么是原型

在Javascript中的每一个函数,都会有一个原型对象,这个原型对象和我们普通的对象没有区别。只不过默认会有一个constructor属性指向这个函数。 同时,所有这个函数的实例都会有一个引用指向这个原型对象。如果不太清楚,那就看看下面这张图吧:

Javascript基础回顾之(三) js面向对象

以上就是构造函数,构造函数原型,以及实例之间的关系。以我们的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基础回顾之(三) js面向对象

利用原型实现继承

其实我们上面已经讲了继承在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中简单的实现像C#中using功能(有源码下载)
Jan 09 Javascript
js constructor的实际作用分析
Nov 15 Javascript
js根据给定的日期计算当月有多少天实现思路及代码
Feb 25 Javascript
javascript的BOM汇总
Jul 16 Javascript
jQuery Ajax 异步加载显示等待效果代码分享
Aug 01 Javascript
jQuery中 $ 符号的冲突问题及解决方案
Nov 04 Javascript
javascript 显示全局变量与隐式全局变量的区别
Feb 09 Javascript
js遍历json对象所有key及根据动态key获取值的方法(必看)
Mar 09 Javascript
vue 刷新之后 嵌套路由不变 重新渲染页面的方法
Sep 13 Javascript
微信小程序商品详情页底部弹出框
Nov 22 Javascript
使用node.JS中的url模块解析URL信息
Feb 06 Javascript
你不知道的SpringBoot与Vue部署解决方案
Nov 09 Javascript
Javascript基础回顾之(二) js作用域
Jan 31 #Javascript
Javascript基础回顾之(一) 类型
Jan 31 #Javascript
jQuery使用siblings获取某元素所有同辈(兄弟姐妹)元素用法示例
Jan 30 #Javascript
AngularJS报错$apply already in progress的解决方法分析
Jan 30 #Javascript
JavaScript基础之AJAX简单的小demo
Jan 29 #Javascript
JavaScript Date 知识浅析
Jan 29 #Javascript
JavaScript实现时钟滴答声效果
Jan 29 #Javascript
You might like
DC这些乐高系列动画电影你看过几部?
2020/04/09 欧美动漫
Php Image Resize图片大小调整的函数代码
2011/01/17 PHP
简单分析ucenter 会员同步登录通信原理
2014/08/25 PHP
thinkphp3.x中cookie方法的用法分析
2016/05/19 PHP
PHP实现基于回溯法求解迷宫问题的方法详解
2017/08/17 PHP
PHP观察者模式定义与用法实例分析
2019/03/22 PHP
菜单效果
2006/10/14 Javascript
类似框架的js代码
2006/11/09 Javascript
jquery下组织javascript代码(js函数化)
2010/08/25 Javascript
SharePoint 客户端对象模型 (一) ECMA Script
2011/05/22 Javascript
给Flash加一个超链接(推荐使用透明层)兼容主流浏览器
2013/06/09 Javascript
js中定义一个变量并判断其是否为空的方法
2014/05/13 Javascript
判断字符串的长度(优化版)中文占两个字符
2014/10/30 Javascript
jQuery实现对无序列表的排序功能(附demo源码下载)
2016/06/25 Javascript
基于jQuery实现照片墙自动播放特效
2017/01/12 Javascript
Bootstrap选项卡学习笔记分享
2017/02/13 Javascript
JavaScript下拉菜单功能实例代码
2017/03/01 Javascript
ES6模块化的import和export用法方法总结
2017/08/08 Javascript
如何在vue里添加好看的lottie动画
2018/08/02 Javascript
Vue开发环境中修改端口号的实现方法
2019/08/15 Javascript
在vue项目中使用codemirror插件实现代码编辑器功能
2019/08/27 Javascript
[05:31]DOTA2英雄梦之声_第08期_莉娜
2014/06/23 DOTA
python实现的简单文本类游戏实例
2015/04/28 Python
使用Python的Bottle框架写一个简单的服务接口的示例
2015/08/25 Python
Eclipse和PyDev搭建完美Python开发环境教程(Windows篇)
2016/11/16 Python
浅谈tensorflow1.0 池化层(pooling)和全连接层(dense)
2018/04/27 Python
python简单实现9宫格图片实例
2020/09/03 Python
CSS3中的Transition过度与Animation动画属性使用要点
2016/05/20 HTML / CSS
Avène雅漾美国官方网站:敏感肌肤护理专家
2016/10/24 全球购物
国外软件测试工程师面试题
2016/12/09 面试题
业务员简历自我评价
2014/03/06 职场文书
青蓝工程实施方案
2014/03/27 职场文书
励志演讲稿范文
2014/04/29 职场文书
暖通工程师岗位职责
2014/06/12 职场文书
2014年政协工作总结
2014/12/09 职场文书
PyQt5爬取12306车票信息程序的实现
2021/05/14 Python