JavaScript 构造函数 prototype属性和_proto_和原型链 constructor属性 apply(),call()和bind() 关键字this
1.构造函数:
通常构造函数首字母需要大写,主要是为了区别ECMAScript的其它函数。(高程三 P145)
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。只要通过new来调用,任何函数都是构造函数;而任何函数,如果不通过new来调用,那么它和普通函数也没有任何区别。(P146)
所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
(就是一个普通的函数,与其他函数没有任何区别,可以理解为 函数==构造函数,它只是概念上的一个定义,使用它用来实例化对象。)
对于JavaScript的内置对象,Object、Array、Date等等这些都是构造函数。
2. prototype(prototype属性)和 _proto_
阮一峰 继承机制的设计思想 __proto__ VS. prototype in JavaScript 深入理解JavaScript系列(10):JavaScript核心(晋级高手必读篇)
The use of __proto__
is controversial, and has been discouraged(有争议,不被鼓励的). It was never originally included in the EcmaScript language spec, but modern browsers decided to implement it anyway. Only recently, the __proto__
property has been standardized in the ECMAScript 2015 language specification for web browsers to ensure compatibility, so will be supported into the future.
_proto_
是所有对象(包括函数)都有的,它才叫做对象的原型,原型链就是靠它形成的。(如果不是实在没有别的办法,是非常不建议在代码中使用的。)
所有构造器/函数(函数也是对象)的__proto__都指向Function.prototype,它是一个空函数(Empty function)
Number.__proto__ === Function.prototype // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
RegExp.__proto__ === Function.prototype // true
Error.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype // true
JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的__proto__是Object.prototype。如下
Math.__proto__ === Object.prototype // true
JSON.__proto__ === Object.prototype // true
上面说的“所有构造器/函数”当然包括自定义的。如下
// 函数声明
function Person() {}
// 函数表达式
var Man = function() {}
console.log(Person.__proto__ === Function.prototype) // true
console.log(Man.__proto__ === Function.prototype) // true
函数也是对象,构造函数也是对象,可以理解为:构造函数是由“Function构造函数“实例化出来的函数对象。
这说明什么呢?
所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind(ES5)。
知道了所有构造器(含内置及自定义)的__proto__都是Function.prototype,那Function.prototype的__proto__是谁呢?
相信都听说过JavaScript中函数也是一等公民,那从哪能体现呢?如下
console.log(Function.prototype.__proto__ === Object.prototype)
这说明所有的构造器也都是一个普通JS对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。
最后Object.prototype的__proto__是谁?
Object.prototype.__proto__ ===
null
// true
已经到顶了,为null。
实例是没有prototype属性的,prototype
只有函数(准确地说是构造函数)才有的。它跟原型链没有半毛钱关系。
它的作用是:构造函数new对象的时候,告诉构造函数新创建的对象的原型是谁。是的,只在new一个对象的时候才起作用。当你new完得到这个对象后,随便你怎么改构造函数的prototype
属性,都不会影响已创建的对象的原型链。
function Father(){
// Code goes here
}
var obj1 = new Father(); // 此时,
Father.prototype = {
name:"hhhhh",
last:"wwwww",
sayName:function(){
alert(this.name);
}
}
obj1.sayName() // error
// 因为,构造函数的prototype属性被重写了,js的对象都是不相等的
调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数。
不过一般来说,我们会使用__<内部属性名>__ 下划线来代替双括号,例如__proto__(这是某些脚本引擎比如SpiderMonkey的对于原型概念的具体实现,尽管并非标准).
如果一个对象的prototype没有显示的声明过或定义过,那么__proto__的默认值就是object.prototype, 而object.prototype也会有一个__proto__, 这个就是原型链的终点了,被设置为null。
2.1 prototype
Object.prototype The Object.prototype
property (是对象的属性)represents the Object
prototype object. (prototype对象)
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。
这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用(prototype对象)的。
2.2 _proto_
参考资料 JavaScript中__proto__与prototype的关系 理解js中的原型链,prototype与__proto__的关系 参考资料三
原型链如图所示:
3. constructor:(constructor属性)
MDN Object.prototype.constructor
Returns a reference to the Object
constructor function that created the instance object(返回生成该实例的构造函数). Note that the value of this property is a reference to the function itself, not a string containing the function's name.
constructor是部署在Object.prototype上的属性,所以位于原型链的最高一层(再高一层是null),任何对象(包括函数,都有constructor属性)
任何一个prototype对象都有一个constructor属性,指向它的构造函数。
eg: Cat.prototype.constructor = Cat;
每一个实例也有一个constructor属性(原型包含constructor属性,因此可以通过对象实例访问),默认调用prototype对象的constructor属性,即也指向它的构造函数.
eg: var new cat = Cat(){};
cat.constructor = Cat;
原型对象的修改和重写是不一样的。
修改:添加删除属性还是在原来的prototype对象上做的修改。
重写:直接就用新的字面量对象来替换了,而字面量对象的constructor就是Object构造函数
4. apply(),call(),bind()
每个函数都包含两个非继承而来的方法:call() 和 apply()
这两个函数等于设置函数体内this对象的值
call和apply是Function的方法,第一个参数是this,第二个是Function的参数。比如函数里写了this,普通调用这个方法这个this可能是window。而如果使用了call()或是apply(),第一个参数写啥,里面的this就是啥。(即改变了this的指向)
call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。因为 JavaScript 的函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。
普通的函数调用 是隐式的传入 this,call 和 apply 可以显式指定它。
call() 和apply() 真正强大的地方是能够扩充函数赖以运行的作用域,而且好处在于对象不需要与方法有任何的耦合关系。
bind()
5. this关键字
在函数代码中使用this时很有趣,这种情况很难且会导致很多问题。
这种类型的代码中,this值的首要特点(或许是最主要的)是它不是静态的绑定到一个函数。
正如我们上面曾提到的那样,this是进入上下文时确定,在一个函数代码中,这个值在每一次完全不同。
不管怎样,在代码运行时的this值是不变的,也就是说,因为它不是一个变量,就不可能为其分配一个新值(相反,在Python编程语言中,它明确的定义为对象本身,在运行期间可以不断改变)。
this
只是一个引用别名(referencing alias) - 这个别名只知道当前指向的那个对象, 而这也是最棘手的地方。this
是一个特殊的标识符关键字 —— 在每个 function 中自动根据作用域(scope) 确定, 指向的是此次调用的 “所有者,owner” (即 那个对象)this
是如何创建的?每调用一次 JavaScript 函数时,都会创建一个新的对象, 其中的信息包括: 传入了哪些参数, 函数是如何调用(invoked)的, 函数是在哪里被调用(called)的,等等。该对象中还有一个重要的属性是 this
引用, 函数是哪个对象的方法,this
就会自动绑定到该对象。
分析一下call的用法
- Step1: 把第二个到最后一个参数作为函数执行时要传入的参数
- Step2: 把函数执行时的this指向第一个参数
- Step3: 在上面这个特殊的上下文中执行函数
function say(word) { console.log(world); }
say("Hello world"); // 两者是
say.call(window, "Hello world"); // 等效的
每次看见functionName(xxx)的时候,你需要马上在脑海中把它替换为functionName.call(window,xxxx),
this
的作用域(scope) 与函数定义的位置没有关系, 而是取决于函数在哪里被调用( where they are called from ;i.e. the context)。
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
其实秘密花园里面总结的不错(与上面的四点是一一对应)
1.当在全部范围内使用 this,它将会指向全局对象。
2.函数调用时,这里 this 也会指向全局对象。
3.方法调用 test.foo(); 这个例子中,this 指向 test 对象。
4.调用构造函数 new foo(); 如果函数倾向于和 new 关键词一块使用,则我们称这个函数是构造函数。在函数内部,this 指向新创建的对象。
关于 this
的知识点
和其他机制一样, this
关键字也遵循一些简单的规则, 如果你了解了这些规则,那就可以用得顺畅一些。下面快速回顾这两节中所学的知识点:
- 在下列情况下
this
指向的是全局对象:
当函数作为父对象的属性被调用时,this
指向的是父对象(parent object)。- 在最外层的代码中, 不在任何 function 里面
- 不是对象方法(method)的函数(method)里面
- 不是构造函数(constructor)的函数里面
- 当函数通过
call()
、apply()
或者bind()
调用时,this
指向的是传递给这些方法的第一个参数。如果第一个参数是null
或者不是一个对象, 那么this
指向的是全局对象。 - 在使用
new
操作符来调用一个函数时,this
指向的是新创建的这个对象。 - 在 ECMAScript 6 中使用箭头函数时,
this
根据所处的语法作用域指向上级对象(parent object)。
了解了这些简单直接的规则,我们就可以很容易地看出 this
指向的是哪个对象, 如果指向不正确, 那可以用学过的这些黑科技来搞定。
new operator(new 操作符)
The new
operator creates an instance of a user-defined object type(?对象类型,还是理解为不同的数据结构?) or of one of the built-in object types that has a constructor function.
new 操作符生成一个实例,这个实例是用户自定义的“对象类型”或是内建的对象类型的实例。
Description:
Creating a user-defined object requires two steps:
- Define the object type by writing a function.通过一个函数(构造函数)来定义一种对象类型
- Create an instance of the object with
new
.用new操作符生成一个这种对象类型的实例
To define an object type, create a function for the object type that specifies its name and properties. An object can have a property that is itself another object.
new操作符的原理
When the code new Foo(...)
is executed, the following things happen:
- A new object is created, inheriting(一个新的对象生成,继承自构造函数的prototype属性对象) from
Foo.prototype
. - The constructor function
Foo
is called with the specified arguments, and withthis
bound to the newly created object(构造函数被调用,并且把this关键字绑定到新生成的对象上).new Foo
is equivalent tonew
Foo
()
, i.e. if no argument list is specified,Foo
is called without arguments. - The object returned by the constructor function becomes the result of the whole
new
expression. If the constructor function doesn't explicitly return an object(若构造函数没有显示的返回对象,则第一步生成的对象就会被返回), the object created in step 1 is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)
(高程三,创建对象,new操作符的作用)
test
JavaScript 构造函数 prototype属性和_proto_和原型链 constructor属性 apply(),call()和bind() 关键字this的更多相关文章
- Javascript之傻傻理不清的原型链、prototype、__proto__
新人学习Javascript,其中的原型链一直是云里雾里,不得要领,查了很多相关资料,觉得这遍讲得最为清晰易懂,特转载分享,共同学习. 1. JavaScript内置对象 所谓的内置对象 指的是:Ja ...
- 275 原型与原型链:显式原型prototype ,隐式原型__proto__,隐式原型链,原型链_属性问题,给原型对象添加属性/方法
1.所有函数都有一个特别的属性 prototype : 显式原型属性 [普通构造函数的实例对象没有prototype 属性,构造函数有__proto__属性,原型对象有__proto__属性 ] 2. ...
- 玩转JavaScript OOP[3]——彻底理解继承和原型链
概述 上一篇我们介绍了通过构造函数和原型可以实现JavaScript中的“类”,由于构造函数和函数的原型都是对象,所以JavaScript的“类”本质上也是对象.这一篇我们将介绍JavaScript中 ...
- javascript基础修炼(1)——一道十面埋伏的原型链面试题
在基础面前,一切技巧都是浮云. 题目是这样的 要求写出控制台的输出. function Parent() { this.a = 1; this.b = [1, 2, this.a]; this.c = ...
- [js高手之路]原型对象(prototype)与原型链相关属性与方法详解
一,instanceof: instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型. 我在之前的两篇文章 [js高手之路]构造函数的基本特性与优缺点 [js高手 ...
- javascript原型链__proto__属性的理解
在javascript中,按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头.一个方法使用new操作符创建,例如下面代码块中的Person1(可以吧Person1看做 ...
- javascript作用域、闭包、对象与原型链
原文作者总结得特别好,自己收藏一下.^-^ 1.作用域1.1函数作用域JS的在函数中定义的局部变量只对这个函数内部可见,称之谓函数作用域.它没有块级作用域(因此if.for等语句中的花括号不是独立作用 ...
- Javascript作业—数组去重(要求:原型链上添加函数)
数组去重(要求:原型链上添加函数) <script> //数组去重,要求:在原型链上添加函数 //存储不重复的--仅循环一次 if(!Array.prototype.unique1){ A ...
- 面向对象继承 (for in 原型链查找属性)
window.onload=function(){ new Preson('liujian','男').show(); new Work('liujian','男','工人').show(); new ...
随机推荐
- MVC网站后台分离
1.新建一个项目TestAdmin项目类型随便选一个,待会删掉,因为这里我只需要解决方案名称 [项目存放于F盘 F:\TestAdmin] 2.新建一个 ZGJ.Web 的前台MVC项目,将开 ...
- [Centos] mod_wsgi 安装流程以及遇到问题解决办法。apxs: command not found 或 Sorry, Python developer package does not appear to be installed.
前提: Centos 系统, apache 已安装, python 已安装. 1. 首先下载mod_wsgi-3.5.tar.gz 下载地址:https://code.google.com/p/mod ...
- Xcode 8 控制台输出大量不用的log的问题解决&&NSLog失效的解决
Product-->Scheme-->editeScheme中:Auguments中Environment Variable中 Scheme中添加环境变量 "OS_ACTIVIT ...
- python 学习 设计模式(goF设计模式)
一 单例模式 用来创建单个实例 #/usr/bin/env python3 # -*- coding:utf-8 -*- # Author: ZSHAOX class Foo: instance = ...
- vsftp 详解鸟哥版
FTP (File Transfer Protocol) 可说是最古老的协议之一了,主要是用来进行档案的传输,尤其是大型档案的传输使用 FTP 更是方便!不过,值得注意的是,使用 FTP 来传输时,其 ...
- sql 函数 DATEADD 使用
DATEADD ( datepart , number, date ) 在日期中添加或减去指定的时间间隔. datepart 是规定应向日期的哪一部分返回新值的参数. number 为要增加或减去 ...
- loadrunner破解方法--lm70.dll,mlr5lprg.dll下载地址
一.由于在压力测试执行中,出现一个-10803的错误 ,为解决这个错误,重新设置的环境变量,在次执行错误,这个问题解决了,但另外一个问题出来了,LR,打开脚本编辑器老提示找不到TEMP目录,当时没有想 ...
- Android: Failure [INSTALL_FAILED_DEXOPT] and Failure [INSTALL_FAILED_UID_CHANGED] 解决方案
1. 错误: Failure [INSTALL_FAILED_DEXOPT] Android安装App时 D:\WorkSpace\Administrator\workspace\svn\soot ...
- 淘淘商城_day01_课堂笔记
今日大纲 聊聊电商行业 电商行业发展 11.11 2015双11: 2016年: 预测:2017年的双11交易额将达到:1400亿 电商行业技术特点 淘淘商城简介 淘淘商城的前身 电商行业的概念 B2 ...
- 使用javassist运行时动态重新加载java类及其他替换选择
在不少的情况下,我们需要对生产中的系统进行问题排查,但是又不能重启应用,java应用不同于数据库的存储过程,至少到目前为止,还不能原生的支持随时进行编译替换,从这种角度来说,数据库比java的动态性要 ...