JavaScript学习——进阶(续2)

继承

  • 传统形式——原型链:由于可以形成很长的链,所以会过多的继承链上的没用的属性
  • 借用构造函数——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>

在这里插入图片描述

发布了53 篇原创文章 · 获赞 33 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/jy_z11121/article/details/99202268