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代码
Dec 26 Javascript
在IE浏览器中resize事件执行多次的解决方法
Jul 12 Javascript
Function.prototype.bind用法示例
Sep 16 Javascript
jQuery extend 的简单实例
Sep 18 Javascript
原生js实现淘宝首页点击按钮缓慢回到顶部效果
Apr 06 Javascript
JS实现鼠标滑过折叠与展开菜单效果代码
Sep 06 Javascript
js面向对象之常见创建对象的几种方式(工厂模式、构造函数模式、原型模式)
Nov 09 Javascript
javascript获取以及设置光标位置
Feb 16 Javascript
JavaScript错误处理和堆栈追踪详解
Apr 18 Javascript
解决VUEX刷新的时候出现数据消失
Jul 03 Javascript
Vue安装浏览器开发工具的步骤详解
May 12 Javascript
微信小程序swiper使用网络图片不显示问题解决
Dec 13 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生成短网址示例
2014/05/05 PHP
PHP判断数据库中的记录是否存在的方法
2014/11/14 PHP
orm获取关联表里的属性值
2016/04/17 PHP
PHP session 会话处理函数
2016/06/06 PHP
Javascript图像处理—虚拟边缘介绍及使用方法
2012/12/27 Javascript
百度地图api应用标注地理位置信息(js版)
2013/02/01 Javascript
jQuery动画animate方法使用介绍
2013/05/06 Javascript
javascript实现图片循环渐显播放的方法
2015/02/24 Javascript
基于jQuery实现的扇形定时器附源码下载
2015/10/20 Javascript
JS与HTML结合使用marquee标签实现无缝滚动效果代码
2016/07/05 Javascript
js判断一个字符串是以某个字符串开头的简单实例
2016/12/27 Javascript
AngularJs 常用的过滤器
2017/05/15 Javascript
javascript+jQuery实现360开机时间显示效果
2017/11/03 jQuery
微信小程序实现tab页面切换功能
2018/07/13 Javascript
使用jQuery如何写一个含验证码的登录界面
2019/05/13 jQuery
layui监听单元格编辑前后交互的例子
2019/09/16 Javascript
kafka调试中遇到Connection to node -1 could not be established. Broker may not be available.
2019/09/17 Javascript
解决vue组件销毁之后计时器继续执行的问题
2020/07/21 Javascript
Python学习教程之常用的内置函数大全
2017/07/14 Python
Python数字图像处理之霍夫线变换实现详解
2018/01/12 Python
基于python实现自动化办公学习笔记(CSV、word、Excel、PPT)
2019/08/06 Python
Python多线程爬取豆瓣影评API接口
2019/10/22 Python
Python 实现敏感目录扫描的示例代码
2020/05/21 Python
迪梵英国官方网站:Darphin英国
2017/12/06 全球购物
英国计算机商店:Technextday
2019/12/28 全球购物
小学作文评语大全
2014/04/21 职场文书
店铺转让协议书(2014版)
2014/09/23 职场文书
人大代表选举标语
2014/10/07 职场文书
2014年小学数学工作总结
2014/12/12 职场文书
农村婚庆主持词
2015/06/29 职场文书
企业文化学习心得体会
2016/01/21 职场文书
祝福语集锦:给妹妹结婚的祝福语
2019/12/18 职场文书
pytorch实现线性回归以及多元回归
2021/04/11 Python
redis哨兵常用命令和监控示例详解
2021/05/27 Redis
opencv检测动态物体的实现
2021/07/21 Python
Redis 操作多个数据库的配置的方法实现
2022/03/23 Redis