高级技巧(1)——作用域安全的构造函数和函数绑定

高级技巧(1)——作用域安全的构造函数和函数绑定

作用域安全的构造函数

构造函数其实就是一个使用new操作符调用的函数

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer");

例子解析:该this对象是在运行时绑定的,所以直接调用Person(),this会映射到window对象,例如下面这个测试例子:(没有new将它看成了普通函数

var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"//
alert(window.age); //29
alert(window.job); //"Software Engineer"

从这个例子可以看到Person实例的三个属性被加到了window上

  • 问题原因: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"
  • 理解:使用一个if语句,检查并确保this是person的实例,由此给出限制:要么new一个,要么在现有的person实例环境中调用构造函数,所以最后结果是调用这个构造函数的时候无论是否使用了new操作符,都会返回一个pperson的新实例,避免了给全局对象设置属性
构造函数窃取模式的继承(”子类“的构造函数中调用父类的构造函数以实现继承父类属性)
  • 如果使用构造函数模式继承且不使用原型链,那么这个继承很可能被破坏
  • 例子
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 构造函数则不是,因此这个this对象并不是Polygon的实例,所以会创建并返回一个新的Polygon对象,本来call方法会增长作用域链,但是此时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();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2
  • 复习
    • 1、call()和apply()

    • 高级技巧(1)——作用域安全的构造函数和函数绑定_第1张图片

    • 2、prototype:参考https://www.cnblogs.com/wulihong/p/8906231.html

函数绑定(常常与回调函数和事件处理程序一起使用)

  • 例子
var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick);
  • 结果:是undefined而不是 Event handled
  • 问题原因: 没 有 保 存
    handler.handleClick() 的环境,所以 this 对象最后是指向了 DOM按钮而非 handler
  • 解决(用闭包):
var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", function(event){
handler.handleClick(event);
});

之前在讲闭包的博客中提到过创建多个闭包可能会令代码变得难于理解和调试

  • 解决bind()函数
    • 语法:(接收一个函数和一个环境)
function bind(fn, context){
return function(){
return fn.apply(context, arguments);
};
}
  • 使用方式:(在 bind() 中创建了一个闭包,闭包使用 apply() 调用传入的函数,并给 apply() 传递 context 对象和参数)
var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

记得这里使用的 arguments 对象是内部函数的,而非 bind() 的

  • 适用情况:某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行
  • 注意点:被绑定函数与普通函数相比有更多的开销,它们需要更多内存并且运行时间也会更加慢一些
  • 复习arguments:是一个对象,是一个长的很像数组的对象,包含函数运行时的实参列表,有个callee属性中装了当前正在运行的函数

你可能感兴趣的:(js)