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

上面代码中,构造函数Vehiclereturn语句返回一个数值。这时,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 张三.

对象person1person2的模板,后者继承了前者的属性和方法。

 

this关键字

this可以用在构造函数之中,表示实例对象。除此之外,this还可以用在别的场合。但不管是什么场合,this都有一个共同点:它总是返回一个对象。

this就是属性或方法“当前”所在的对象。

this.property

this就代表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、对象的方法里面包含thisthis的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。

var obj ={
  foo: function () {
    console.log(this);
  }
};

obj.foo() // obj

obj.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

数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用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 提供了callapplybind这三个方法,来切换/固定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    ---- 参数为nullundefined,则等同于指向全局对象。
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的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是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
// 此外,对于undefinednullinstanceof运算符总是返回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循环。 

  

posted on 2021-03-11 15:49  liumcb  阅读(145)  评论(0编辑  收藏  举报