JavaScript之安全作用域的构造函数(高级函数)

来源:互联网 发布:广州980数控编程视频 编辑:程序博客网 时间:2024/06/06 06:43

函数是JavaScript最有趣的部分之一,它可以是最简单的函数,也可以是最复杂的函数。一些额外的功能还可以通过闭包来实现。由于函数也是对象,所以通过函数指针来操作函数是最非常简单的,以下是几种函数使用的高级技巧。


安全的类型检测

JavaScript内置的类型检测机制并非完全可靠。如:intsanceof操作符在存在多个全局作用域的情况下,也是有问题的。
var arr = ["1", "2", "hello"];console.log(arr instanceof Array); //true
以上代码要想返回true,value必须是一个数组,并且与Array构造函数在同一个全局作用域下。如果value是在另一个frame中定义的,那么以上代码会返回false。

在检测某个对象到底是原生对象还是开发人员自定义对象时也有问题。原因是因为浏览器也可以支持JSON对象了,JSON库定义了一个全局对象,于是开发人员很难确定JSON对象是不是原生对象。

解决上述问题的方法就是:我们知道任何值调用Object原生的toStiring()方法(即:Object.ptorotype.toString())都会返回一个[object xxx]的字符串,用以确定该对象属于哪一种原生构造函数,返回原生构造函数名。变量对象可以使用Object的原生方法toString来检测。用call()方法。
如:
      var arr = ["red", "hello", 1, 2, true];       console.log(Object.prototype.toString.call(arr)); //[object Array]
用这个方法检测arr,返回一个"[object Array]",说明该变量属于原生数组对象。

由于原生构造函数名与全局作用域无关,因此可以使用toSting()方法保证返回一致的值。

利用这一点,可以创建以下函数用于检测一个对象是否是原生数组对象
 //该函数用于检测某变量是否是原生数组对象 function isArray (value) {     return (Object.prototype.toString.call(value)); }  var arr = ["red", "hello", 1, 2, true];  console.log(isArray(arr)); //[object Array]

同理,也可以创建一个函数用于检测某函数是原生对象还是自定义对象

//该函数用于检测某变量是否是原生函数function isFunction (value) {    return (Object.prototype.toString.call(value));}var func = function () {alert("hi");};console.log(isFunction(func)); //[object Function]

也可以创建一个函数用于检测某正则表达式是否是原生正则表达式。
 //该函数用于检测某正则表达式是否是原生正则表达式 function isRegExp (value) {     return (Object.prototype.toString.call(value)); }  var parent = /.a/gi; console.log(isRegExp(parent)); //[object RegExp]


以上技巧也应用于检测JSON对象。Object的原生的toString()方法不能 检测非原生构造函数的构造函数名,也就是说开发人员自定义的对象属于非原生对象,该方法返回"[object Object]"。

如:
//该函数用于检测某对象是否是原生对象function isObject (value) {    return (Object.prototype.toString.call(value));}var obj = {naem: "tom", age: 21}console.log(isObject(obj)); //[object Object] 非原生对象

检测JSON对象:
 //该函数用于检测某对象是否是原生对象 function isObject (value) {     return (Object.prototype.toString.call(value)); }  var isJSON = window.JSON; var json = Object.prototype.toString.call(JSON); console.log(isObject(isJSON)); //[object JSON]  console.log(isObject(json)); //[object String]

在WEB开发中区分原生对象与非原生对象是非常重要的。


作用域安全的构造函数

我们再次回故前面所讲的使用new关键字和构造函数名来创建一个新的实例对象的过程:

function Person (name, age, job) {    this.name = name;    this.age = age;    this.job = job;        this.sayName = function () {        console.log(this.name);    };}//使用new实例化一个对象var person1 = new Person("Tom", 21, "WEB前端");

分离构造器:
使用new关键字创建对象要经历四个步骤:

1、首先是创建一个对象:var person = {};

2、其次将构造函数的作用域赋值给新的对象(this对象就指向了这个新对象)那么this也就指向了这个构造函数(这是某实例对象是否为这个构造函数的实例的关键点)

3、执行构造函数中的代码。Person中的代码

4、返回这个新对象。

最后一步就说明了,我们只要返回这个新对象即可。实际上,关键点就是new操作符就是将原型链与实例的this对象联系起来。所以说如果想有原型链就必须用new操作符,否则this就变成了window对象了。



构造函数就是使用关键字new调用的函数。当使用new调用时,构造函数内部的this对象会指向新创建的实例对象。

下面就定义一个构造函数并实例化一个对象:
function Person (name, age, job) {    this.name = name;    this.age = age;    this.job = job;        this.sayName = function () {        console.log(this.name);    };}//使用new实例化一个对象var person1 = new Person("Tom", 21, "WEB前端");

console.log(person1.age); //21person1.sayName(); //Tom

Person构造函数使用this对象给三个属性赋值:name、age和job。当使用new创建新对象时,this对象就会指向这个新对象,同时给它分配这个三个属性。


如果没有使用new关键字来调用该构造函数,直接就使用Person构造函数来创建新对象,由于this对象是在运行时才与新对象绑定的,所以直接调用Person(),this对象将映射到window对象上,即直接指向window对象,导致将Person构造的属性添加到window对象上。

