ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

JavaScript学习——进阶(续1)

2019-09-15 15:07:27  阅读:154  来源: 互联网

标签:function 构造函数 进阶 JavaScript 学习 Person var prototype name


对象

先了解一下“原始值”与“引用值”:

  • 引用值包括数组、函数、对象等。
  • 原始值存放在栈内存,引用值存放在堆内存
  • 原始值没有属性和方法,引用值有属性和方法。
  • 有些原始值有包装类,所以即使表示成原始值,也可以在调用其属性和方法时不报错,但其中有需要注意的问题(下面讲解)。

对象的创建

(1)对象字面量/对象直接量(PlainObjejct)

  • 语法:var obj = {…}
  • 实例:
<script type="text/javascript">
var student = {
	name: "zjy",
	id: "1001",
	eat : function() {	//普通的函数...可以有参数,也可以有返回值
		document.write('eateateateat...');
	}
}

//delete student.name;		//删除属性
//student.name = "zyy";		//修改属性
//student.sex = "女";		//增加属性
//obj.f = function () { ... }//增加方法
</script>
  • 控制台中查看对象:
    在这里插入图片描述
  • 控制台中操作对象:
    在这里插入图片描述
    (注意上面这幅图:变量在未定义时试图打印,将报错;但一个对象的属性未定义却试图打印时,结果为undefined!)
    在这里插入图片描述

(2) 使用构造函数创建对象

(js中的构造函数相当于java中的类)

系统自带的构造函数
var obj = new Object();
var str = new String();
var num = new Number();

//后续也可为这些对象增加属性或方法
自定义构造函数
  • 实例:
<script type="text/javascript">
	//自定义构造函数与普通函数在书写形式上没有任何区别;
	//我们人为规定,构造函数的函数名的首字母大写.
	function Car(color) {
		this.color = color;		//this.color 由参数决定
		this.name = "BMW";		//this.name 被写死了,但后续可进行修改
		this.height = "1400";
		this.lang = "4900";
		this.weight = 1000;
		this.run = function () {
			console.log('runrunrunrun...');
		}
	}
	var car = new Car();
	var car1 = new Car('black');
	car.name = "dazhong";
</script>

在这里插入图片描述

