继承
- 传统形式——原型链:由于可以形成很长的链,所以会过多的继承链上的没用的属性
- 借用构造函数——call()/apply():不能继承借用的那个构造函数的原型;每次创建一个完整的对象时,实际上调用了多个函数(即,要借用其他的构造函数),效率并不高
- 共享原型/公有原型:不能随便改动自己的原型,因为一旦改动自己的原型,就会影响自己所继承的构造函数或者继承了自己的那些构造函数,因为它们的指向是相同的,所以任意一方有所改变,则都会随之改变。
<script type = "text/javascript">
Father.prototype.lastName = 'zjy';
function Father() {}
function Son() {}
Son.prototype = Father.prototype; //关键
var son = new Son();
</script>
或:
<script type = "text/javascript">
Father.prototype.lastName = 'zjy';
function Father() {}
function Son() {}
function inherit(Target, Origin) { //关键1(一个普通的函数/方法)
Target.prototype = Origin.prototype;
}
inherit(Son, Father); //关键2
var son = new Son();
</script>
- 圣杯模式
<script type = "text/javascript">
Father.prototype.lastName = 'zjy';
function Father() {}
function Son() {}
function inherit(Target, Origin) { //关键: 形成了一个三层的原型链,中间那一层起连接作用,从而使得修改某一边界的原型时,不会使另一边界的原型被修改
function F() {};
F.prototype = Origin.prototype;
Target.prototype = new F();
}
inherit(Son, Father);
var son = new Son();
var father = new Father();
</script>
上图中,我们看到:son的constructor属性不再指向Son构造函数…这是因为继承关系的存在。那么如何使son仍指向Son的构造函数呢?只需在inherit()方法中增加Target.prototype.constructor = Target;
语句…结果如下:
另外,为了使程序更具有实用性,通常在inherit()方法中有如下语句:Target.prototype.uber = Origin.prototype;
表示我们可以调用Target.prototype中的uber属性来查看真正继承的父类。
因此,圣杯模式的最终效果:
function inherit(Target, Origin) {
function F() {};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
圣杯模式的高级写法(利用了闭包的第三点作用):
var inerit = (function () { //inerit 是一个立即执行函数
var F = function () {};//F形成了闭包,于是成了inherit函数的私有化变量(本身F只是为了过渡,被没有实际用途)
return function (Target, Origin) {//返回一个函数引用
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
}());
命名空间
- 作用:管理变量,防止污染全局,适用于模块化开发。
1. 陈旧的命名空间
使用:
2. 高端开发方法——闭包
<script type = "text/javascript">
var init = (function () {
var name = 'abc';
function callName() {
console.log(name);
}
return function () {
callName();
}
}())
var init2 = (function () {
var name = 'def';
function callName() {
console.log(name);
}
return function () {
callName();
}
}())
init();
init2();
</script>
init()和init2()函数中都是用到了name属性,但闭包的第三点作用可以将其私有化,互不影响(毕竟只能在自身所在的函数中使用,必然不会对外界产生影响)!这也是闭包的第四点作用——模块化开发,防止溢出/污染全局变量。
在构造函数中形成闭包的实例和一些面试题:
<script type = "text/javascript"> function Person(name, age, sex) { var a = 0; this.name = name; this.age = age; this.sex = sex; function sss() { a++; document.write(a); } this.say = sss; //sss()是私有化变量,调用say()会形成闭包 } var oPerson = new Person(); oPerson.say(); // a = 1 oPerson.say(); // a = 2 var oPerson1 = new Person(); oPerson1.say(); // a = 1 </script>
<script type = "text/javascript"> var a = (function (x) { delete x; //此语句不会产生实际效果,因为参数x不会被删除掉 return x; })(1); document.write(a); // a = 1 </script>
<script type = "text/javascript"> (function () { document.write(typeof(arguments)); //object. 因为arguments是一个类数组,类数组也是对象 })() </script>
<script type = "text/javascript"> var h = function a() { //写到表达式中的函数名称是无效的...所以系统并无发识别出“a()" return 23; } console.log(typeof a()); //由于上面的原因,所以a()无法执行。最终报错:a is not defined </script>
<script type = "text/javascript"> var x = 1; if (function f() {}) { x += typeof f; //typeof f为undefined(字符串类型),所以其实质为字符串的拼接 } console.log(x); //输出结果:1undefined。 </script>
<script type = "text/javascript"> var f = ( function f() { return "1"; }, function g() { return 2; } )(); console.log(typeof f); //结果:number </script>
对象的枚举
通过for循环,我们可以遍历一个数组,现在,我们想要看一个对象里都有哪些属性以及对应的值——for…in…循环
<script type = "text/javascript">
var obj = {
name : 'zjy',
age : 100,
sex : 'female',
height : '188',
prop : '......'
}
var prop;
for(prop in obj) {
console.log (typeof(prop) + ' : ' + prop + ' —— ' + obj[prop]);
//打印格式:属性的数据类型 : 属性名 —— 属性值
}
for(var prop in obj) {
console.log (obj.prop);
//注意:如果这样写,则将prop当做了obj的属性,每次都打印的是这个属性值。
//解释:obj.prop底层会转换为obj['prop'],这说明prop是一个定量的字符串,所以每次都打印出同样的prop值。
//修正:console.log (obj[prop]);
}
</script>
for…in…循环能否打印出原型中自定义的属性?可以(注意:原型中系统定义的属性无论如何是不打印的):
<script type = "text/javascript">
var obj = {
__proto__ : {
name : 'sunny'
}
}
var prop;
for(prop in obj) {
console.log (typeof(prop) + ' : ' + prop + ' —— ' + obj[prop]);
//打印格式:属性的数据类型 : 属性名 : 属性值
}
</script>
如何设定不打印原型中自定义的属性?如下:
<script type = "text/javascript">
var obj = {
age : 12,
__proto__ : {
name : 'sunny'
}
}
var prop;
for(prop in obj) {
if(obj.hasOwnProperty(prop)) {
//hasOwnProperty(prop):用来判断prop是否为obj自己定义的属性(而不是原型中的属性)。
//返回值为Boolean:true表示是自己定义的属性(不是原型中的属性)
console.log (typeof(prop) + ' : ' + prop + ' —— ' + obj[prop]);
}
}
</script>
判断对象中是否存在某属性——in关键字。对于上面的例子,有如下演示:
【注意:属性要写成字符串形式!否则出现错误。】
判断A对象是否是利用B构造函数构造出来的—— A instanceof B。实例:
<script type = "text/javascript"> function Person() { } function Student() { } var person = new Person(); var stu = new Student(); var obj = {}; </script>
【注意:看到上面的图,我们实际上应该这样理解A instanceof B——看A对象的原型链上是否存在B的原型。】
一个变量可能是个数组
var arr = [];
,也可能是个其他对象var arr = {};
,如何判断它是否是数组?
- 方法1. 查看这个变量的构造函数constructor
- 方法2. 使用instanceof关键字
- 方法3. 使用Object类的原型中的toString()方法
由于Object类是原型链上的最高层,如Number、Array、String、Boolean的原型中都有各自特有的toString()方法,这些类中的toString()方法就是重写的其最终父类Object中的toString()方法。如果想不想调用本身重写的方法,而是想要调用Object内的,则需要调用特殊的方法call(elem):Object.prototype.toString.call(elem)
就可以看到elem属于何种数据类型。实例:<script type = "text/javascript"> var num = 123; </script>
一个实例
拷贝一个已有的对象
<script type = "text/javascript">
//步骤:
//遍历所有属性,看其数据类型——
//原始值:可直接拷贝
//引用值:不可直接拷贝; 它还需要分类——
//数组:遍历每一元素分别进行拷贝——递归
//对象:对这一对象内的所有属性继续遍历、拷贝——递归
var obj = {
name : 'sunny',
age : 12,
card : ['visa', 'master'],
son : {
name : 'tom',
sister : {
name : 'aaa'
}
}
}
var obj1 = { };
// @origin: 要拷贝的对象
// @target: 拷贝出来的结果
function deepClone(origin, target) {
var target = target || {},//预备工作之为target赋值:target不为null的话就还用target,否则就给target赋值为{}
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for (var prop in origin) {
if(origin.hasOwnProperty(prop)) {
if(origin[prop] !== 'null' && typeof(origin[prop] == 'object')) { //1 属性为引用值的情况
//“!==”表示“绝对不等于”,有隐式转换也不行的
if(toStr.call(origin[prop] == arrStr)) { //1.1 属性为引用值——数组的情况
target[prop] = [];
} else { //1.2属性为引用值——对象的情况
target[prop] = {};
}
//target[prop] = toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop], target[prop]);
} else { //2 属性为原始值的情况
target[prop] = origin[prop];
}
}
}
return target;
}
deepClone(obj, obj1);
</script>
PS. 上面是一个“深克隆”过程,所谓“深克隆”是指克隆后,如果某一方的值改变不会对另一方有影响。与之相对的是“浅克隆”,其实现方式是直接target[prop] = origin[prop];
,而不去区分引用值、原始值,这样引用值如果在某处改变,则会影响到另一处。
判断数据类型
<script type="text/javascript">
function type(target) {
var ret = typeof(target);
var template = { //属性名似乎可以写成任何形式了...反正实际是看成字符串的,所以把它写在了字符串里
"[object Array]" : "array",
"[object Object]" : "object",
"[object Number]" : "number - object",
"[object Boolean]" : "boolean - object",
"[object String]" : "string - object"
}
//一共三种分类:
if(target == null) { //1. null值
return null;
}
if(ret == 'object') { //2. 引用值
var str = Object.prototype.toString.call(target);//str只有五种情况,可能的结果已经保存在template中
return template[str];//!!!!!!
} else{ //3. 原始值 以及 传过来的参数是个函数(此时返回’function‘)
return ret;
}
}
</script>