Nine
一:构造函数之 this
代码范例一:
function Car(){
this.color = 'black';
}
问:上面的代码中,如果不实例化构造函数,
this,是否存在?答:存在。
问:为什么?
答:当函数被定义时,
this存储在 GO 对象里面,指向window对象; 当函数被执行时,
this同时也存储在 AO 对象里,同样也是指向window对象。
代码范例二:
function Car(){
//this.color = 'black';
window.color = 'black';
console.log(window.color);
}
Car();
//Print Result: black
验证代码范例一中的解答:当不实例化构造函数时,
this指向的是window对象。
代码范例三:
function Car(){
this.color = 'black';
}
var car = new Car();
console.log(car.color);
代码范例三
当实例化构造函数时,
this的指向将会发生改变,它将指向实例化后的对象(car)。当
console.log(car.color)时,访问的是car对象中的color属性。
代码范例四:
function Car(color){ //传参
this.color = color; //属性值指向形参,键值对(属性名 --- 属性值)
}
var car1 = new Car('black');//通过使用 new 关键字实例化 Car 构造函数并将结果赋值给全局变量 car1 ,同时传实参 black
var car2 = new Car('red');//上面的解释一堆,其实就是 自定义创建一个对象
console.log(car1.color);// black
console.log(car2.color);// red
通过代码范例四中的打印结果得出如下结论:
1:不管实例化多少个对象,每个对象都是相互独立且互不干扰;
2:当实例化构造函数后,构造函数中的
this指向的一定是实例化后对象的本身,而不是构造函数本身;
代码范例五:
//GO = {
// Car: (function),
// car1: {
// color: 'black',
// brand: 'Benz'
// }
//}
//AO = {
// this: {
// color: color,
// brand: brand
// }
//}
function Car(color,brand){
//this = {
// color: color,
// brand: brand
//}
this.color = color;
this.brand = brand;
//return this
}
var car1 = new Car('black','Benz');
console.log(car1.color);// black
当实例化构造函数时,就相当于普通函数执行时一个样。
1:页面加载时,生成
GO对象,其中就包含有Car函数以及car1变量;2:当实例化构造函数时(函数执行时),生成
AO对象;3:且 JavaScript 引擎在 25 行 发现关键字
new之后,就认为这是在实例化对象, 并立即在AO中默认生成一个this对象;4:同时 JavaScript 引擎在
Car构造函数中 隐式 的写入一个this对象(此时还没有写入对象的键值对);5:然后 JavaScript 引擎继续向下走,走到 21-22 行后,发现两个 参数赋值语句 ;
6:就立即在
AO写入相应的键值对,且同时在Car构造函数中的 17-20 行 隐式写入键值对;7:JavaScript 引擎 在走完
Car构造函数代码后,会隐式的在 23 行写入一句return this,把这个 this 对象返回出去;8:代码的第 25 行,通过一个变量
car1来接受返回出来的 this 对象,同时GO中就会相应的生成一个car1对象,这就是实例化构造函数;9:代码的第 26 行,在控制台打印
car1.color,也就是在GO中查找相应的键值对(属性与值),并打印出来。总结: 自定义构造函数与普通函数,区别在于关键字
new,而new的存在意义,就是在函数内部创建一个对象,让this指向这个新的对象。
代码范例六:
//用普通函数来实现自定义构造函数
//同时验证 代码范例五 中的隐式结论
function car(color, brand){
var me = {};
me.color = color;
me.brand = brand;
return me;
}
var car1 = car('black', 'Benz');
console.log(car1);//Print Result: { color: 'black', brand: 'Benz' }
console.log(car1.color, car1.brand);//Print Result: black Benz
通过在控制台中打印的结果来看,普通函数也可以实现自定义构造函数的大部分功能。
代码范例七:
function Car(){
this.color = 'black';
this.brand = 'Benz';
//return 123; //Print Result:自动忽略Number 123,代码的17行打印还是 car1.color 属性值 black;
//return 'abc'; //Print Result:自动忽略String abc,代码的17行打印还是 car1.color 属性值 black;
//return flash; //Print Result:自动忽略Boolen false,代码的17行打印还是 car1.color 属性值 black;
//以上为原始值
//以下为引用值
//return {}; //Print Result:忽略 car1.color 属性值 black,打印出来的是 undefined,如果是car1,则打印的是空对象;
//return []; //Print Result:忽略 car1.color 属性值 black,打印出来的是 undefined,如果是car1,则打印的是空数组;
//return function(){}; //Print Result:忽略 car1.color 属性值 black,打印出来的是 undefined,如果是car1,则打印的是函数体;
//return function test(){};//Print Result:忽略 car1.color 属性值 black,打印出来的是 undefined,如果是car1,则打印的是函数体;
}
var car1 = new Car();
console.log(car1.color);
通过代码范例七中的注释,由此可知:
1:在自定义构造函数中,强行加上
return指令,如果后面跟着的是原始值,则不改变实例化后对象的内容;2:如果跟着的是引用值,则将改变实例化后对象的内容,且改变的内容为
return指令出去的引用值。
二:包装类
在 JavaScript 中,包装类是一种特殊的对象类型,用于将原始类型的值( Number --- 数字、String --- 字符串、Boolean --- 布尔值)转换为对象。这种转换
会根据使用场景不同会有所变化(临时性)。但,Undefined 和 Null 没有包装类。
三个包装类作用有二:
1:使我们可以像操作对象一样来使用原始数据类型,不建议使用,容易出现不可预知的问题(这个是在网上查到的,目前学习阶段还看不出来);
2:系统使用(如浏览器、Node等),当我们调用字符串的方法时系统会临时通过包装类来创建对象,然后通过对象调用方法,完成之后就会立即销毁。
2.1 原始值是没有自己的属性与方法。
原始值 --- Number(数字类型)、String(字符串类型)、Boolean(布尔类型)是没有自己的属性与方法。
个人理解:在 JavaScript 中,除了原始类型之外,所有的类型都可以有自己的属性与方法。
代码范例一:验证上述内容
//假设:原始值有自己的属性与方法。
//那么就一定可以设置属性(键值对),方法。
//当然,这个是假设,按照目前学习的进度以及知识储备而言,应该是不行,我们来测试下。
//同时类似字符串的 .length 方法,这种系统内置的,在这个大章节的其它小章节内在讨论。
var a = 1; //数字
var b = 'abc';//字符串
//布尔类型就不做测试了
a.name = 'number';//设置属性
console.log(a.name);//Print Result:undefined
a.say = function(){
console.log('hello world');
}
a.say();//Print Result:a.say is not function 报错
b.name = 'string';//设置属性
console.log(b.name);//Print Result:undefined
b.say = function(){
console.log('hello world');
}
b.say();;//Print Result:b.say is not function 报错
//Explanation:由此可见,上述内容是正确的。
2.2 Number、String、Boolean类型一定是原始值吗?
问:Number、String、Boolean类型一定是原始值吗?
答:不是。
问:为什么?
答:在 JavaScript 中,Number、String、Boolean 等可以是原始值,也可以是对象值。具体展开如下:
原始值,是存储在栈内存中,不包含任何引用。在 JavaScript 中,原始值包含 数字、字符串、布尔值、undefined、null;
对象值,是存储在堆内存中的值,包含一个引用指向堆内存中的实际值。在 JavaScript 中,对象值包括数组、函数和其他复杂对象。
总结:所以在大多数情况下,Number、String、Boolean都是原始值,但并不绝对。
代码范例二:验证上述内容
//数字(Number)一定是原始值吗?
var a = 1; //原始值
console.log(a);//打印的也是原始值
//这段代码的意思:通过 关键字 new 以及 Number 方法,实例化变量(原始值) a 转换为对象,并赋值给了 b。
var b = new Number(a);//包装类
console.log(b);//值改变为对象值,所以数字不一定是原始值。
//Print Result:Number{1}
//Explanation:由此可见,上述内容是正确的。其它类型的就不一一测试了。
2.3 为什么说,包装类会根据使用场景不同会有所变化(临时性)?
场景1:在进行数学运算时,包装类实例又会重新返回为原始类型参与运算,运算完成后,又重新再次返回为对象;
场景2:系统调用时( JavaScript 引擎),当我们调用字符串的方法时系统会临时通过包装类来创建对象,然后通过对象调用方法,完成之后就会立即销毁。
代码范例三:验证上述内容
//场景1只做数字类型的测试,其它类型就不一一测试。
var a = 3; //数字
var b = new Number(a); //包装类
console.log(b);//Print Result:Number{3}
var d = b + 1; //将 b 进行加法运算
console.log(d); //Print Result:4
//Explanation:被包装类实例进行转换为对象后,在进行加法运算时,又重新返回为原始值来参与进行运算。
console.log(b); //Print Result:Number{3}
//Explanation:参与加法运算完成后,又重新再次返回为对象。
//场景2
var str = 'abc';
console.log(str.length)//Print Result:3
//Explanation:JavaScript 引擎执行到第15行的 str.length 时:
//1:创建一个 String 实例;
//2:调用实例特定的方法;
//3:销毁实例。
2.4 包装类种类
包装类一共有三种,具体如下:
new Number()--- 对应 数字
new String()--- 对应 字符串
new Boolean()--- 对应 布尔值
代码范例四:演示三种包装类
//new Number() --- 对应 数字
var num = 123;
var newNum = new Number(num);
newNum.name = '321'; //添加属性
newNum.say = function(){ // 添加方法
console.log('hello world');
}
console.log(newNum);//打印包装类实例化后的对象
//Print Result:Number {123, name: '321', say: ƒ}
//new String() --- 对应 字符串
var str = 'abc';
var newStr = new String(str);
newStr.name = 'cbd'; //添加属性
newStr.say = function(){ // 添加方法
console.log('hello world');
}
console.log(newStr);//打印包装类实例化后的对象
//Print Result:String {'abc', name: 'cbd', say: ƒ}
//new Boolean() --- 对应 布尔值
var bool = true;
var newBool = new Boolean(bool);
newBool.name = false; //添加属性
newBool.say = function(){ // 添加方法
console.log('hello world');
}
console.log(newBool);//打印包装类实例化后的对象
//Print Result:Boolean {true, name: false, say: ƒ}
2.5 Undefined 和 Null 没有包装类。
通过对比打印结果,可以验证,Undefined 和 Null 是没有包装类。
也可以根据这两个特殊的类型判定是没有包装类,因为一个是未定义,一个是空。
代码范例五:验证上述内容
//以下代码演示,会有点迷惑性
//这里的undefined与null,是进行传参,而不是包装类实例化
var test = new Number(undefined);
console.log(test);//Print Result:Number {NaN}
var test = new Number(null);
console.log(test);//Print Result:Number {0}
var test = new String(undefined);
console.log(test);//Print Result:String {'undefined'}
var test = new String(null);
console.log(test);//Print Result:String {'null'}
//对比打印结果
var num = 123;
num.len = 23;
console.log(num.len );//Print Result:undefined
var str = 'abc';
str.len = 1;
console.log(str.len);//Print Result:undefined
console.log(undefined.length);//Print Result:直接报错
console.log(null.length);//Print Result:直接报错
//Explanation:由此可见,上述内容是正确的。
2.6 包装类深入
问1:在原始类型上,尝试用
.点语法添加一个属性,然后打印属性名为什么不报错且返回的是一个 undefined ?答1:因为 JavaScript 引擎会对其进行隐式包装类操作,所以不报错,而返回一个 undefined。
问2:在原始类型上,尝试用
.点语法添加一个方法为什么报错,而不是 undefined ?答2:在对其进行隐式包装类操作后,就立即销毁,在去调用方法名,当然会直接报错并提示该方法不是一个函数。
问3:为什么说原始值是没有属性和方法,但字符串类型可以调用
.length方法?答3:常规性解释为,因为系统自带的,也可以这样理解,但不严谨。正确的理解是,JavaScript 引擎帮我们进行了隐式的包装类操作。
代码范例六:验证上述内容
//问1:
var num = 223;
num.len = 3;
console.log(num.len);//Print Result:undefined
//Explanation:在 JavaScript 引擎执行到第3行, num.len = 3 时,会有如下步骤:
//1:进行隐式的转换为对应的 Number 包装类操作,并临时添加一个属性和赋值操作 new Number(123).len = 3;
//2:完成第一步操作后,需将相应的结果用一个容器(变量)进行接收(保存),但并没有这个容器,然后就会立即销毁这个临时添加的属性,delete 操作;
//以上结束后,因为不存在这个属性,所以在控制台打印就会直接返回 undefined,这步操作与打印不存在的对象属性名一样的效果。
//问2:
var num = 223;
num.say = function(){
console.log('hello world');
}
num.say();//Print Result:num.say is not a function
//Explanation:根据 问1 的解释,只是换成了方法而已。JavaScript 引擎销毁了方法名,当然报错并提示该方法不是一个函数。
//问3:
var b = 'abc';
console.log(b.length);//3
//new String().length
//Explanation:隐式的经过一层包装类的转换
console.log(new String());//通过打印结果可知,有一个 length,以及 Prototype(原型)中也有一个 length。
//综合汇总
var str = 'abc';
str.length = 1;
// new String(str).length = 1;
//delete
console.log(str.length);//隐式 new String(str).length
三:数组 .length 方法的新用法
两种使用
.length的方法:1:
arr.length返回数组的长度,从1开始计数;2:
arr.length = n截断数组,如数组长度为 6,且 n 为 3 ,则按照从左到右,取 3 个数组元素,并重新赋值给当前的数组; 如果 n 大于当前数组长度,则在当前数组中最末一位元素后,添加相应的数量的空元素;
如果 n 为 0,则将当前数组修改为空数组;
代码范例:
//n 小于 当前数组长度,且不等于 0
var arr = [1, 2, 3, 4, 5, 6]
arr.length = 3;
console.log(arr);//Print Result: (3)[1, 2, 3]
//n 大于 当前数组长度
var arr = [1, 2, 3, 4, 5, 6]
arr.length = 2;
console.log(arr);//Print Result: (8)[1, 2, 3, 4, 5, 6, 空属性 × 3]
arr[5] = 6, arr[6] = 7, arr[7] = 8; //也可以新增元素
console.log(arr);//Print Result: (8)[1, 2, 3, 4, 5, 6, 7, 8]
//n 等于0 时
var arr = [1, 2, 3, 4, 5, 6]
arr.length = 0;
console.log(arr);//Print Result:[]
四:练习
4.1 练习题一
//给出答案,并写出步骤或注释
var name = 'blackwolf'; //字符串
name += 10; //blackowlf10
var type = typeof(name); //字符串 'string'
if(type.length === 6){ //true
type.text = 'string'; //系统临时创建包装类,然后销毁,并不赋值
}
console.log(type.text) //undefined
//改造 使在控制台中打印 type.text 属性,值为 string
var name = 'youtube'; //字符串
name += 10; //youtube10
var type = new String(typeof(name)); //包装类
if(type.length === 6){ //true
type.text = 'string'; //添加属性与属性值
}
console.log(type.text) //string
4.2 练习题二
//请问 car 里面的brand 和 color 是什么?
function Car(brand, color){
this.brand = 'Benz'; //这里已经有对应的值,同时形参也没有进行赋值
this.color = 'red'; //同上
}
var car = new('Audo', 'black');
console.log(car);//Print Result:Benz red
//Explanation:这题主要是细心,大意的话,容易被实例化时的传参带进去。
//最终打印出什么,又为什么?
function Test(a, b, c){
var d = 1;
this.a = a;
this.b = b;
this.c = c;
function f(){
d++;
console.log(d);
}
this.g = f; //形成闭包,返回到全局
}
var test1 = new Test();
test1.g();//Print Result:2
test1.g();//Print Result:3
var test2 = new Test();
test2.g();//Print Result:2
//Explanation:这题感觉没什么好解释的,无非就是闭包,然后就是构造函数实例化了2个对象,分别调用了 f 函数,两个对象内的上下文相互独立,互不干扰。
4.3 练习题三
//给出答案,同时给出原因
var x = 1,
y = z = 0;
//x 1,y z 0;
function add(n){//同名函数
return n = n +1;
}
y =add(x);//4 这里执行的函数是下面的 add 函数
function add(n){//同名函数,因为是预编译作用,覆盖上方同名函数
return n = n + 3;
}
z = add(x);//4
console.log(x, y, z);//Print Result:1 4 4
//Explanation:第一次看到这个题,结果写成了 1 2 4,做任何事,还是要细心,因为预编译的作用,所以结果为 1 4 4
4.4 练习题四
//那个函数能输出1, 2, 3, 4, 5
function foo1(x){
console.log(arguments);
return x;
}
foo(1, 2, 3, 4, 5); //这个可以
function foo2(x){
console.log(arguments);
return x;
}(1, 2, 3, 4, 5);//这个不行
(function foo3(x){
console.log(arguments);
return x;
})(1, 2, 3, 4, 5);//这个可以
//Explanation:因为 foo2 的立即执行函数格式书写错误,其它两个,一个是函数声明,一个是立即执行函数,所以可以无误的打印结果。
4.5 练习题五
//打印的结果是什么,解释为什么结果是这个?
function b(x, y, a){ //形成于实参统一
a = 10; //形参已经在函数内部被赋值,所以实参忽略
console.log(arguments[2]); //虽然 arguments[2] 对应实参值 3,但是,arguments 与 形参时映射关系,形参一旦变化,arguments 就会发生改变
}
b(1, 2, 3);
//Explanation:这题是在考 arguments 与实参和形参的关系,理解这个,这题就很简单,答案已经在注释内,这里就不多废话。
五:额外的小知识 Ascii 与Uincode
Ascii 码:应该包含了,美式键盘上所有的字符,总共有255个字符,十进制,其中表1包含 0-127, 表2包含 128-255 ,每个字符为1个 byte(字节)。
Uincode 码:俗称万国码,其中就涵盖了Ascii码,除开 Ascii 码外每个字符为 2个 byte(字节)
以上为我能记住的,再细的话,就要去查表了。其实也无所谓,用到在去查,这个无需太过记忆。
在 JavaScript 中,可以使用方法
.charCodeAt()显示 Uincode 码,默认为十进制显示。
代码范例:
var str = '';
var num = '123';
var strNum = 'abc123';
var strNumCn = 'abc123中国';
function showCharCode(str){
if(str.length === 0){
return 0;
}
var byteCount = 0;
var showByteArr =[];
for(var i = 0; i < str.length; i++){
var charCode = str.charCodeAt(i);
showByteArr.push(str.charCodeAt(i));
if(charCode <= 255){
byteCount += 1;
}else{
byteCount += 2;
}
}
console.log(str + ',中每个的Uincode码为:' + showByteArr);
console.log(str + ',字节数为:' + byteCount);
}
showCharCode(str);