JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。
它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
new命令:
new
命令的作用,就是执行构造函数,返回一个实例对象。
var Vehicle = function (p) { this.price = p; }; var v = new Vehicle(500);
new 命令的原理
使用new命令时,它后面的函数依次执行下面的步骤。 1.创建一个空对象,作为将要返回的对象实例。 2.将这个空对象的原型,指向构造函数的prototype属性。 3.将这个空对象赋值给函数内部的this关键字。 4.开始执行构造函数内部的代码。
也就是说,构造函数内部,this
指的是一个新生成的空对象,所有针对this
的操作,都会发生在这个空对象上。
构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this
对象),将其“构造”为需要的样子。
如果构造函数内部有return
语句,而且return
后面跟着一个对象,new
命令会返回return
语句指定的对象;否则,就会不管return
语句,返回this
对象。
var Vehicle = function () { this.price = 1000; return 1000; }; (new Vehicle()) === 1000 // false
上面代码中,构造函数Vehicle
的return
语句返回一个数值。这时,new
命令就会忽略这个return
语句,返回“构造”后的this
对象。
但是,如果return
语句返回的是一个跟this
无关的新对象,new
命令会返回这个新对象,而不是this
对象。这一点需要特别引起注意。
var Vehicle = function (){ this.price = 1000; return { price: 2000 }; }; (new Vehicle()).price // 2000
Object.create()以这个现有的对象作为模板,生成新的实例对象。
var person1 = { name: '张三', age: 38, greeting: function() { console.log('Hi! I\'m ' + this.name + '.'); } }; var person2 = Object.create(person1); person2.name // 张三 person2.greeting() // Hi! I'm 张三.
对象person1
是person2
的模板,后者继承了前者的属性和方法。
this关键字
this
可以用在构造函数之中,表示实例对象。除此之外,this
还可以用在别的场合。但不管是什么场合,this
都有一个共同点:它总是返回一个对象。
this
就是属性或方法“当前”所在的对象。
this.propertythis
就代表property
属性当前所在的对象。
只要函数被赋给另一个变量,this
的指向就会变。
var A = { name: '张三', describe: function () { return '姓名:'+ this.name; } }; var name = '李四'; var f = A.describe; f() // "姓名:李四"
内部的this
就会指向f
运行时所在的对象(本例是顶层对象)。
this
JavaScript 语言之所以有 this 的设计,目的就是在函数体内部,指代函数当前的运行环境。
this
主要有以下几个使用场合。
1、全局环境使用this
,它指的就是顶层对象window
。
this === window // true function f() { console.log(this === window); } f() // true 上面代码说明,不管是不是在函数内部,只要是在全局环境下运行,this就是指顶层对象window。
2、构造函数中的this
,指的是实例对象。
var Obj = function (p) { this.p = p; }; var o = new Obj('Hello World!'); o.p // "Hello World!" 上面代码定义了一个构造函数Obj。由于this指向实例对象,所以在构造函数内部定义this.p,就相当于定义实例对象有一个p属性。
3、对象的方法里面包含this
,this
的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this
的指向。
var obj ={ foo: function () { console.log(this); } }; obj.foo() // objobj.foo
方法执行时,它内部的this
指向obj
。
下面这几种用法,都会改变this
的指向。
// 情况一 (obj.foo = obj.foo)() // window // 情况二 (false || obj.foo)() // window // 情况三 (1, obj.foo)() // window 上面代码中,obj.foo就是一个值。这个值真正调用的时候,运行环境已经不是obj了,而是全局环境,所以this不再指向obj。 可以这样理解,JavaScript 引擎内部,obj和obj.foo储存在两个内存地址,称为地址一和地址二。obj.foo()这样调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj。
但是,上面三种情况,都是直接取出地址二进行调用,这样的话,运行环境就是全局环境,因此this指向全局环境。上面三种情况等同于下面的代码。 // 情况一 (obj.foo = function () { console.log(this); })() // 等同于 (function () { console.log(this); })() // 情况二 (false || function () { console.log(this); })() // 情况三 (1, function () { console.log(this); })()
如果this
所在的方法不在对象的第一层,这时this
只是指向当前一层的对象,而不会继承更上面的层。
var a = { p: 'Hello', b: { m: function() { console.log(this.p); } } }; a.b.m() // undefined a.b.m方法在a对象的第二层,该方法内部的this不是指向a,而是指向a.b
如果这时将嵌套对象内部的方法赋值给一个变量,this
依然会指向全局对象。
var a = { b: { m: function() { console.log(this.p); }, p: 'Hello' } }; var hello = a.b.m; hello() // undefined
数组的map
和foreach
方法,允许提供一个函数作为参数。这个函数内部不应该使用this
。
var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this.v + ' ' + item); }); } } o.f()
foreach
方法的回调函数中的this
,其实是指向window
对象,因此取不到o.v
的值。原因跟上一段的多层this
是一样的,就是内层的this
不指向外部,而指向顶层对象。
解决这个问题的一种方法,就是前面提到的,使用中间变量固定this
。
var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { var that = this; this.p.forEach(function (item) { console.log(that.v+' '+item); }); } } o.f() // hello a1 // hello a2
另一种方法是将this
当作foreach
方法的第二个参数,固定它的运行环境。
var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this.v + ' ' + item); }, this); } } o.f() // hello a1 // hello a2
绑定this的方法:
JavaScript 提供了call
、apply
、bind
这三个方法,来切换/固定this
的指向。
1、call方法是改变this指向;也就是将外面的对象的this指向调用的()里面。
var n = 123; var obj = { n: 456 }; function a() { console.log(this.n); } a.call() // 123 ---- 指向全局对象 a.call(null) // 123 ---- 参数为null
或undefined
,则等同于指向全局对象。 a.call(undefined) // 123 a.call(window) // 123 a.call(obj) // 456 ---- 指向obj
call
方法还可以接受多个参数。
call
的第一个参数就是this
所要指向的那个对象,后面的参数则是函数调用时所需的参数。
func.call(thisValue, arg1, arg2, ...)
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
2、apply
方法的作用与call
方法类似,也是改变this
指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,
func.apply(thisValue, [arg1, arg2, ...])
使用apply
方法和Math.max
方法,就可以返回数组的最大元素。
var a = [10, 2, 4, 15, 9]; Math.max.apply(null, a) // 15
利用数组对象的slice
方法,可以将一个类似数组的对象(比如arguments
对象)转为真正的数组
Array.prototype.slice.apply({0: 1, length: 1}) // [1] Array.prototype.slice.apply({0: 1}) // [] Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined] Array.prototype.slice.apply({length: 1}) // [undefined]
3、bind()
方法用于将函数体内的this
绑定到某个对象,然后返回一个新函数。
bind
方法的参数就是所要绑定this
的对象。
var counter = { count: 0, inc: function () { this.count++; } }; var obj = { count: 100 }; var func = counter.inc.bind(obj); // 方法指向obj,this.count++ 为101 func(); obj.count // 101
bind()
方法将inc()
方法内部的this
,绑定到obj
对象。结果调用func
函数以后,递增的就是obj
内部的count
属性。
bind()
还可以接受更多的参数,将这些参数绑定原函数的参数。
var add = function (x, y) { return x * this.m + y * this.n; } var obj = { m: 2, n: 2 }; var newAdd = add.bind(obj, 5); ---- 绑定对象是obj,然后 x 默认值为5,此时在传入 y 就可以了。 newAdd(5) // 20
原型对象:原型对象的作用,就是定义所有实例对象共享的属性和方法。
原型链:对象到原型,再到原型的原型。
所有对象都有自己的原型对象;任何一个对象,都可以充当其他对象的原型。由于原型对象也是对象,所以它也有自己的原型。
Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
什么叫做'覆盖'? 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”
如果让构造函数的prototype
属性指向一个数组,就意味着实例对象可以调用数组方法。
var MyArray = function () {}; MyArray.prototype = new Array(); MyArray.prototype.constructor = MyArray; var mine = new MyArray(); mine.push(1, 2, 3); mine.length // 3 mine instanceof Array // true
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle(); v instanceof Vehicle // true
instanceof
运算符只能用于对象,不适用原始类型的值。
var s = 'hello'; s instanceof String // false
//
此外,对于undefined
和null
,instanceof
运算符总是返回false
undefined instanceof Object // false
null instanceof Object // false
利用instanceof
运算符,还可以巧妙地解决,调用构造函数时,忘了加new
命令的问题。
function Fubar (foo, bar) {
if (this instanceof Fubar) { // 判断this是否是Fubar的实例,如果不是,就表明忘了加new
命令。
this._foo = foo;
this._bar = bar;
} else {
return new Fubar(foo, bar);
}
}
子类继承父类:Shape()父类构造函数,Rectangle()子类
// 父类构造函数 function Shape() { this.x = 0; this.y = 0; } Shape.prototype.move = function (x, y) { this.x += x; this.y += y; console.info('Shape moved.'); };
我们需要让Rectangle
构造函数继承Shape
。
// 第一步,子类继承父类的实例 function Rectangle() { Shape.call(this); // 调用父类构造函数 } // 第二步,子类继承父类的原型 Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle;
Rectangle是子类的构造函数,this
是子类的实例。在实例上调用父类的构造函数Shape,就会让子类实例具有父类实例的属性。
这句话的理解:https://segmentfault.com/q/1010000005721719
Shape.call(this); 这时的 this 指向Rectangle本身。 Shape里面的this 指向 Rectangle 里面的this。
Object 对象的相关方法:
1、Object.getPrototypeOf
方法返回参数对象的原型。
var F = function () {}; var f = new F(); Object.getPrototypeOf(f) === F.prototype // true
2、Object.setPrototypeOf
方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。
var a = {}; var b = {x: 1}; Object.setPrototypeOf(a, b); Object.getPrototypeOf(a) === b // true a.x // 1
// 对象a
的原型,设置为对象b
,因此a
可以共享b
的属性。
3、Object.create()
方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
// 原型对象 var A = { print: function () { console.log('hello'); } }; // 实例对象 var B = Object.create(A); Object.getPrototypeOf(B) === A // true B.print() // hello B.print === A.print // true
//Object.create()
方法以A
对象为原型,生成了B
对象。B
继承了A
的所有属性和方法。
4、实例对象的__proto__
属性(前后各两个下划线),返回该对象的原型。该属性可读写。
var obj = {}; var p = {}; obj.__proto__ = p; Object.getPrototypeOf(obj) === p // true
// 上面代码通过__proto__
属性,将p
对象设为obj
对象的原型。
5、Object.getOwnPropertyNames
方法返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名。
方法返回所有键名,不管是否可以遍历。只获取那些可以遍历的属性,使用Object.keys
方法。
6、对象实例的hasOwnProperty
方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。
hasOwnProperty
方法是 JavaScript 之中唯一一个处理对象属性时,不会遍历原型链的方法。
7、in运算符和for...in循环
in
运算符返回一个布尔值,表示一个对象是否具有某个属性。in
运算符常用于检查一个属性是否存在。
获得对象的所有可遍历属性(不管是自身的还是继承的),可以使用for...in
循环。