JavaScript 继承详解(二)


Posted in Javascript onJuly 13, 2009

this

this表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window; 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用。 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向。

先看一个在全局作用范围内使用this的例子:

<script type="text/javascript">
   console.log(this === window); // true
   console.log(window.alert === this.alert); // true
   console.log(this.parseInt("021", 10)); // 10
  </script>

 

函数中的this是在运行时决定的,而不是函数定义时,如下:

// 定义一个全局函数
  function foo() {
   console.log(this.fruit);
  }
  // 定义一个全局变量,等价于window.fruit = "apple";
  var fruit = "apple";
  // 此时函数foo中this指向window对象
  // 这种调用方式和window.foo();是完全等价的
  foo(); // "apple"

  // 自定义一个对象,并将此对象的属性foo指向全局函数foo
  var pack = {
   fruit: "orange",
   foo: foo
  };
  // 此时函数foo中this指向window.pack对象
  pack.foo(); // "orange"

 

全局函数apply和call可以用来改变函数中this的指向,如下:

// 定义一个全局函数
  function foo() {
   console.log(this.fruit);
  }
  
  // 定义一个全局变量
  var fruit = "apple";
  // 自定义一个对象
  var pack = {
   fruit: "orange"
  };
  
  // 等价于window.foo();
  foo.apply(window); // "apple"
  // 此时foo中的this === pack
  foo.apply(pack); // "orange"
注:apply和call两个函数的作用相同,唯一的区别是两个函数的参数定义不同。

 

因为在JavaScript中函数也是对象,所以我们可以看到如下有趣的例子:

// 定义一个全局函数
  function foo() {
   if (this === window) {
    console.log("this is window.");
   }
  }
  
  // 函数foo也是对象,所以可以定义foo的属性boo为一个函数
  foo.boo = function() {
   if (this === foo) {
    console.log("this is foo.");
   } else if (this === window) {
    console.log("this is window.");
   }
  };
  // 等价于window.foo();
  foo(); // this is window.
  
  // 可以看到函数中this的指向调用函数的对象
  foo.boo(); // this is foo.
  
  // 使用apply改变函数中this的指向
  foo.boo.apply(window); // this is window.

 

prototype

我们已经在第一章中使用prototype模拟类和继承的实现。 prototype本质上还是一个JavaScript对象。 并且每个函数都有一个默认的prototype属性。
如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数。 比如下面一个简单的场景:

// 构造函数
  function Person(name) {
   this.name = name;
  }
  // 定义Person的原型,原型中的属性可以被自定义对象引用
  Person.prototype = {
   getName: function() {
    return this.name;
   }
  }
  var zhang = new Person("ZhangSan");
  console.log(zhang.getName()); // "ZhangSan"
作为类比,我们考虑下JavaScript中的数据类型 - 字符串(String)、数字(Number)、数组(Array)、对象(Object)、日期(Date)等。 我们有理由相信,在JavaScript内部这些类型都是作为构造函数来实现的,比如:
// 定义数组的构造函数,作为JavaScript的一种预定义类型
  function Array() {
   // ...
  }
  
  // 初始化数组的实例
  var arr1 = new Array(1, 56, 34, 12);
  // 但是,我们更倾向于如下的语法定义:
  var arr2 = [1, 56, 34, 12];
同时对数组操作的很多方法(比如concat、join、push)应该也是在prototype属性中定义的。
实际上,JavaScript所有的固有数据类型都具有只读的prototype属性(这是可以理解的:因为如果修改了这些类型的prototype属性,则哪些预定义的方法就消失了), 但是我们可以向其中添加自己的扩展方法。
// 向JavaScript固有类型Array扩展一个获取最小值的方法
  Array.prototype.min = function() {
   var min = this[0];
   for (var i = 1; i < this.length; i++) {
    if (this[i] < min) {
     min = this[i];
    }
   }
   return min;
  };
  
  // 在任意Array的实例上调用min方法
  console.log([1, 56, 34, 12].min()); // 1

注意:这里有一个陷阱,向Array的原型中添加扩展方法后,当使用for-in循环数组时,这个扩展方法也会被循环出来。
下面的代码说明这一点(假设已经向Array的原型中扩展了min方法):
var arr = [1, 56, 34, 12];
  var total = 0;
  for (var i in arr) {
   total += parseInt(arr[i], 10);
  }
  console.log(total); // NaN
解决方法也很简单:
var arr = [1, 56, 34, 12];
  var total = 0;
  for (var i in arr) {
   if (arr.hasOwnProperty(i)) {
    total += parseInt(arr[i], 10);
   }
  }
  console.log(total); // 103

 

constructor

constructor始终指向创建当前对象的构造函数。比如下面例子:

// 等价于 var foo = new Array(1, 56, 34, 12);
  var arr = [1, 56, 34, 12];
  console.log(arr.constructor === Array); // true
  // 等价于 var foo = new Function();
  var Foo = function() { };
  console.log(Foo.constructor === Function); // true
  // 由构造函数实例化一个obj对象
  var obj = new Foo();
  console.log(obj.constructor === Foo); // true
  
  // 将上面两段代码合起来,就得到下面的结论
  console.log(obj.constructor.constructor === Function); // true

 

但是当constructor遇到prototype时,有趣的事情就发生了。
我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:

function Person(name) {
   this.name = name;
  };
  Person.prototype.getName = function() {
   return this.name;
  };
  var p = new Person("ZhangSan");
  
  console.log(p.constructor === Person); // true
  console.log(Person.prototype.constructor === Person); // true
  // 将上两行代码合并就得到如下结果
  console.log(p.constructor.prototype.constructor === Person); // true
