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代码
Aug 13 Javascript
javascript动态加载三
Aug 22 Javascript
javascript的回调函数应用示例
Feb 20 Javascript
json中换行符的处理方法示例介绍
Jun 10 Javascript
Windows 系统下安装和部署Egret的开发环境
Jul 31 Javascript
jquery+easeing实现仿flash的载入动画
Mar 10 Javascript
JavaScript实现LI列表数据绑定的方法
Aug 04 Javascript
实现非常简单的js双向数据绑定
Nov 06 Javascript
巧用Javascript的逻辑运算符
Dec 02 Javascript
jQuery幻灯片插件owlcarousel参数说明中文文档
Feb 27 jQuery
浅谈TypeScript 用 Webpack/ts-node 运行的配置记录
Oct 11 Javascript
JS轮播图的实现方法2
Aug 25 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中全局变量global的使用演示代码
2011/05/18 PHP
phpExcel导出大量数据出现内存溢出错误的解决方法
2013/02/28 PHP
PHP FATAL ERROR: CALL TO UNDEFINED FUNCTION BCMUL()解决办法
2014/05/04 PHP
微信公众平台之快递查询功能用法实例
2015/04/14 PHP
php学习笔记之字符串常见操作总结
2019/07/16 PHP
js验证是否为数字的总结
2013/04/14 Javascript
javascript使用onclick事件改变选中行的颜色
2013/12/30 Javascript
JS跨域问题详解
2014/11/25 Javascript
ECMAScript 6即将带给我们新的数组操作方法前瞻
2015/01/06 Javascript
纯JavaScript代码实现文本比较工具
2016/02/17 Javascript
Javascript的表单验证-揭开正则表达式的面纱
2016/03/18 Javascript
jQuery在ie6下无法设置select选中的解决方法详解
2016/09/20 Javascript
jQuery设置Easyui校验规则(推荐)
2016/11/21 Javascript
详解基于Node.js的微信JS-SDK后端接口实现代码
2017/07/15 Javascript
BootStrap模态框和select2合用时input无法获取焦点的解决方法
2017/09/01 Javascript
详解vue2.0监听属性的使用心得及搭配计算属性的使用
2018/07/18 Javascript
老生常谈JavaScript获取CSS样式的方法(兼容各浏览器)
2018/09/19 Javascript
javascript面向对象程序设计实践常用知识点总结
2019/07/29 Javascript
Vue移动端项目实现使用手机预览调试操作
2020/07/18 Javascript
Python 正则表达式的高级用法
2016/12/04 Python
Python MySQL数据库连接池组件pymysqlpool详解
2017/07/07 Python
分享给Python新手们的几道简单练习题
2017/09/21 Python
python3 selenium 切换窗口的几种方法小结
2018/05/21 Python
python pandas修改列属性的方法详解
2018/06/09 Python
Python 打印中文字符的三种方法
2018/08/14 Python
使用Python操作FTP实现上传和下载的方法
2019/04/01 Python
PyQt5 实现字体大小自适应分辨率的方法
2019/06/18 Python
python图片二值化提高识别率代码实例
2019/08/24 Python
python openCV实现摄像头获取人脸图片
2020/08/20 Python
python实现学生通讯录管理系统
2021/02/25 Python
html5实现多文件的上传示例代码
2014/02/13 HTML / CSS
西班牙在线宠物食品和配件商店:bitiba
2019/10/11 全球购物
构造方法和其他方法的区别
2016/04/26 面试题
新闻网站实习自我鉴定
2013/09/25 职场文书
大学生求职信怎么写
2015/03/19 职场文书
预备党员入党感言
2015/08/01 职场文书