网站首页 > 基础教程 正文
Object
JavaScript的所有对象都衍生于Object对象,所有对象都继承了Object.prototype上的方法和属性,虽然它们可能会被覆盖,熟悉它对于编程能起到很大的作用,也能比较深刻的了解JavaScript这门语言。
Object
创建一个对象可以使用new,也可以使用快速创建的方式:
var _object = {};
_object对象中就可以使用Object.prototype中所有的方法和属性,虽然看起来它是空的。说到这里在编程中常常有一个非常有用的需求,如何判断一个对象是空对象。
这是zepto中的判断一个对象是否是空对象,常常使用:
$.isEmptyObject = function(obj) { var name for (name in obj) return false
return true}
也顺便看了下jQuery原理是一模一样的:
isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false;
} return true;
}
使用in操作符来实现,它不会遍历到父原型链。
constructor返回一个指向创建了该对象的函数引用,这个东西主要是可以用来识别(类)到底是指向哪里的。
defineProperty直接在一个对象上定义一个新属性,非常适合用于动态构建,传入三个参数[动态添加对象的目标对象,需要定义或被修改的属性名,需要定义的对象],在第三个参数中可以有些属性来表示是否继承(proto),要不要定义get,set方法,enumerable是否可枚举。
defineProperties跟上述defineProperty一样,但是它可以添加多个。
getOwnPropertyNames返回一个由指定对象的所有属性组成的数组
keys返回一个数组包括对象所有的属性(可枚举)
keys是经常会用到的一个属性,它只能包可枚举的,如果想获取一个对象的所有属性包括不枚举的,那么使用getOwnPropertyNames。
hasOwnProperty用于判断某个对象是否包含有自身的属性,这个方法常常用于检测对象中的属性是否存在,它只检测自身,对于继承过来的都是false,这一点是非常重要的理解。
isPrototypeOf 用于检测一个对象是否在另一个对象的原型链上,比如有两个对象是互相交互的,常常会使用它来进行检测。
propertyIsEnumerable这个方法也比较重要,返回一个布尔值,检测一个对象的自身属性是否可以枚举
可枚举的理解,也就是对象的属性可枚举,它的属性值不可以修改,但是在Js中它有自己的定义,引擎内部看不见的该属性的[[Enumerable]]特性为true,那么就是可枚举的。基本上把一个普通对象可以看做是一个枚举类型,比如var color = {'red':1},red是可以修改的,但是red是可枚举的,但是如果是继承过来的属性,propertyIsEnumerable是返回false的,它还有一个特点,就是自身。
如果要定义不可枚举的属性,那就要使用defineProperty方法了,目前不能用对象直接量或者构造函数定义出来。
var obj = {name: 'jack', age:23}Object.defineProperty(obj, 'id', {value : '123', enumerable : false });
深拷贝与浅拷贝
关于拷贝的问题,主要分为深拷贝和浅拷贝,但是如果从空间分配上来说JavaScript的拷贝不应该算是深拷贝,比如:
var d = {};for(k in a){
d[k] = a[k];
}return d;
今天突然想到了这么一个问题,在C语言中,所谓的拷贝,就是分两种情况,一种是把指针地址拷贝给另外一个变量,虽然也开辟的了一个内存空间,在栈上也存在着一个地址,我对这个变量进行修改,同一个指针是会改变其值的,这种拷贝叫浅拷贝。另外一种情况,直接开辟一个新空间,把需要复制的值都复制在这个新的空间中,这种拷贝叫中深拷贝。
如果看到上述的一段Js代码,很多人说它是浅拷贝,假设传入一个a对象,拷贝完成之后返回一个d,当我修改返回对象的值时并不能同时修改a对象,于是,在这里我有一个很大的疑问,在Js中到底什么是浅拷贝,什么是深拷贝的问题?
这一点上感觉Js真的很奇葩,如果在开发iOS中,不可变对象copy一下,依然是不可变,所以是浅拷贝,拷贝了指针变量中存储的地址值。如果是可变对象copy一下,到不可变,空间变化了,包括不可变mutableCopy到不可变,空间依然变化了,所以是深拷贝。但是JavaScript中对于这一点要考虑一种情况,值类型,和引用类型,这个基础知识,我相信大家都非常清楚。数字,字符串等都是值类型,object,array等都是引用类型。
var a = [1,2,3];var b = a;b.push(4);console.log(a); //[1,2,3,4]var numb = 123;var _numb = numb;
_numb = 567;console.log(numb); //123
从这个例子中可以看的出来,它们使用的都是=符号,而数组a发生了变化,numb数字却没有发生变化。那么从这里,可以有一个总结,所谓了深拷贝,浅拷贝的问题,应该针对的是有多个嵌套发生的情况。不然假设是这样的情况,还能叫浅拷贝么?
var object = {"de":123};var o = copy(object);
o.de = 456;console.log(object) //{"de":123}
明显对象o中的de属性修改并没有影响到原始对象,一个对象中的属性是一个字符串,如果从内存空间的角度上来说,这里明显是开辟了新的空间,还能说是浅拷贝么?那么针对另外一种情况。
var object = { "de":{ "d":123
}
}var o = deepCopy(object);o.de.d = "asd";
如果一个对象中的第一层属性,不是值类型,只单层循环,这样来看的话确实是一个浅拷贝,因为在Js中引用类型用=赋值,实际上是引用,这样说的通。所以,深拷贝,还需要做一些处理,把object,array等引用类型识别出来,深层递归到最后一层,一个一个的拷贝。
var deepCopy = function(o){ var target = {}; if(typeof o !== 'object' && !Array.isArray(o)){ return o;
} for(var k in o){
target[k] = deepCopy(o[k]);
} return target;
}
思路是如此,这个例子只考虑了两种情况,对象和数组,为了验证这样的思路,最后的结果与预期是一样的。
var _copy = { 'object':{ 'name':'wen'
}, 'array':[1,2]
}var h = deepCopy(_copy);h.object.name = 'lcepy';h.array[1] = 8;console.log(h);console.log(_copy);
面向对象
面向对象的语言有一个非常明显的标志:类,通过类来创建任意多个具有相同属性和方法的对象,可惜的是Js里没有这样的概念。
但是Js有一个特性:一切皆是对象。
聪明的开发者通过这些特性进行摸索,于是迂回发明了一些程序设计,以便更好的组织代码结构。
工厂模式
主要是用来解决有多个相同属性和方法的对象的问题,可以用函数来封装特定的接口来实现
var computer = function(name,version){ return { 'name':name, 'version':version, 'showMessage':function(){ alert(this.name);
}
}
}var test = computer('apple','11.1');test.showMessage();
构造函数模式
我们知道像原生的构造函数,比如Object,Array等,它们是在运行时自动出现在执行环境中的。因此,为了模仿它,这里也可以通过一个普通的函数,并且new出一个对象,这样就成为了自定义的构造函数,也可以为他们添加自定义的属性和方法。
但是这样的构造函数有一个缺陷,就是每个方法都会在每个实例上创建一次,因为每次创建都需要分配内存空间,但是有时候这样的特性还是有用的,主要是要控制它们,在不使用的时候释放内存。
var Computer = function(name,version){ this.name = name; this.version = version; this.showMessage = function(){ alert(this.name);
}
}var apple = new Computer('apple',2014);var dell = new Computer('dell',2010);apple.showMessage();dell.showMessage();
像apple,dell是通过Computer实例化出来的不同的对象,但是它们的constructor都是指向Computer的。这里也可以使用instanceof来对(对象)进行检测。
在书写上构造函数跟其他函数是没有什么区别的,主要的区别还是在使用上,构造函数需要使用new操作符。
其实这样的书写,已经跟类没有什么区别了,表面上来看,而构造函数我个人更倾向于一个类的某个静态方法。
原型模式
说到原型模式就不得不提一提关于指针的问题,因为每一个函数都有一个prototype属性,而这个属性是一个指针,指向一个对象。
C语言描述指针,这个在iOS开发中非常重要
比如我先定义一个int类型的指针变量和一个普通的int类型数据,然后给指针变量赋值。
int *p; int pp = 123;
p = &pp;
*p = 999; printf('%d',pp);
*是一个特殊符号用于标明它是一个指针变量。
&是地址符
分析这个就要说到栈内存和堆内存了,比如*p在栈内存中分配了一个地址假设是ff22x0,它还没有空间。而pp存在一个地址ff23x0,并且分配了一个空间存储着123,这个地址是指向这个空间的。
p = &pp 这样的赋值操作,也就是把ff23x0取出来,并且给p分配一个空间把ff23x0存储进去,并且ff22x0指向这个空间。
*p = 999 从这里就可以看出来p操作的是地址,而这个地址不就是ff23x0么,于是pp成了999。
所谓的指针也就是存储着地址的变量。
回到原型上,如果每一个函数中的 prototype属性都是一个指针,实际上它只是一个地址引用着一个空间,而这个空间正是我们写的xxx.prototype.xxx = function(){}这样的代码在运行时分配的空间。那么可见,使用原型的好处是空间只分配一次,大家都是共享的,因为它是指针。
先看一个例子
var Computer = function(name){ this.name = name;
}Computer.prototype.showMessage = function(name){ alert(name);
}var apple = new Computer('apple');var dell = new Computer('dell');Computer.prototype.isPrototypeOf(apple);
在解释这个原型链之前,还要明白Js的一个特性,就是如果自身不存在,它会沿着原型往上查找。它的原理稍微有些绕,Computer自身的prototype是指向它自身的原型对象的,而每一个函数又有一个constructor指向它自身,prototype.constructor又指向它自身。于是Computer的两个实例apple,dell内部有一个proto属性是指向Computer.prototype的,最后的结果是它们可以使用showMessage方法。
当然它们还有一个搜索原则,比如在调用showMessage的时候,引擎先问apple自身有showMessage吗?“没有”,继续搜索,apple的原型有吗,“有”,调用。所以从这里可以看出,this.showMessage是会覆盖prototype.showMessage的。
另外还可以使用isPrototypeOf来检测一个对象是否在另一个对象的原型链上,上述的代码返回的是true。
apple.hasOwnProperty('name')apple.hasOwnProperty('showMessage')
使用hasOwnProperty来检测到底是对象属性还是原型属性,使用this创建的属性是一个对象属性。
从上面可以看出来原型链的好处,但是它也不是万能的,正因为指针的存在,对于某些引用类型来说这个就非常不好了,我需要保持原对象属性值是每一个对象特有的,而不是共享的,于是把之前的构造函数与原型组合起来,也就解决了这样的问题。
var Computer = function(name){ this.name = name;
}Computer.prototype.showMessage = function(){ alert(this.name);
}var apple = new Computer('apple');apple.showMessage();
这样的结果是在对象中都会创建一份属于自己的属性,而方法则是共享的。
动态原型模式
有时候遇到某些问题需要动态添加原型,但是实例中是不能添加的,所以绕来一下,在初始化构造函数中添加。
var Computer = function(){ if(typeof this.showMessage !== 'function'){ Computer.prototype.showMessage = function(){
}
}
}
只要初始化了一次,以后就不用修改了。
寄生构造函数模式
这种模式的原理就是在一个函数中封装需要创建对象的代码,然后返回它。
var test = function(name){ return { 'name':name
}
}var g = new test('apple');var f = de('dell');
看起来它跟工厂模式还是很像的,
稳妥模式
这种模式主要是在解决需要安全的环境中使用,一般来说一个类如果不提供getter,setter方法,是不允许直接访问和修改的。
var computer = function(name){ var _name = name; return { 'getter':function(){ return _name;
}, 'setter':function(name){
_name = name;
}
}
}
这样的方式可以保证属性或者说是数据的安全性,不允许直接随便修改,如果不提供setter方法的话,压根就不允许。
早读课提醒
言归正传我们在微信群中推出了《早读课》,每日分享一篇我们认真精选的文章(不限于前端开发类),其目的是帮助开发者来学习有价值的东西。想加微信群的朋友,直接添加我的微信号:icepy_1988,审核之后会邀请你入群。想加QQ群的朋友,可以直接添加:418898836,答对问题即可入群。
关注我们
更多精彩内容可关注微信公众号:搜索 fed-talk ,来关注我们吧,也欢迎你将它分享给自己的朋友。
猜你喜欢
- 2024-12-17 springboot:实现文件上传下载实时进度条功能【附带源码】
- 2024-12-17 我用ChatGPT做开发之小轻世界聊天系统
- 2024-12-17 electron聊天室|vue+electron-vue仿微信客户端|electron桌面聊天
- 2024-12-17 JavaScript检测用户是否在线! js判断用户是否登录
- 2024-12-17 深入了解EL表达式 什么是el表达式
- 2024-12-17 Vue虚拟DOM 什么是虚拟DOM?如何实现一个虚拟DOM 虚拟DOM原理
- 2024-12-17 SpringBoot系列——Jackson序列化
- 2024-12-17 箭头函数和常规函数之间的 5 个区别
- 2024-12-17 Vue合理配置WebSocket并实现群聊 vue建立websocket连接
- 2024-12-17 访问静态资源和处理AJAX请求的数据
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)