JavaScript 性能优化之作用域安全的构造函数

构造函数其实就是一个使用new操作符调用的函数。当使用new调用时,构造函数内用到的this对象会指向新创建的对象实例,如下面的例子所示:

            function Person(name, age, job) {
                this.name = name;
                this.age = age;
                this.job = job;
            }
            var person = new Person("aqingya", 18, "吴志广");

上面这个例子中,Person构造函数使用this对象给三个属性赋值: name、age和 job。当和new操作符连用时,则会创建一个新的 Person对象,同时会给它 分配这些属性。问题出在当没有使用new操作符来调用该构造函数的情况上。由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window.上,导致错误对象属性的意外增加。例如:

            var person = Person("aqingya", 18, "吴志广");
            alert(window.name); //"aqingya"
            alert(window.age); //18
            alert(window.job); //吴志广

原本针对Person实例的三个属性被加到window对象上,因为构造函数是作为普通函数调用的,忽略了new操作符。这个问题是由this 对象的晚绑定造成的,在这里this被解析成了window对象。

解决方法:就是创建一个作用域安全的构造函数。在进行更改之前,首先确认this对象的正确类型的实例,如果不是就会创建新的实例并返回。

           function Person(name, age, job) {
                if (this instanceof Person) {
                    this.name = name;
                    this.age = age;
                    this.job = job;
                } else {
                    return new Person(name, age, job);
                }
            }
            var person1 = Person("Nicholas", 29, "Software Engineer");
            alert(window.name); //""
            alert(person1.name); //"Nicholas"
            var person2 = new Person("Shelby", 34, "Ergonomist");
            alert(person2.name); //"Shelby"

Person构造函数添加了一个检查并确保this对象是Person实例的 if 语句,它表示要么使用new操作符,要么在现有的Person实例环境中调用构造函数。任何一种情况下,对象初始化都能正常进行。如果this并非Person的实例,那么会再次使用new操作符调用构造函数并返回结果。


但是这也会出现一个问题:实现这个模式后,你就锁定了可以调用构造函数的环境。如果你使用构造函数窃取模式的继承且不使用原型链,那么这个继承很可能被破坏。如下:

            function Polygon(sides) {
                if (this instanceof Polygon) {
                    this.sides = sides;
                    this.getArea = function() {
                        return 0;
                    };
                } else {
                    return new Polygon(sides);
                }
            }

            function Rectangle(width, height) {
                Polygon.call(this, 2);
                this.width = width;
                this.height = height;
                this.getArea = function() {
                    return this.width * this.height;
                };
            }
            var rect = new Rectangle(5, 10);
            alert(rect.sides); //undefined

在这段代码中,Polygon 构造函数是作用域安全的,然而Rectangle构造函数则不是。新创建一个Rectangle实例之后,这个实例应该通过Polygon. call ()来继承Polygon的sides属性。但是,由于Polygon构造函数是作用域安全的,this 对象并非Polygon的实例,所以会创建并返回一个新的Polygon对象。Rectangle 构造函数中的this对象并没有得到增长,同时Polygon. call ()返回的值也没有用到,所以Rectangle实例中就不会有sides属性。

解决方法:构造函数窃取结合使用原型链的方法可以解决该问题。

            function Polygon(sides) {
                if (this instanceof Polygon) {
                    this.sides = sides;
                    this.getArea = function() {
                        return 0;
                    };
                } else {
                    return new Polygon(sides);
                }
            }

            function Rectangle(width, height) {
                Polygon.call(this, 2);
                this.width = width;
                this.height = height;
                this.getArea = function() {
                    return this.width * this.height;
                };
            }
            
            Rectangle.prototype = new Polygon();
            //让Rectangle的原型成为 Polygon的实例。Rectangle的实例同时也是Polygon的实例。
            //所以 Polygon.call()会按照原意执行。为Rectangle添加sides属性
            
            var rect = new Rectangle(5, 10);
            alert(rect.sides); //2


愿你的坚持终有收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值