JavaScript学习笔记九

94 阅读15分钟

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