当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖), constructor的行为就有点奇怪了,如下示例:
function Person(name) {
   this.name = name;
  };
  Person.prototype = {
   getName: function() {
    return this.name;
   }
  };
  var p = new Person("ZhangSan");
  console.log(p.constructor === Person); // false
  console.log(Person.prototype.constructor === Person); // false
  console.log(p.constructor.prototype.constructor === Person); // false
为什么呢?
原来是因为覆盖Person.prototype时,等价于进行如下代码操作:
Person.prototype = new Object({
   getName: function() {
    return this.name;
   }
  });
而constructor始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:
function Person(name) {
   this.name = name;
  };
  Person.prototype = {
   getName: function() {
    return this.name;
   }
  };
  var p = new Person("ZhangSan");
  console.log(p.constructor === Object); // true
  console.log(Person.prototype.constructor === Object); // true
  console.log(p.constructor.prototype.constructor === Object); // true
怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:
function Person(name) {
   this.name = name;
  };
  Person.prototype = new Object({
   getName: function() {
    return this.name;
   }
  });
  Person.prototype.constructor = Person;
  var p = new Person("ZhangSan");
  console.log(p.constructor === Person); // true
  console.log(Person.prototype.constructor === Person); // true
  console.log(p.constructor.prototype.constructor === Person); // true

 


下一章我们将会对第一章提到的Person-Employee类和继承的实现进行完善。

Javascript 相关文章推荐
Javascript WebSocket使用实例介绍(简明入门教程)
Apr 16 Javascript
node.js中的path.dirname方法使用说明
Dec 09 Javascript
JavaScript面试题大全(推荐)
Sep 22 Javascript
jQuery实现大图轮播
Feb 13 Javascript
JavaScript方法_动力节点Java学院整理
Jun 28 Javascript
ES6模块化的import和export用法方法总结
Aug 08 Javascript
jQury Ajax使用Token验证身份实例代码
Sep 22 Javascript
Dropify.js图片宽高自适应的方法
Nov 27 Javascript
jQuery图片加载失败替换默认图片方法汇总
Nov 29 jQuery
微信小程序实现登录遮罩效果
Nov 01 Javascript
浅谈Node框架接入ELK实践总结
Feb 22 Javascript
vue项目中全局引入1个.scss文件的问题解决
Aug 01 Javascript
JavaScript 继承详解(一)
Jul 13 #Javascript
javascript dom 操作详解 js加强
Jul 13 #Javascript
jquery 学习笔记 传智博客佟老师附详细注释
Sep 12 #Javascript
JavaScript 事件查询综合
Jul 13 #Javascript
JavaScript 事件对象的实现
Jul 13 #Javascript
Prototype Date对象 学习
Jul 12 #Javascript
Prototype Function对象 学习
Jul 12 #Javascript
You might like
php遍历目录方法小结
2015/03/10 PHP
php获取当前url地址的方法小结
2017/01/10 PHP
PHP封装的数据库模型Model类完整示例【基于PDO】
2019/03/14 PHP
使用AmplifyJS组件配合JavaScript进行编程的指南
2015/07/28 Javascript
jQuery动画效果实现图片无缝连续滚动
2016/01/12 Javascript
jQuery form插件之formDdata参数校验表单及验证后提交
2016/01/23 Javascript
JSONP跨域请求
2017/03/02 Javascript
基于JavaScript实现瀑布流效果
2017/03/29 Javascript
聊聊Vue.js的template编译的问题
2017/10/09 Javascript
Django中使用jquery的ajax进行数据交互的实例代码
2017/10/15 jQuery
解决Vue使用swiper动态加载数据,动态轮播数据显示白屏的问题
2018/09/27 Javascript
vue进入页面时滚动条始终在底部代码实例
2019/03/26 Javascript
koa-passport实现本地验证的方法示例
2020/02/20 Javascript
[02:00]DOTA2英雄COSPLAY闹市街头巡游助威2015国际邀请赛
2015/08/02 DOTA
使用Python设置tmpfs来加速项目的教程
2015/04/17 Python
Python的Flask框架中实现登录用户的个人资料和头像的教程
2015/04/20 Python
初步解析Python下的多进程编程
2015/04/28 Python
python实现requests发送/上传多个文件的示例
2018/06/04 Python
Python图像处理实现两幅图像合成一幅图像的方法【测试可用】
2019/01/04 Python
django框架基于模板 生成 excel(xls) 文件操作示例
2019/06/19 Python
Django自定义模板过滤器和标签的实现方法
2019/08/21 Python
html5-canvas中使用clip抠出一个区域的示例代码
2018/05/25 HTML / CSS
Notino罗马尼亚网站:购买香水和化妆品
2019/07/20 全球购物
波兰办公用品和学校用品在线商店:Dlabiura24.pl
2020/11/18 全球购物
化工专业推荐信范文
2013/11/28 职场文书
单位介绍信范文
2014/01/18 职场文书
公司开业庆典策划方案
2014/06/04 职场文书
奥林匹克运动会口号
2014/06/19 职场文书
办理收楼委托书范本
2014/10/09 职场文书
2014年办公室主任工作总结
2014/11/12 职场文书
2014年机关后勤工作总结
2014/12/16 职场文书
年度考核表个人总结
2015/03/06 职场文书
员工考勤管理制度
2015/08/06 职场文书
大学生社区义工服务心得体会
2016/01/22 职场文书
在K8s上部署Redis集群的方法步骤
2021/04/27 Redis
python实现黄金分割法的示例代码
2021/04/28 Python