JavaScript 构造函数 prototype属性和_proto_和原型链 constructor属性 apply(),call()和bind() 关键字this new操作符
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
posted on 2016-04-10 17:00 kevin4dev 阅读(6800) 评论(1) 编辑 收藏 举报