《web前端笔记11》js—面向对象构造函数return、new本质原理总结

  通常,构造器没有 return 语句。它们的任务是将所有必要的东西写入 this,并自动转换为结果。但是,如果这有一个 return 语句,那么规则就简单了:

  如果 return 返回的是一个对象,则返回这个对象,而不是 this。如果 return 返回的是一个原始类型,则忽略。

  换句话说,带有对象的 return 返回该对象,在所有其他情况下返回 this。

  例如,这里 return 通过返回一个对象覆盖 this:

  function BigUser() {

  this.name="John";

  return { name: "Godzilla" }; // <-- 返回这个对象

  }

  alert( new BigUser().name ); // Godzilla,

  这里有一个 return 为空的例子

  (或者我们可以在它之后放置一个原始类型,没有什么影响):

  function SmallUser() {

  this.name="John";

  return; // <-- 返回 this

  }

  alert( new SmallUser().name ); // John

  通常构造器没有 return 语句。这里我们主要为了完整性而提及返回对象的特殊行为。

  顺便说一下,如果没有参数,我们可以省略 new 后的括号:

  let user=new User; // <-- 没有参数

  // 等同于 let user=new User();

  这里省略括号不被认为是一种“好风格”,但是规范允许使用该语法。

  如果构造函数内部有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

  上面代码中,构造函数Vehicle的return语句,返回的是一个新对象。new命令会返回这个对象,而不是this对象。

  另一方面,如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。

  function getMessage() {

  return 'this is a message';

  }

  var msg=new getMessage();

  msg // {} typeof msg // "object"

  上面代码中,getMessage是一个普通函数,返回一个字符串。对它使用new命令,会得到一个空对象。

  这是因为new命令总是返回一个对象,要么是实例对象,要么是return语句指定的对象。本例中,return语句返回的是字符串,所以new命令就忽略了该语句。

  使用构造函数来创建对象会带来很大的灵活性。构造函数可能有一些参数,这些参数定义了如何构造对象以及要放入什么。

  当然,我们不仅可以将属性添加到 this 中,还可以添加方法。

  例如,下面的 new User(name) 用给定的 name 和方法 sayHi 创建了一个对象:

  function User(name) {

  this.name=name;

  this.sayHi=function() {

  alert( "My name is: " + this.name

  );

  };

  }

  let john=new User("John");

  john.sayHi(); // My name is: John /* john={ name: "John", sayHi: function() { ... } } */

  案例1:是否可以创建像 new A()==new B() 这样的函数 A 和 B?

  function A() { ... }

  function B() { ... }

  let a=new A;

  let b=new B;

  alert( a==b ); // true

  答案:

  是的,这是可以的。

  如果一个函数返回一个对象,那么 new 返回那个对象而不是 this。

  所以它们可以,例如,返回相同的外部定义的对象 obj:

  let obj={};

  function A() {

  return obj;

  }

  function B() {

  return obj;

  }

  alert( new A()==new B() ); // true

  new 操作符执行时,它按照以下步骤:

  一个新的空对象被创建并分配给 this。函数体执行。通常它会修改 this,为其添加新的属性。返回 this 的值。

  function User(name) {

  // this={};(隐式创建)

  // 添加属性到this

  this.name=name;

  this.isAdmin=false;

  // return this;(隐式返回) }

  如果我们想创建其他用户,我们可以调用 new User("Ann"),new User("Alice") 等。

  比每次都使用字面量创建要短得多,而且更易于阅读

  这是构造器的主要目的 — 实现可重用的对象创建代码。

  new命令的作用就是执行构造函数,返回一个对象实例。

  var Vehicle=function () {

  this.price=1000;

  };

  var v=new Vehicle();

  v.price // 1000

  上面代码通过new命令,让构造函数Vehicle生成一个实例对象,保存在变量v中。

  这个新生成的实例对象,从构造函数Vehicle得到了price属性。

  new命令执行时,构造函数内部的this,就代表了新生成的实例对象,

  this.price表示实例对象有一个price属性,值是1000。

  一个很自然的问题是,如果忘了使用new命令,直接调用构造函数会发生什么事?

  这种情况下,构造函数就变成了普通函数,并不会生成实例对象。

  而且由于后面会说到的原因,this这时代表全局对象,将造成一些意想不到的结果。

  var Vehicle=function (){

  this.price=1000;

  };

  var v=Vehicle();

  v // undefined

  price // 1000

  上面代码中,调用Vehicle构造函数时,忘了加上new命令。结果,变量v变成了undefined,

  而price属性变成了全局变量。

  因此,应该非常小心,避免不使用new命令、直接调用构造函数。

  为了保证构造函数必须与new命令一起使用,

  一个解决办法是,构造函数内部使用严格模式,即第一行加上use strict。

  这样的话,一旦忘了使用new命令,直接调用构造函数就会报错。

  function Fubar(foo, bar){

  'use strict';

  this._foo=foo;

  this._bar=bar;

  }

  Fubar() // TypeError: Cannot set property '_foo' of undefined

  上面代码的Fubar为构造函数,use strict命令保证了该函数在严格模式下运行。

  由于严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错(JavaScript 不允许对undefined添加属性)。

  另一个解决办法,构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

  function Fubar(foo, bar) {

  if (!(this instanceof Fubar)) {

  return new Fubar(foo, bar);

  }

  this._foo=foo;

  this._bar=bar;

  }

  Fubar(1, 2)._foo // 1

  (new Fubar(1, 2))._foo // 1

  上面代码中的构造函数,不管加不加new命令,都会得到同样的结果。

  使用new命令时,它后面的函数依次执行下面的步骤。

  创建一个空对象,作为将要返回的对象实例。将这个空对象的原型,指向构造函数的prototype属性。将这个空对象赋值给函数内部的this关键字。开始执行构造函数内部的代码。

  也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,

  都会发生在这个空对象上.

  构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。

  new命令简化的内部流程,可以用下面的代码表示。

  function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {

  // 将 arguments 对象转为数组

  var args=[].slice.call(arguments);

  // 取出构造函数

  var constructor=args.shift();

  // 创建一个空对象,继承构造函数的 prototype 属性

  var context=Object.create(constructor.prototype);

  // 执行构造函数

  var result=constructor.apply(context, args);

  // 如果返回结果是对象,就直接返回,否则返回 context 对象

  return (typeof result==='object' && result !=null) ? result : context; }

  // 实例 var actor=_new(Person, '张三', 28);

  在一个函数内部,我们可以使用new.target 属性来检查它是否被使用 new 进行调用了。

  对于常规调用,它为空,对于使用 new 的调用,则等于该函数:

  function User() {

  alert(new.target);

  }

  // 不带 "new": User();

  // undefined

  // 带 "new": new User();

  // function User { ... }

  它可以被用在函数内部,来判断该函数是被通过 new 调用的“构造器模式”,

  还是没被通过 new 调用的是“常规模式”。

  我们也可以让 new 调用和常规调用做相同的工作,像这样:

  function User(name) {

  if (!new.target) {

  // 如果你没有通过 new 运行我

  return new User(name);

  // ……我会给你添加 new

  }

  this.name=name;

  }

  let john=User("John"); // 将调用重定向到新用户

  alert(john.name); // John

  这种方法有时被用在库中以使语法更加灵活。这样人们在调用函数时,无论是否使用了 new,程序都能工作。

  不过,到处都使用它并不是一件好事,因为省略了 new 使得很难观察到代码中正在发生什么。而通过 new 我们都可以知道这创建了一个新对象。

  函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。

  function f() {

  console.log(new.target===f);

  }

  f() // false

  new f() // true

  使用这个属性,可以判断函数调用的时候,是否使用new命令。

  function f() {

  if (!new.target) {

  throw new Error('请使用 new 命令调用!');

  }

  // ...

  }

  f() // Uncaught Error: 请使用 new 命令调用!

  上面代码中,构造函数f调用时,没有使用new命令,就抛出一个错误。

  9:Object.create() 创建实例对象

  构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。

  我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用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的模板,后者继承了前者的属性和方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值