JS笔记《构造函数与new》

66 阅读3分钟

构造函数

  • 一种特殊的函数,主要用来初始化对象,为对象成员赋初始值。总与new一起使用,把对象中一些公共的属性方法抽取出来,封装到函数中。
  • 构造函数有两个特点:
    • 函数体内使用this关键字,代表了所要生成的对象实例。
    • 生成对象时必须使用new命令。
function Person(opt) {          // 构造函数名大驼峰式命名  
    this.uname = opt.uname;     // 实例成员,只能通过实例对象访问。  
    this.age = opt.age;         // 实例成员  
}  
Person.sex = '男';              // 静态成员,只能通过构造函数访问  
  
var person = new Person({uname:'张三'age18});  // 创建 person对象  
var person1 = new Person({uname:'李四'age20}); // 创建 person1对象,与 person彼此独立 
person    // Person {uname: "张三", age: 18}          
person1   // Person {uname: "李四", age: 20}
  • 如果忘记使用了new,直接调用函数的话,构造函数就变成了普通函数。但this会指向全局,相当于给window添加属性
function Person(opt) {          
    this.uname = opt.uname;     
    this.age = opt.age;         
}  
var p = Person({uname:'张三', age: 20});
p // undefined

window.uname  // '张三'
window.age    // 20
  • 构造函数使用严格模式可以避免忘记使用new命令的情况发生,因为函数内部的this不能指向全局对象。
// 方法一:严格模式
function Person(opt) {
  'use strict';
  this.uname = opt.uname;  // this === undefined
  this.age = opt.age;      // this === undefined
}
var p = Person({ uname: '张三', age: 20 });  
// Uncaught TypeError: Cannot set properties of undefined (setting 'uname')


// 方法二:构造函数内判断是否使用new
function Person(opt) {
  if(!(this instanceof Person)){ // 如果使用new命令,则this会指向Person实例
    return new Person(opt);
  } 
  this.uname = opt.uname; 
  this.age = opt.age; 
}

var person = Person({ uname: '张三', age: 18 }); 
person  // Person {uname: '张三', age: 18}

new 命令原理

  • new命令步骤原理:

    • 创建一个空对象,作为将要返回的对象实例。
    • 将这个空对象的原型指向构造函数的prototype属性。
    • 将这个空对象赋值给函数内部的this
    • 开始执行构造函数内部代码。
    • 如果构造函数有return且跟着的是对象,则返回这个对象。否则无论return跟着的是什么,都返回this
  • new命令总会返回一个对象,要么是实例对象,要么是return跟着的对象

function fn(){
  // console.log(this);    // fn{} this指向实例对象
  return 'Hellow World!'
}

console.log(new fn())  // fn{} 返回的是fn的实例对象
// 因为return返回的是字符串,不是对象。所以返回this

模拟 new 实现

/**
 * 模拟new关键字
 * constructor 构造函数
 */
 function _new(constructor) {
    // 创建一个空对象,将此对象的原型指向构造函数的prototype属性
    var obj = Object.create(constructor.prototype);
    // 获取参数
    var args = Array.prototype.slice.call(arguments, 1);
    // 将构造函数的this赋值给obj
    const res = constructor.apply(obj, args);
    // 如果构造函数返回的是对象则返回该对象,否则返回 obj
    return (typeof(res) === 'object' && res !== null) ? res : obj;
}


function Person(option) {
  this.name = option.name;
  this.age = option.age;
}


var p = _new(Person, {name: '张三', age: 20});
var p2 = _new(Person, {name: '李四', age: 18})
console.log(p)   // Person {name: '张三', age: 20}
console.log(p2)  // Person {name: '李四', age: 18}
console.log(p instanceof Person) // true

new.target

  • 函数内部使用此属性。如果当前函数是通过new调用的,new.target指向当前当前函数,否则为undefined
function f() {
  console.log(new.target === f);
}

f()     // false
new f() // true
  • 使用此方法可以判断d当前函数是否是通过new调用的。
function f() {
  if (!new.target) {
    throw new Error('请使用 new 命令调用!');
  }
  // ...
}

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

Ojbect.create()

  • 以一个现有对象作为模板,生成新的实例对象。
var person = {
  name: '张三',
  age: 20,
  say: function(){
    console.log('My name is ' + this.name);
  }
}
person.say()

var person2 = Object.create(person);
console.log(person2.name)  // '张三'
person2.say()  // 'My name is 张三'