如:
function Person (name, age, job) {    this.name = name;    this.age = age;    this.job = job;        this.sayName = function () {        console.log(this.name);    };}//使用new实例化一个对象var person1 = new Person("Tom", 21, "WEB前端");console.log(person1.age); //21person1.sayName(); //Tom//不使用new关键字,直接调用Person(),将属性添加到了window对象上var person2 = Person("Bob", 22, "Doctor");console.log(window.name); //Bobconsole.log(window.age); //22window.sayName(); //Bob

原本是给person实例的三个属性被添加到了window对象上,没有使用new操作符,这里的构造函数是作为普通函数调用的,即"var person = Person()"就相当于“Person()”,this对象晚绑定,解析器将this对象解析为window对象了。原来的window的name属性量用于标识链接目标和frme的,这里name属性将覆盖window对象原来的属性,因为所导致其它错误。

解决上述问题的方案就是:创建一个作用域安全的构造函数也就是能让this对象指向正确的实例对象。首先判断this对象是否为正确类型的实例(this对象是否指向构造函数),即判断this对象是不是Person类型的实例,如果不是,则创建新的实例并返回。提示:使用instanceof方法来判断this对象是否属于正确的类型实例(也就是构造函数的实例)。上述例子错误的原因就是将this对象解析为了window对象,window对象不属于Person构造函数的实例类型。所以在进行构造函数的一切操作前,先判断this对象是否为某构造函数的实例。

function Person (name, age, job) {    //首先检测this对象是否为正确类型的实例,即是否为Person类型的实例。    if (this instanceof Person) { //现有的实例环境中,判断this是否指向Person        this.name = name;        this.age = age;        this.job = job;            this.sayName = function () {            console.log(this.name); //返回一个新的实例(那么以上赋值操作就没有执行)        };    } else {        return new Person(name, age, job); //如果检测出this不是正确类型的实例则创建实例并返回    }}//首先使用new操作符创建新实例var person1 = new Person("Tom", 21, "WEB前端");console.log(person1.age); //21person1.sayName(); //Tom//接着不使用new,直接调用Person()作为普通函数。var person2 = Person("Bob", 22, "Doctor");console.log(person2.name); //Bobconsole.log(person2.age); //22person2.sayName(); //Bob

通过判断this对象是否是构造函数的实例,来确定由该构造函数创建的对象是否是该构造函数的实例。
这里添加了一段if()语句用于判断this对象是否为Person构造函数的实例,它表示要么使用new关键字创建实例,要么在现有的Person实例环境中调用构造函数。如果this对象不是Person的实例,那么就会再次使用new操作符调用Person构造函数返回结果。所以最后不管是否使用new关键字调用Person构造函数,都会返回一个Person的新实例。


构造函数窃取模式的继承

构造函数窃取模式的继承是JavaScript最常见的继承方法之一,它的思想就是:在子类型构造函数中调用超类型构造函数。介它有一些缺陷。它依就不安全。
function Person (sides) {    //首先检测this对象是否为Person的实例。    if (this instanceof Person) { 判断this是否指向Person        this.sides = sides;        this.getArea = function () {            return 0;        };    } else {        return new Person(sides); //如果检测出this不是正确类型的实例则创建实例并返回    }}function Rec (w, h) {    Person.call(this, 2); //想借用构造函数模式来继承Person的属性和方法    this.w = w;    this.h = h;        this.getArea = function () {        return this.w * this.h;    };}var rec = new Rec(10, 10);console.log(rec.sides); //undefined

在以上代码中,Person构造函数作用域是安全的,而Rec构造函数作用域不是安全的。在新创建一个Rec实例rec后,这个实例rec本该通过Person.call(this, 2)继承Person的sides属性的,但是,Person构造函数作用域是安全的,判断出this对象不是Person的实例(此时的this是Rec实例中的this对象),因此构造函数会创建并返回一个新的Person实例,而不会把sides属性赋值给this对象(也就是不会执行if(){}中的语句,而是执行else{}中的语句),所以Rec的this对象上没有sides属性。Rec的实例中也不会有sides属性了。因此返回"undefined"。


构造函数窃取模式结合原型链实现继承


通过借用构造函数模式与原型链模式的组合来实现继承。
function Person (sides) {    //首先检测this对象是否为正确类型的实例,即是否为Person类型的实例。    if (this instanceof Person) {        this.sides = sides;        this.getArea = function () {            return 0;        };    } else {        return new Person(sides); //如果检测出this不是Person的实例,则创建实例并返回    }}function Rec (w, h) {    Person.call(this, 2); //借用构造函数模式继承    this.w = w;    this.h = h;        this.getArea = function () {        return this.w * this.h;    };}Rec.prototype = new Person(); //原型链实现继承:原型对象等于另一个对象的实例。var rec = new Rec(10, 10);console.log(rec.sides); //2  sides的值

通过原型链继承,使Rec的实例也变成Person的实例,“Person.call(this. 2)“会按原意执行,最终为Rec添加了sides属性,这样Rec的实例也就继承了Person的属性sides。


借用构造函数实现继承这个问题思路量:要通过if判断语句这道关卡(也就是说当this指向Person时才能通过)。把Rec的实例也变成Person的实例,那么this对象就指向了Person构造函数。

还有要时刻记住:通过new操作创建实例对象的过程。


0 0
原创粉丝点击