编写高质量代码:改善JavaScript程序建议--面向对象编程

建议1:参照Object构造体系分析prototype机制

​ 对象(Object)是没有原型的,只有构造函数拥有原型,而构造类的实例对象能够通过prototype属性访问原型对象。prototype表示类的原型,就是构造类拥有的原始成员。构造函数的prototype属性存储着一个引用对象指针,该指针指向一个原型对象。

​ 所有的函数在其定义时就已经自动创建和初始化好了prototype属性,这个初始化好的prototype属性指向一个只包含一个constructor属性的对象,并且这个constructor属性指向这个function自身。

var obj = new Object()
console.log(obj.constructor); // function Object() { [native code] }
 
 
  • 1
  • 2

​ 当构造函数实例化后,所有实例对象都可以访问构造函数的原型成员。如果在原型对象中声明一个成员,则所有实例对象都可以共享它。

​ Object与Function之间关系非常微妙,它们都是高度抽象的类型,互为对方的实例。

console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true
 
 
  • 1
  • 2

类型、原型和对象实例之间的关系

​ 原型关系是一种动态的关系,如果添加一个新的属性到原型中,那么该属性会立即被所有基于该原型创建的对象继承。

function P(x){this.x = x;}
var p1 = new P("p1");
var p2 = new P("p2");
P.prototype.y = 1;
console.log(p1.x, p1.y); // p1 1
console.log(p2.x, p1.y); // p2 1
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里写图片描述

建议2:不要直接检索对象属性值

var modelValue = obj.obj_1.model; // TypeError
var modelValue = obj.obj_1 && obj.obj_1.model; // undefined
 
 
  • 1
  • 2

稳妥起见,可以通过&&运算符避免上述错误!

建议3:防止原型反射

使用for…in语句,可以遍历对象中所有属性(包括原型);Object.keys()只遍历自身可枚举属性。

  • 使用hasOwnProperty方法过滤原型属性
  • 使用typeof运算符排除方法函数
for(var name in obj) {
    if(obj.hasOwnProperty(name) && typeof obj[name] !== 'function') {
        console.log(name, obj[name]);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

建议4:谨慎处理对象的Scope

​ 在JavaScript中,function是作用于词法范围而不是动态运行范围的(词法作用域)。

​ 当function作为对象的方法运行是,this是该对象的引用;如果该function没有作为对象的方法,则this代表全局对象。

var a = 1;
function f1() {
    var a  = 2;
    function f2() {
        console.log(this.a); // this指向window
    } 
    f2();
}
f1(); // 1
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

​ 解决上述问题的通常做法是在将外部function的this值保存到某变量中,在内部函数中更使用。

var obj = {
    myVal: 'hello',
    fn: function() {
        var self = this;
        function inner() {
            console.log(self.myVal);
        }
        inner();
    }
}
obj.fn();  // hello
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

​ 在JSON字符串中加上一对括号,这样做可以迫使eval方法在评估JavaScript代码时强制作为表达式执行从而得到JSON对象,而不是作为语句执行。

var JSONString = '{"name":"ligang","age":27}';
var JSONObject = eval("(" + JSONString + ")");
console.log(JSONObject.name);
 
 
  • 1
  • 2
  • 3

注意:evalwindow.eval

function testEvalScope() {
    eval('var a = 1');
    console.log(a);
}
testEvalScope();
console.log(typeof a); // undefined
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
function testEvalScope() {
    window.eval('var a = 1');
    console.log(a);
}
testEvalScope();
console.log(typeof a); // number
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

建议5:this是动态指针,不是静态引用

​ this指向的对象是由this所在执行域(运行环境)决定的,而不是由this所在的定义域决定的。它始终指向当前调用对象。在JavaScript中类似指针特性的标识还有如下3个:

  • callee:函数的参数集合包含的一个静态指针,它始终指向参数集合所属的函数;
  • prototype:函数包含的一个半静态指针,在默认状态下它始终指向函数附带的原型对象,不过可以改变这个指针,使它指向其他对象;
  • constructor:对象包含的一个指针,它始终指向创建该对象的构造函数。

(1)函数的引用和调用

引用函数能够改变函数的执行作用域,而调用函数不会改变函数的执行作用域。

var obj = {
    name: '对象obj',
    f: function() {
        return this;
    }
};
obj.o1 = {
    name: '对象o1',
    me: obj.f   // 引用对象obj的方法f
};
obj.o2 = {
    name: '对象o2',
    me: obj.f() // 调用对象obj的方法f
};
console.log(obj.o1.me().name); // 对象o1
console.log(obj.o2.me.name);   // 对象obj
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(2)call和apply

call、apply可以直接改变执行函数的作用域。

function f() {
    if(this.constructor === arguments.callee) console.log('实例对象');
    else if(this === window) console.log('windwo对象');
    else console.log(`其他对象 constructor = ${this.constructor}`);
}

f();        // windwo对象
new f();    // new f()
f.call(1);  // 其他对象 constructor = function Number() { [native code] }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(3)异步调用

通过事件机制或定时器来延迟或调整函数的调用时间和时机。由于调用函数的执行作用域不再是原来定义的作用域,所有函数中this总是指向发生该事件的对象或window对象。

var obj = {
    value: 123,
    f: function() {
        console.log(this.value, arguments);
    }
};

var btn = document.getElementById('btn');
btn.addEventListener('click', obj.f); // 空
btn.addEventListener('click', obj.f.bind(obj, event)); // 123
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
var obj = {
    f: function() {
        if(this == obj) console.log("this = obj");
        if(this == window) console.log("this = window");
    }
}
obj.f(); // this = obj
setTimeout(obj.f, 1000); // this = window
setTimeout(function(){
  obj.f.call(obj)
}, 1000); // this = obj
setTimeout(obj.f.bind(obj), 1000); // // this = obj
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

建议6:使用享元类

享元类就是类的类型,即创建类型的类。享元类能够接受类作为参数,即享元类操作的是类,返回类。

function O(x) {
    return function() {
        this.x = x;
        this.get = function() {
            console.log(this.x);
        }
    }
}
var o = new O(1);
var f = new o();
f.get(); // 1
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果构造函数有返回值,并且返回值是引用类型,那么经过new运算符计算后,返回的再不是构造函数自身对应的实例对象,而是构造函数包含的返回值(即引用类型值)。

function F() {
    this.x = 1;
    return function() {
        return this.x;
    }
}
F.prototype.y = function() {
    console.log("call y");
}

var f = new F();
console.log(f.x);   // undefined,构造函数有新返回值
console.log(F()()); // 1,this指向window
console.log(f.y()); // TypeError,无当前成员
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值