js老生常谈之this,constructor ,prototype全面解析


Posted in Javascript onApril 05, 2016

前言

javascript中的this,constructor ,prototype,都是老生常谈的问题,深入理解他们的含义至关重要。在这里,我们再来复习一下吧,温故而知新!

this

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

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

console.log(this === window); // true
console.log(window.alert === this.alert); // true
console.log(this.parseInt("021", 10)); // 10

函数中的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本质上还是一个JavaScript对象。

并且每个函数都有一个默认的prototype属性。 如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数。 比如下面一个简单的场景:

// 构造函数
    function Person(name) {
      this.name = name;
    }
    // 定义Person的原型,原型中的属性可以被自定义对象引用
    Person.prototype = {
      getName: function() {
        return this.name;
      }
    }
    var hao= new Person("haorooms");
    console.log(hao.getName());  // "haorooms"

作为类比,我们考虑下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("haorooms");

    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("haorooms");
    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("haorooms");
    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 = {
      getName: function() {
        return this.name;
      }
    };
    Person.prototype.constructor = Person;
    var p = new Person("haorooms");
    console.log(p.constructor === Person); // true
    console.log(Person.prototype.constructor === Person); // true
    console.log(p.constructor.prototype.constructor === Person); // true

也可以这么写:

function Person(name) {
      this.name = name;
    };
    Person.prototype = {
     constructor:Person,//指定constructor
      getName: function() {
        return this.name;
      }
    };

以上这篇js老生常谈之this,constructor ,prototype全面解析就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript对象、属性、事件手册集合方便查询
Jul 04 Javascript
国外大牛IE版本检测!现在IE都到9了,IE检测代码
Jan 04 Javascript
javascript学习笔记--数字格式类型
May 22 Javascript
jQuery中:enabled选择器用法实例
Jan 04 Javascript
jQuery+css3实现Ajax点击后动态删除功能的方法
Aug 10 Javascript
实例解析jQuery中如何取消后续执行内容
Dec 01 Javascript
JS中检测数据类型的几种方式及优缺点小结
Dec 12 Javascript
Vuex的初探与实战小结
Nov 26 Javascript
如何让node运行es6模块文件及其原理详解
Dec 11 Javascript
JS立即执行的匿名函数用法分析
Nov 04 Javascript
JS轮播图的实现方法
Aug 24 Javascript
Vue组件生命周期运行原理解析
Nov 25 Vue.js
实例详解ECMAScript5中新增的Array方法
Apr 05 #Javascript
关于JS中的apply,call,bind的深入解析
Apr 05 #Javascript
javascript中apply、call和bind的使用区别
Apr 05 #Javascript
JavaScript数组去重的两种方法推荐
Apr 05 #Javascript
jquery Deferred 快速解决异步回调的问题
Apr 05 #Javascript
JS模拟简易滚动条效果代码(附demo源码)
Apr 05 #Javascript
JS中的forEach、$.each、map方法推荐
Apr 05 #Javascript
You might like
php5 图片验证码实现代码
2009/12/11 PHP
php和mysql中uft-8中文编码乱码的几种解决办法
2012/04/19 PHP
php 文件上传实例代码
2012/04/19 PHP
smarty表格换行实例
2014/12/15 PHP
PHP实现自动对图片进行滚动显示的方法
2015/03/12 PHP
PHP实现递归无限级分类
2015/10/22 PHP
extjs 学习笔记(三) 最基本的grid
2009/10/15 Javascript
ASP小贴士/ASP Tips javascript tips可以当桌面
2009/12/10 Javascript
一个js拖拽的效果类和dom-drag.js浅析
2010/07/17 Javascript
分享一个自定义的console类 让你不再纠结JS中的调试代码的兼容
2012/04/20 Javascript
基于jQuery判断两个元素是否有重叠部分的代码
2012/07/25 Javascript
火狐textarea输入法的bug的触发及解决
2013/07/24 Javascript
jquery退出each循环的写法
2014/02/26 Javascript
JavaScript插件化开发教程(六)
2015/02/01 Javascript
JavaScript保存并运算页面中数字类型变量的写法
2015/07/06 Javascript
JS实现获取剪贴板内容的方法
2016/06/21 Javascript
jQuery窗口拖动功能的实现代码
2017/02/04 Javascript
js获取json中key所对应的value值的简单方法
2020/06/17 Javascript
js实现打字小游戏
2019/12/17 Javascript
vue 使用v-for进行循环的实例代码详解
2020/02/19 Javascript
如何在Python中编写并发程序
2016/02/27 Python
Python编写电话薄实现增删改查功能
2016/05/07 Python
利用Python获取操作系统信息实例
2016/09/02 Python
python逐行读写txt文件的实例讲解
2018/04/03 Python
基于Django与ajax之间的json传输方法
2018/05/29 Python
Python Threading 线程/互斥锁/死锁/GIL锁
2019/07/21 Python
selenium中get_cookies()和add_cookie()的用法详解
2020/01/06 Python
股东合作协议书
2014/04/14 职场文书
产品包装策划方案
2014/05/18 职场文书
幼儿园教师求职信
2015/03/20 职场文书
2015年爱牙日活动总结
2015/03/23 职场文书
银行催款通知书
2015/04/17 职场文书
2016大学迎新欢迎词
2015/09/29 职场文书
分位数回归模型quantile regeression应用详解及示例教程
2021/11/02 Python
TV动画「神渣☆爱豆」公开第一弹主视觉图
2022/03/21 日漫
简单聊聊TypeScript只读修饰符
2022/04/06 Javascript