基础部分
构造函数本质上就是一个使用 new 操作符调用的函数, 使用 new 调用时, 构造函数内用到的 this 对象会指向新创建的对象实例:
- function Girlfriend(name, age, height) {
- this.name = name;
- this.age = age;
- this.height = height;
- }
- // 使用 new 操作符来分配这些属性
- var girlfriend = new Girlfriend("Ying", 23, 170);
平时写变量时, 如果因为失误忘记使用 new 操作符时, 会造成一个糟糕的影响 ---- 因为 this 对象是在运行时绑定的, 直接调用构造函数, this 会映射到全局对象 Windows 上, 导致错误对象属性的增加, 增添不必要的变量到全局对象中:
- var girlfriend = new Girlfriend("Ying", 23, 170);
- console.log(Windows.name); // "Ying"
- console.log(Windows.age); // 23
- console.log(Windows.height); // 170
特别的, 当你自己构造函数内的某些变量名与 Windows 变量名重名时 (像一些常用变量名 name,length 等), 对这些属性的偶然覆盖就很可能导致其他地方出错, 并且这个 bug 还相当难找!
在这种情况下构造一个作用域安全的构造函数就显得很有必要:
- function Girlfriend(name, age, height) {
- // 首先确认 this 对象是正确类型的实例,
- // 如果不是就创建新的实例并返回
- if (this instanceof Girlfriend) { // 添加一个检查
- console.log('created');
- this.name = name;
- this.age = age;
- this.height = height;
- } else {
- console.log('new');
- return new Girfriend(name, age, height);
- }
- }
- var girlfriend1 = Girlfriend("Ying", 23, 170); // "new" "created"
- console.log(Windows.name); // ""console.log(girfriend1.name); //"Ying"var girlfriend2 = new Girlfriend("Lin", 22, 165); //"created"console.log(girfriend1.name); //"Lin"girlfriend1 背后构造函数先 new 了一个实例并返回实例 (打印"new"), 再对实例进行赋值 (打印"created").
girlfriend2 自己就先 new 了一个实例, 直接对该实例进行赋值 (只打印 "created").
这样在任何情况下就都可以返回一个安全作用域的实例了.
进阶部分
使用上面添加一个检查的方法可以创建一个作用域安全的构造函数, 但如果有的函数窃取该函数的继承且没有使用原型链, 那这个继承将被破坏不生效:
- function Bmi(sex, weight=1, height=1) { // ES6 开始支持的默认值
- if (this instanceof Bmi) {
- this.sex = sex;
- this.weight = weight;
- this.height = height;
- this.getBmi = function() {
- return this.weight / (this.height ** 2);
- };
- } else {
- return new Bmi(sex);
- }
- }
- function People(height, weight) {
- Bmi.call(this, 'male');
- this.height = height;
- this.weight = weight;
- }
- var guy = new People(1.75, 68); // 单位是 m 和 kg
- console.log(guy.sex) // undefined
Bmi 构造函数作用域是安全的, 但 People 并不是. 新创建一个 People 实例后, 这个实例准备通过 Bmi.call() 来继承 Bmi 的 sex 属性, 但由于 Bmi 的作用域是安全的, this 对象并非是 Bmi 的实例, 所以 Bmi 会先自己创建一个新的 Bmi 对象, 不会把新的 Bmi 对象的值传递到 People 中去.
这样 People 中的 this 对象并没有得到增长, 同时 Bmi.call() 返回的值也没有用到, 所以 People 实例中就不会有 sex,weight,height 属性和 getBmi() 函数.
解决办法: 构造函数结合使用原型链或寄生组合:
- function Bmi(sex, weight=1, height=1) {
- if (this instanceof Bmi) {
- this.sex = sex;
- this.weight = weight;
- this.height = height;
- this.getBmi = function() {
- return this.weight / (this.height ** 2);
- };
- } else {
- return new Bmi(sex);
- }
- }
- function People(height, weight) {
- Bmi.call(this, 'male');
- this.height = height;
- this.weight = weight;
- }
- People.prototype = new Bmi(); // 重点
- var guy = new People(1.75, 68);
- console.log(guy.sex) // "male"
这样写的话, 一个 People 的实例同时也是一个 Bmi 的实例, 所以 Bmi.call() 才会去执行, 为 People 实例添加上属性和函数.
总结
当多个人一同构建一个项目时, 作用域构安全函数就非常必要, 对全局对象意外的更改可能就会导致一些常常难以追踪的错误, 这和平常设置空变量和空函数一样避免因为其他人可能发生的错误而阻塞程序执行.
来源: https://www.cnblogs.com/hencins/p/10042752.html