<script type="text/javascript">
	function Student(id, name, age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
</script>
  • 根据上面的实例,思考构造函数的执行过程?
  1. 执行步骤如下所示:
		function Car(color) {
//		Step1、在函数体最前面隐式的加上this = {}; 表示声明一个空对象(事实上,不是一个空对象,其内是有东西的,7.3.5节将讲解到): 
// 		var this = { };
//		Step2、执行this.xxx = xxx,使得属性具有对应的值:
//			color : "",
//			name: "",
//			...
			this.color = color;
			this.name = "BMW";
			this.height = "1400";
			this.lang = "4900";
			this.weight = 1000;
//			Step3、隐式的返回this:(s(即使捣乱性的自己随便return一个,最终系统也会强制return this))
//			return this;
		}
  1. 下面根据上面构造函数执行的过程,来自己写一个“更具内涵的”构造函数(当然,实际不需写的太有内涵,而直接写成上上面那样…)
function Person(name, height) {
	var that = {};
	that.name = name;
	that.height = height;
	return that;
}
var person = Person();
var person1 = Person('lily', 180);

仍可以在控制台中查看到:
在这里插入图片描述

原始值与对象(包装类)

  • 原始值:指直接var出来的变量值…如,var str = ‘abcd’
  • 对象:使用new创建出来的对象…如,var str = new String(‘abcd’)

对象里有属性和方法,而原始值只是作为个体存在,没有属性和方法,这在一定程度上造成了不便。于是产生了包装类。所谓包装类,是指把原始值包装成引用值…如下——分析下面的例子:原始值没有属性,但为何下面的程序没有报错,而输出“undefined”?

var num = 4;	
num.length = 3;	
console.log(num.length);

解释:上述代码实际上内部进行了隐式的包装类转换——原始值调用属性就会导致隐式转换成包装类。

var num = 4;	
num.length = 3;	//这里原始值调用属性,因此导致隐式转换成包装类:new Number(4).length = 3; 这样做后就创建了这样一个num.length变量,但创建完后就立即销毁这个new对象(因为留着的话系统不知道有啥用)
console.log(num.length);//这里的“num.length”又会导致隐式转换成包装类::new Number(4).length;并没有对其赋值,且与上面的“new Number(4).length = 3; ”无关(因为它已经被销毁了)。因此最终输出undefined

例题:

var str = "abcd";	
str.length = 3;	//new String('abcd').length = 3; 创建完后就删除
console.log(str);//结果:abcd
console.log(str.length);//结果:4。自动创建new  String('abcd')并访问字符串本来就有的length属性(用来计算字符串长度),计算得4。
var arr = [0, 1, 2, 3];
arr.length = 2;
console.log(arr);		//结果:0, 1
console.log(arr.length);//结果:2
//注意,这里arr数组本身就是对象,所以有属性,且具有截断操作的方法。本来arr.length为4,但我们的语句arr.length = 2;就使得数组的长度设置为2,即进行了截断操作。
var str = "abc";
str += 1;
var test = typeof(str);	//使得:test = "String"
if(test.length == 6) {	//原始值调用属性,隐式转换成包装类new String("String");长度确实为6,所以进入if语句
	test.sign = "...";	//test为原始值,所以会发生包装类的隐式转换:new String(test).sign = '...';执行完就销毁
}
document.write(test.sign);//这里又发生一次新的没有赋值的类型转换:new String(test).sign;最后打印结果为undefined,不是“...”。
//下面是个预编译考题,问:xyz最终分别是什么?
var x = 1, y = z = 0;
function add(n) {
	return n = n + 1;
}
y = add(x);
function add(n) {
	return n = n + 3;
}
z = add(x);
//我的错误答案:1 2 4
//正确答案:1 4 4 
//因为add(x)执行的都是第二个函数!!因为函数整体提升!先出现的函数被后面出现的同名的函数覆盖。。

原型

(1)定义

原型的实质是个对象,这个对象通过构造函数的prototype属性获得(注意:必须是构造函数名来调用,而不能使用利用构造函数创建出来的对象来调用prototype属性)。调用该属性,即获得这个构造函数的父类——称为“原型”。为这个“父类”(时刻注意它的实质也是个对象哦)添加一些属性或方法,那么该构造函数产生的对象将都可以继承这个"父类"的属性或方法(应参照下面的例子理解这句话)。
(PS. 一般的构造函数如果没有给其指定原型,那么其原型就是Object类。且所有构造函数的最高父类都是Object类。)

<script type="text/javascript">		
	function Person() {
		
	}
	Person.prototype.sex = '女'; //在Person的原型上的创建了sex属性
	var person1 = new Person();
	var person2 = new Person();
</script>

在这里插入图片描述
构造函数中并没有sex属性,实际上是从其父类Person.prototype上找到的sex属性。

  • 验证“原型的实质是个对象”:
    在这里插入图片描述
    从上面看出:原型中默认含有constructor函数,这个函数是构造函数,即, constructor相当于一个构造器:
    在这里插入图片描述> 这个constructor可以手动更改:
<script type="text/javascript">		
function Person() { }
Person.prototype.sex = '女'; 
function Car() { }
Person.prototype.constructor = Car;
</script>

在这里插入图片描述

(2)特点

  • 如果构造函数(时刻要注意,这里的“构造函数”相当于JAVA中的“类”)中有与原型相同的属性,则创造出来的对象的该属性的值与构造函数中的值一致,而不是与原型中的一致。(用已学过的Java中的知识解释:子类“重写”了父类的属性或方法,最后新建子类对象时,其值肯定与子类的一致…)

(3)应用

原型的实际应用是:将构造函数的对象的公共属性添加到原型上,而不再写于构造函数内。这样每次新建一个对象时,就可以使计算机少做一些工作了。

<script type="text/javascript">
	function Person(name, ID_Card) {
		this.name = name;
		this.ID_Card = ID_Card;
	}

// 利用原型创建公共属性的方式1:	
	Person.prototype.country = 'China';	//“用此构造函数创建出来的对象——人,都来自China”
	Person.prototype.eat = function() {
		console.log("eateateat...");
	}
// 	利用原型创建公共属性的方式2:	
//	Person.prototype = {
//		country : China;
//		eat : function() {
//			console.log("eateateat...");
//		}
//	}

	var person1 = new Person("zjy", 1001);
	var person2 = new Person("zyy", 1002);
</script>

(4)原型中属性或方法的增删改查操作

  • 改:通过代码或通过控制台重新为其属性赋值
<script type="text/javascript">
	function Person(name, ID_Card) {
		this.name = name;
		this.ID_Card = ID_Card;
	}
	Person.prototype.country = 'China';
	Person.prototype.country = 'US';//修改属性
	var person1 = new Person("zjy", 1001);
	var person2 = new Person("zyy", 1002);
</script>

在这里插入图片描述

  • 增:通过代码或通过控制台添加新的属性或方法
  • 删:通过代码或通过控制台删除属性或方法
    在这里插入图片描述

(5)__ proto __

如何找到一个对象的原型呢?通过对象的隐式属性__ proto __来查看/找到原型,此时,就可以使用构造函数构造出来的对象找到原形了,而不是使用构造函数来找到原型。

<script type = "text/javascript">
	Person.prototype = {
		name : 'a',
		sayName : function() {
			console.log(this.name);
		}
	}

	function Person() {
		this.name = "b";
	}

	var person = new Person();
</script>

在这里插入图片描述
【“__XXX __”命名方式的变量表示系统命名的隐式属性。另外,在写构造函数时,如果想时某个变量是“私有”的,则这样命名:“_XXX”(与Python类似)。】

NO1、

<script type = "text/javascript"> 	
function Person() {

	}		 	
var person = new Person(); 
</script>

在这里插入图片描述
在这里插入图片描述

  • 可以看出,__ proto __里含有原型prototype。

NO2、

<script type = "text/javascript"> 	
Person.prototype.name = 'abc'; 	
function Person() {

	}		 	
var person = new Person(); 
</script> 

在这里插入图片描述

  • __proto __从何而来?

《7.1.2.1 自定义构造函数》中讲解了构造函数的执行过程,说到“第一步是在构造函数内部首先隐式的加上var this={},表示声明一个空对象”,但其实并不是空的:

<script type = "text/javascript">
	Person.prototype.name = 'abc';
	function Person() {

		// var this = {
		// 	__proto__ : Person.prototype
		// };

	}		
	var person = new Person();
</script>

当访问对象中的属性时,如果发现没有自定义该属性,则通过__proto__指向的索引去找Person.prototype上有无该属性,即寻找系统定义的属性。因此,__proto__起一个连接的作用——把自定义属性与系统属性连接起来了。如,通过下面的方式访问name属性:
在这里插入图片描述

  • 可以通过下面的方式改变__proto __的值:

方式1、

<script type = "text/javascript"> 	
Person.prototype.name= 'abc'; 	
function Person() {

	}	

var obj = { 		
	name : "sunny" 	
}
var person = new Person(); </script> 

在这里插入图片描述
【注意:person.__proto__ = obj不可写成Person.__proto__ = obj,否则修改无效。】

方式2、

在代码中添加person.__proto__ = obj;(也不能写成Person.__proto__ = obj;)。

方式3、

<script type = "text/javascript">
Person.prototype.name = 'abc';
function Person() {

}	

var person = new Person();
Person.prototype.name = 'sunny';
</script>

请注意下面的代码段:

<script type = "text/javascript">
Person.prototype.name = 'abc';
function Person() {

}	

var person = new Person();
Person.prototype = { //修改Person.prototype,令其指向一个新对象(原来的Person.prototype指向“祖先”对象)
	name : 'sunny'
}
</script>
  • 结果:未能使name属性改变(仍是‘abc’)。
  • 对结果的解释:这里修改的是Person.prototype对象,而另一种修改修改的是Person.prototype对象里的name属性,这相当于:
<script type = "text/javascript">
var obj = {name : 'abc'};
var obj1 = obj;
obj = {name : 'sunny'};
</script>

最终并未使obj1的name属性改变(但obj的name属性已改变)。对于Person.prototype的修改:
在这里插入图片描述
因此,可以看成:

<script type = "text/javascript">
Person.prototype = {name : 'abc'};
__proto__ = Person.prototype;
Person.prototype = {name : 'sunny'};
</script>

并未使__proto__改变。

再看下面这段代码:

<script type = "text/javascript">
Person.prototype.name = 'abc';
function Person() {

}	

Person.prototype = { //修改Person.prototype,令其指向一个新对象(原来的Person.prototype指向“祖先”对象)
	name : 'sunny'
}

var person = new Person();
</script>

结果会使name属性变成‘sunny’,因为new之前已经修改了Person.prototype,所以对应的其中的name属性随之会变化(由‘abc’变为‘sunny’)。

原型链

(1)原型链的构成

原型链的构成相当于本对象的原型指向另一个对象,另一个对象的原型又指向另另个对象…最终,都是指向Object的原型:
在这里插入图片描述
如下代码,就构成了一个原型链:

<script type = "text/javascript">
	Grand.prototype.lastName = "ggg";
	function Grand() {

	}
	var grand = new Grand();

	Father.prototype = grand;
	function Father() {
		this.fff = 'fff';
		this.num = 10;
		this.fortune = {
			card1 : 'china bank'
		}
	}
	var father = new Father();

	Son.prototype = father;
	function Son() {
		this.sss = 'sss';
	}
	var son = new Son();
</script>

这个原型链是这样体现的:Son.prototype → Father. prototype → Grand.prototype → Object.prototype。如下示例,我们可以使用Son对象访问处于Grand.prototype中的lastName属性:
在这里插入图片描述

(2)原型链上属性的增删改查

可以查看“上辈儿”原型中的属性,但不能对其删除、修改、增加(只能自己对自己进行这样的操作)。有一特例:如果“上辈儿”原型中的某个属性是引用值,则可以对这一引用值进行修改、增加一类的操作。
在这里插入图片描述
但是,如果此时查看father.fortune,结果却是:
在这里插入图片描述
与之类似的,这样一个例子:
在这里插入图片描述
(PS. 图中son.num ++操作后的10为返回值…不表示结果)
也就是说,如果“修改”“上辈儿”原型中的属性,并没有真正修改…

下面看一个题目:

<script type = "text/javascript">
	Person.prototype = {
		name : 'a',
		sayName : function() {
			console.log(this.name);
		}
	}

	function Person() {
		this.name = "b";
	}

	var person = new Person();
</script>

没有this.name = "b";语句时:
在这里插入图片描述
this.name = "b";语句时:
在这里插入图片描述
在这里插入图片描述
这是因为this:this指代的是调用sayName()的对象,分别是person与Person.prototype,两对象中有不同的name值,所以有不同的输出。

下面再看一个题目:

<script type = "text/javascript">
	Person.prototype = {
		num : 100
	}

	function Person() {
		this.count = function () {
			this.num ++;
			//return undefined;
		}
	}

	var person = new Person();
</script>

在这里插入图片描述

注意:var obj = {...};(如var obj = ‘abc’;)与var obj = new Object();的obj都是有原型的,推荐使用前者.

(3)使用Object.create()创建对象

Object类中有create()函数,该函数有一个参数——引用对象或原型或null(不能是原始值);该函数的作用是用来创建对象

  • 参数为对象:
<script type = "text/javascript">
	var obj = {name : 'sunny', age : 12};
	var obj1 = Object.create(obj); //参数为对象
</script>

在这里插入图片描述

  • 参数为原型:
<script type = "text/javascript">
	Person.prototype = {
		num : 100
	}
	function Person() {

	}

	var person = Object.create(Person.prototype);
	// 参数为原型
	// 等价于var person = new Person();
</script>

在这里插入图片描述

  • 参数为null:
<script type = "text/javascript">
	var obj = Object.create(null); //参数为null
</script>

在这里插入图片描述
在这里插入图片描述

call()

作用:改变this指向

解释:

<script type = "text/javascript">
	function Person(name, age) {
		this.name = name;
		this.age = age;
	} 
	var person = new Person('zjy', 100);
	var obj = {

	}
	
	Person.call(obj, 'zjy', 200); 
	//该语句使得Person构造函数中所有的this都指的是obj
	//可以这样来理解:执行Person构造函数,构造函数的参数为call()的除第一个外的所有参数,这样就形成了一个新对象,然后这个新对象用obj(即call()的第一个参数)来接收

//	Person(); //实际上该语句是这样执行的: Person.call(); 这样执行的构造函数的this指向Window(请看下面的引用部分)
</script>

在这里插入图片描述

  • 【预编译时的this指向window】
<script type = "text/javascript">
	function test() {
		console.log(this);
	}
	test();
</script>

在这里插入图片描述
原因:因为函数执行时,预编译形成的执行期上下文AO的最先的工作是——找形参变量声明arguments→预编译期每一个函数体里的this都指向window。
如果使用了call(), 那么预编译时的“this指向widow”就变成“this指向指定的内容”了。如:

<script type = "text/javascript">
	function test() {
		console.log(this);
	}
	test.call({name : 'sunny', age : 12});
</script>

在这里插入图片描述


  • 【谁调用的函数,函数里的this就指向谁】
<script type = "text/javascript">
	var obj = {
		name : 'obj',
		say : function() {
			console.log(this.name);
		}
	}
	obj.say();
</script>

在这里插入图片描述
如果使用call(),将obj.say();改成obj.say.call(window);
在这里插入图片描述
显然,call()的作用就是改变this的指向

<script type = "text/javascript">
	var obj = {
		name : 'obj',
		say : function() {
			console.log(this.name);
		}
	}
		var fun = obj.say; //表示“拿出函数体”,即fun仅仅代表一个函数体。
		fun(); //函数自调用
</script>

在这里插入图片描述
如果使用call(),将fun();改成fun.call(obj);
在这里插入图片描述

应用——“借用别人的构造函数来实现自己的功能”

<script type = "text/javascript">
	function Person(name, age) {
		this.name = name;
		this.age = age;
	} 
	function Student(name, age, sex, major, tel) {
		Person.call(this, name, age, sex);
		this.major = major;
		this.tel = tel;
	}

	var stu = new Student('zjy', 200, '女', 'computer', '188'); 

</script>

call() 与 apply()

call() 与 apply()的作用都相同,不同之处在于两者传参列表:前者只能一个一个的传入构造函数的每个参数,而后者则需要以数组的形式传递一个构造函数所需的所有参数。

<script type = "text/javascript">
	function Wheel(size, style) {
		this.size = size;
		this.style = style;
	} 
	function Sit(quality, color) {
		this.quality = quality;
		this.color = color;
	}
	
	function Car(size, style, quality, color) {
		Wheel.apply(this, [size, style]);
		Sit.apply(this, [quality, color])
	}

	var car = new Car(111, 'cool', 'good', 'black'); 

</script>

总结call() 与 apply()的作用是改变this指向,不同之处在于传参列表。】

标签:function,构造函数,进阶,JavaScript,学习,Person,var,prototype,name
来源: https://blog.csdn.net/jy_z11121/article/details/99194549

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有