对象属性的操作
访问对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
var obj = { p: 'Hello World' };
console.log(obj.p);// "Hello World"
console.log(obj['p']);// "Hello World"
obj.c = 'Hi'; //添加属性 c
console.log(obj);//Object {p: "Hello World", c: "Hi"}
delete obj.p; //删除属性 p
console.log(obj);//Object {c: "Hi"}
请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理
。
属性是否存在:in 运算符
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false。它的左边是一个字符串
,表示属性名,右边是一个对象。
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj本身并没有toString属性,但是in运算符会返回true,因为这个属性是继承的。
这时,可以使用对象的hasOwnProperty
方法判断一下,是否为对象自身的属性。
对象属性遍历枚举for…in
for...in
循环用来遍历一个对象的全部属性
JavaScript里面的一个底层原理:使用点运算符时,JavaScript会在内部隐式地转换成方括号运算符。
obj.i
=== 转换成 ===> obj['i']
var obj = { a : 123, b:456 , c : function(){ return this;}};
for(var i in obj){
console.log( '属性: '+i +'值: '+ obj[i]+' ' );
console.log( obj.i ); //undefined
// 遍历枚举时,不能使用点运算符 , obj.i 转变成obj['i'],意思变成了 访问对象里面的i属性
}
for...in
循环有两个使用注意点。
它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
它不仅遍历对象自身的属性,还遍历继承的属性
。
举例来说,对象都继承了toString属性,但是for...in
循环不会遍历到这个属性。
Object.prototype.efg = 123;
var obj = { a : 123, b:456 , c : { d : 7}};
for(var i in obj){
console.log( '属性: '+i +'值: '+ obj[i]+' ' ); // 会遍历出 继承的abc 属性
}
如只想遍历对象自身的属性,应该结合使用hasOwnProperty
方法
Object.prototype.efg = 123;
var obj = { a : 123, b:456 , c : { d : 7}};
for(var i in obj){
if (obj.hasOwnProperty(i)) { //只遍历自己的属性
console.log( '属性: '+i +' 值: '+ obj[i]+' ' );
}
}
对象的创建方法
一、 var obj = {}
(plainObject 对象字面量/对象直接量)
二、 构造函数
1)系统自带的构造函数 Object()
,如:var obj = new Object();
2)自定义的构造函数,如下:
function Person(){
}
var xiaoming = new Person();
自定义构造函数的写法和正常函数没有什么区别,一般约定构造函数名采用大驼峰命名规则
大驼峰:TheFirstName
小驼峰:theFirstName
三、 Object.create()
很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?
JavaScript 提供了Object.create方法,用来满足这种需求。该方法接受一个对象作为参数
,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
// 原型对象
var A = {
print: function () {
console.log('hello');
}
};
// 实例对象
var B = Object.create(A);
Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true
上面代码中,Object.create方法以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
构造函数内部原理三段式
使用 new
关键字后产生隐式三段式
1)在函数体最前面隐式的加上 this ={ }
2)执行 this.xxx = xxx;
3)隐式的返回 this
function Person(name,age){
// var this = { __proto__ : Person.prototype } AO{this :{ }}
this.name = name;
this.age = age;
// var this = {
// name : '',
// age : ,
// __proto__ : Person.prototype
// }
// return this;
}
var xiaoming = new Person('小明',18);
包装对象
三种原始类型的值:数值
、字符串
、布尔值
,在一定条件下,也会自动转为对象,也就是原始类型的 “包装对象
”。
var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);
所谓“包装对象”,就是分别与数值、字符串、布尔值相对应的Number
、String
、Boolean
三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。
包装对象的最大目的,首先是使得 JavaScript 的对象涵盖所有的值,其次使得原始类型的值可以方便地调用某些方法。
var str = 'abc';
console.log(str.length) // 3
// 相当于生成临时对象new String(str)
// console.log(new String(str).length)
// 最后会delete new String(str)
// 等同于
var strObj = new String(str)
// String {
// 0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
// }
console.log(strObj.length) // 3
上面代码中,abc
是一个字符串,本身不是对象,不能调用length
属性。(只有对象才有属性和方法)JavaScript 引擎自动将其转为包装对象,在这个对象上调用length属性。调用结束后,这个临时对象
就会被销毁
。这就叫原始类型与实例对象的自动转换。
var str = 'abc';
str.sign = '标记';
// 假如给str 添加sign属性,因str不是对象,JavaScript 引擎自动生成临时对象 new String(str).sign = '标记';
// 但是使用过后 new String(str) 就会被销毁
console.log(str.sign) // undefined
// str.sign 调用时JavaScript 引擎自动生成新的临时对象 new String(str)
// 但新的临时对象 new String(str) 是没有sign属性的,所以undefined
原型
定义:原型是function对象的一个属性,它定义了构造函数制造的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
。
// Person.prototype ---原型,原型是个对象
Person.prototype.lastName = 'Li';
function Person(){
}
var xiaoming = new Person();
console.log(xiaoming.lastName); // Li
console.log(xiaoming.constructor); // function Person(){}
原型的属性和方法,是实例对象的公共属性和方法
实例对象查看原型 ==> 实例对象的隐式属性__proto__
prototype对象
有一个constructor
属性,默认指向prototype对象所在的构造函数。由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
上面代码中,xiaoming
是构造函数Person的实例对象,但是xiaoming自身没有constructor属性,该属性其实是读取原型链上面的Person.prototype.constructor
属性。
constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
实例对象查看对象的构造函数 ==> 隐式属性 constructor
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype
),由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain
):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object构造函数
的prototype属性
。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性
,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性。
instanceof
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
instanceof运算符的左边是实例对象
,右边是构造函数
。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。
function Person(){
}
var a = new Person();
a instanceof Person //true
由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
instanceof运算符的一个用处,是判断值的类型
var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
call / apply
call
和 apply
都是改变this
的指向,区别是传递的参数列表不同。
继承的发展史
1、传统形式 原型链
Grand.prototype.lastName = 'Li';
function Grand(){
this.big = '大'
}
var laoLi = new Grand();
Father.prototype = laoLi;
function Father(){
this.normal = '中'
}
var liGang = new Father();
Son.prototype = liGang;
function Son(){
this.small = '小'
}
var xiaoming = new Son();
console.log(xiaoming.lastName);
过多的继承了没用的属性和方法
2、借用构造函数
function Person(name , age ){
this.name = name;
this.age = age;
}
function Student(name , age , grade){
Person.call(this , name , age );
this.grade = grade;
}
var xiaoming = new Student('xiaoming',18,3);
不能继承借用构造函数的原型
每次构造函数都要多走一个函数
3、共享原型
Father.prototype.lastName = 'Zhang';
function Father(){
}
function Son(){
}
Son.prototype = Father.prototype; //共用同一个原型
var xiaoming = new Son();
不能随便改动自己的原型
4、圣杯模式
function inherit(Target , Origin){
function F(){}
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
// 另一种写法
// var inherit = (function(){
// var F = function(){};
// return function(Target , Origin){
// F.prototype = Origin.prototype;
// Target.prototype = new F();
// Target.prototype.constructor = Target;
// Target.prototype.uber = Origin.prototype;
// }
// }());
Father.prototype.lastName = 'Zhang';
function Father(){
}
function Son(){
}
inherit(Son , Father);
var liGang = new Father();
var xiaoming = new Son();
模块
ES6 才开始支持“类”和“模块”。下面介绍传统的做法,如何利用对象实现模块的效果。
封装私有变量:立即执行函数的写法
var module1 = (function () {
var _count = 0;
var m1 = function () {
//...
};
var m2 = function () {
//...
};
return {
m1 : m1,
m2 : m2
};
})();
module1
就是 JavaScript 模块的基本写法
1)模块的放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代码为module1
模块添加了一个新方法m3()
,然后返回新的module1
模块。
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"(Loose augmentation)。
var module1 = (function (mod) {
//...
return mod;
})(window.module1 || {});
与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象
。
2)输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);
上面的module1模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
立即执行函数还可以起到命名空间的作用。
(function($, window, document) {
function go(num) {
}
function handleEvents() {
}
function initialize() {
}
function dieCarouselDie() {
}
//attach to the global scope
window.finalCarousel = {
init : initialize,
destroy : dieCouraselDie
}
})( jQuery, window, document );
上面代码中,finalCarousel对象输出到全局,对外暴露init和destroy接口,内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的
区分数组和对象的三种方法
1、constructor
2、instanceof
3、toString
var x = [];
var y = {};
x.constructor // function Array() { [native code] }
y.constructor // function Object() { [native code] }
x instanceof Array // true
y instanceof Object // true
Object.prototype.toString.call(x); //"[object Array]"
Object.prototype.toString.call(y); //"[object Object]"
深度克隆
var a = {
name : "aaa",
say : { name :'name',ln :{ a: 12, b:23}},
sp : [ 123 , 'bbb',['qqqq','wwwww'] ],
talk : function(){
console.log(1);
}
}
function clone(Origin,Target){
var Target = Target || {};
for ( var prop in Origin ){ //遍历对象 Origin
if (Origin.hasOwnProperty(prop)) { //判断是否是自身属性
if ( Origin[prop] !== null && typeof Origin[prop] == 'object'){ //判断 不是原始值 是数组或对象
if ( Origin[prop] instanceof Array ) { //判断 是数组
Target[prop] = [];
}else{ //判断 是对象
Target[prop] = {};
}
clone( Origin[prop], Target[prop]); // 调用自身,递归
}else{
Target[prop] = Origin[prop];
}
}
}
return Target;
}
var b = clone(a);
this
this
总是返回一个对象,this
就是属性或方法“当前”
所在的对象。
1、函数预编译过程 this ===> window
2、全局作用域里 this ===> window
3、call/apply
可以改变函数运行时this
的指向
4、谁调用的this
,this
就指向谁。
5、构造函数中的this
,指的是实例对象。
var name = 11;
var a = {
name : 22,
say : function(){
console.log(this.name);
}
}
var test = a.say;
test(); //11
a.say(); //22
var b = {
name : 33,
say : function( fun ){
fun();
}
}
b.say(a.say); //11
b.say = a.say;
b.say(); //33
arguments
JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments
对象的由来。
需要注意的是,虽然arguments
很像数组,但它是一个对象
。
正常模式
下,arguments对象可以在运行时修改。
严格模式
下,arguments对象是一个只读对象,修改它是无效的,但不会报错。
1)arguments.callee
arguments对象带有一个callee属性,返回它所对应的原函数。
var f = function () {
console.log(arguments.callee === f);
}
f() // true
可以通过arguments.callee
,达到调用函数自身的目的。这个属性在严格模式里面是禁用的
,因此不建议使用。
2)arguments.length
通过arguments对象的length属性
,可以判断函数调用时到底带几个参数。
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0