(五)JavaScript 对象、包装对象、原型、原型链、instanceof、call/apply、继承、模块、深度克隆、this、arguments

对象属性的操作

访问对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

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);

所谓“包装对象”,就是分别与数值、字符串、布尔值相对应的NumberStringBoolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。
包装对象的最大目的,首先是使得 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

callapply 都是改变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、谁调用的thisthis就指向谁。

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

猜你喜欢

转载自blog.csdn.net/weicy1510/article/details/82351034