8.1 闭包
function fn1() {
function fn2() {
var b = 2;
console.log(a); // 1
}
var a = 1;
return fn2;
}
var c = 3;
var test = fn1();
test();
象中this的运用(在对象中,this指代当前的对象)
var teacher = {
name:'张三',
age:32,
sex:'male',
height:176,
weight:130,
teach:function() {
console.log('I am a teacher');
},
smoke:function() {
this.weight--; // 此时this指代当前对象teacher
console.log(this.weight);
},
eat:function() {
this.weight++; // 此时this指代当前对象teacher
console.log(this.weight);
}
}
对象实例(课堂人数统计)
var attendance = {
students:[],
total:6,
join:function(name) {
this.students.push(name);
// 判断当前人数是否到齐
if(this.students.length === this.total) {
console.log(name + '到课,学生已经到齐');
} else {
console.log(name + '到课,但学生未到齐');
}
},
leave:function(name) {
var idx = this.students.indexOf(name);
if(idx !== -1 ) {
this.students.splice(idex, 1);
}
console.log(name + '早退');
console.log(this.students);
},
classOver:function() {
this.students = [];
console.log('已经下课');
}
}
构造函数
对象字面量创建和系统自带的构造函数
// 系统自带的构造函数创建的对象和对象字面量创建的是相等的,没有任何区别,对象是通过构造函数创建的对象实例。
var obj = {};
var obj = new Object(); ```
## 9.1 构造函数中的this和this的原理
### 9.1.1 下面的例子中this是否存在?如果存在this的指向是什么?或者说this指代的是什么?
```JS
1.function Car() {
this.color = '蓝色';
}
// 此时的状态,Car函数只是声明,并没有定义,此时并不会产生this,因为函数根本就没有执行,函数体内的代码都没执行。所以this此时并不存在。
2.function Car() {
this.color = '蓝色';
}
Car();
// 调用Car函数,在学习闭包的时候,我们可以发现当函数执行的时候,产生的AO环境执行上下文中,this默认指向的是window对象,所以此时this.color相当于window.color;如果我们给Car函数传递参数,就相当于给window对象添加属性,如下:
3.function Car(color) {
this.color = color;
}
Car('蓝色');
console.log(window.color); // 蓝色
9.1.2 那通过new关键字实例化构造函数,构造函数内部的this指向是什么?或者说this指代的是什么?
function Car(opt) {
this.color = opt.color;
this.brand = opt.brand;
}
var c1 = new Car({
color:'蓝色',
brand:'benz'
});
console.log(c1); // { color:'蓝色', brand:'benz' };
console.log(window.color, window.brand); // undefined undefined
// 观察上面的代码,可以发现当使用关键字new的时候,实例化构造函数Car,构造函数中的this指向的是构造函数实例化出来的c1对象实例。此时new关键字改变了构造函数内部的this指向。
9.1.3 构造函数实例化出来的对象是同一个对象吗?二者互相影响吗?
function Car(opt) {
this.color = opt.color;
this.brand = opt.brand;
}
var c1 = new Car({
color:'蓝色',
brand:'benz'
})
var c2 = new Car({
color:'绿色',
brand:'BMW'
})
c1.color = '黄色';
console.log(c1); // { color : '黄色', brand : 'benz'};
console.log(c2); // { color : '绿色', brand : 'BMW'};
// 也就是说,通过构造函数实例化出来的对象都是互不影响的
1. 上面的例子说明 : 在函数没有被实例化时,函数体内部的this默认指向的是window对象。
2. 函数成为构造函数通过关键字new,new关键字改变构造函数中的this指向,将this指向window对象改为指向实例化出来的实例对象。
3. 通过构造函数实例化出来的实例对象,都是互相不影响的。
9.2 构造函数中this的原理,以及new关键字起到的作用?
9.2.1 new关键字的作用
function Car(color, brand) {
this.color = color; // this--->window
this.brand = brand;
// 隐式 return undefined
}
var c1 = Car('蓝色', 'benz');
console.log(c1); // undefined
// 观察上面代码, 如果没有通过new关键字进行实例化构造函数,那么构造函数和普通的函数并没有什么区别。因为函数没有设置返回值,所以系统自动隐式添加return undefined,所以c1变量在栈内存中存储的是undefined。
function Car(color, brand) {
// 隐式
// this = {
// }
// 进行赋值
this.color = color;
this.brand = brand;
// 隐式
// return this
}
var c1 = new Car('蓝色', 'benz');
console.log(c1); // { color:'蓝色', brand:'benz' };
// 如果构造函数通过new进行实例化,返回的是实例化之后实例是个对象,也就说明,当构造函数在被new实例化的时候,构造函数体内部系统自动创建一个this空对象,通过return将这个this对象返回出去,并且完成实例化构造函数。
// new关键字操作构造函数步骤:
1. 创建this = {} 空对象。将this指向从window改变为这个实例对象
2. 进行初始化属性和方法,对this进行属性赋值
3. 隐式返回this对象
// JS引擎解析GO AO
GO = {
Car:function Car(){},
c1:undefined,
--> this = {
color:'蓝色',
brand:'benz',
}
}
AO = {
this:{
color: '蓝色',
brand: 'benz',
},
color:undefined,
---> '蓝色'
brand:undefined,
---> 'benz'
}
9.2.2 不通过new关键字可以实现实例化的效果吗?
function Car(color, brand) {
var me = {};
me.color = color;
me.brand = brand;
return me;
}
var c1 = Car('蓝色', 'benz');
console.log(c1); // { color:'蓝色', brand:'benz' };
// 也就是说,不通过关键字new也可以实例化构造函数,创建实例对象。new关键字的作用无非就是创建this空对象,改变this的指向,返回this对象。
9.2.3 那么构造函数的返回值有什么注意事项吗?
function Car(color, brand) {
this.color = color;
this.brand = brand;
return '111';
// return 'str';
}
var c1 = new Car('蓝色', 'benz');
console.log(c1); // { color:'蓝色', brand:'benz' };
// 当你显式让构造函数返回原始值,此时构造函数的返回值不受影响,依旧返回this对象。
function Car(color, brand) {
this.color = color;
this.brand = brand;
return {};
// return [];
// return function(){};
}
var c1 = new Car('蓝色', 'benz');
console.log(c1); // {} [] function(){}
// 当你显式让构造函数返回引用类型的值,此时构造函数的返回值就是引用类型的值。
9.3 包装类
9.3.1 包装类存在哪些现象呢?为什么要叫包装类呢?包装类有哪些呢?
// 观察下面的现象:
var a = 1;
a.len = 3;
console.log(a.len); // undefined
var str = 'abc';
str.add = 'bcd';
console.log(str.add); // undefined
var fn = true;
fn.add = function(){};
console.log(fn); // undefined
// 我们都知道,原始值类型的数据是不可以设置属性和方法的。所以上面尝试去给原始值设置属性都失败了,最终导致输出的都是undefined。
// 再观察下面的代码:
var str = 'abc';
console.log(str.length); // 3
// 原始值不能设置属性和方法,那变量字符串str为什么能够通过str.length打印出字符串的长度呢?
有些人说:字符串系统自带length属性,可以打印出字符串的长度。这种说法是完全错误的。
其实是JavaScript中的包装类在作怪。
// 为什么会存在包装类呢?
var num = 1;
num.len = 3;
console.log(num.len);
// 对于上面的代码,JavaScript引擎在解析的时候,解析到num.len = 1。JS引擎发现你在尝试给原始类型设置属性和方法,但是原始类型是不能设置方法和属性,为了满足你的需求,它通过new Number(num)的方式将num原始值转换为数字对象形式的数据类型。所以num.len = 3其实被引擎转换为 new Number(num).len = 3;但是因为new Number(num).len没有变量接收,我们在后期无法打印,所以系统通过delete自动的将该属性删除。所以在最后打印的其实是console.log(new Number(num).len); 由于属性被删除了,所以打印undefined
// 通过上面的解释,我们就可以解释str的问题
var str = 'abc';
console.log(str.length); // 3
// 因为str是原始值,是字符串类型数据。按照常理来说,原始数据类型是没有属性和方法的,但是由于包装类的影响,在打印console.log(str.length);时,JS引擎尝试将str转换为引用类型的值 new String(str).length; 对于new String(str)来讲,作为一个对象,对象中有length的属性,所以相当于打印的是console.log(new String(str).length); // 3
9.3.2 数组可以通过length属性被截断,那么字符串可以吗?
var arr = [1, 2, 3];
arr.length = 2;
console.log(arr); // [1, 2]
// 也就是说,数组可以被length属性截断。那么字符串也有length属性,字符串可以被截断吗?
var str = 'abc';
str.length = 2;
console.log(str); // 'abc'
// 为啥还是'abc'呢?其实很简单,因为变量str是原始值,你对原始值str设置属性,str.length = 2。此时JS引擎将str转换为new String(str).length = 2;但是因为没有变量接收,导致属性被delete删除掉。而打印的时候console.log(new String(str).length);时,作为转换成对象的变量str来说,本身就有length属性,所以打印出3
9.4
// 说出原因和打印结果
var name = 'languiji';
name += 10;
var type = typeof(name);
if (type.length === 6) {
type.text = 'string';
}
console.log(type.text); // undefined
1. name += 10; // 字符串 + 数组 = 字符串拼接。 'languiji10'
2. typoef('languiji10'); // typeof判断数据类型,返回数据类型,以字符串的形式 string。
3. new String('string').length = 6; // 所以条件成立,进入if语句
4. type.text = 'string'; // 给原始值设置属性 'string'.text = 'string', new String('string').text = 'string',没有变量保存,自动删除。
5. console.log(type.text); // 已经被删除,undefined
// 如果非要让type.text能够储存值呢?
var type = new String(typeof(name));
---> new String('string'); { string } --> {
'0' : 's',
'1' : 't',
'2' : 'r',
'3' : 'i',
'4' : 'n',
'5' : 'g'
}
// 问Car实例的brand和color是什么?
function Car(brand, color) {
this.brand = 'benz';
this.color = 'red';
}
var car = new Car('Mazada', 'black');
console.log(car); // { barnd:'benz', color:'red'};
// 因为new函数实例化构造函数,隐式在构造函数中创建this和返回
function Car(brand, color) {
// 隐式
this = {
brand:'benz',
color:'red'
},
this.brand = 'benz';
this.color = 'red';
return this;
}
var car = new Car('Mazada', 'black'); // 虽然传递实参,但是构造函数内部没有赋值,没有this.brand = brand;
// 问t1.g(); t2.g();分别打印什么?
function Test(a, b, c) {
// 隐式
// this = {
// function f() {
// d++;
// console.log(d);
// }
//}
var d = 1;
this.a = a;
this.b = b;
this.c = c;
function f() {
d++;
console.log(d);
};
this.d = f;
// 隐式
// return this
}
var t1 = new Test();
t1.g(); // 2
t1.g(); // 3
var t2 = new Test();
t2.g(); // 2
// var t1 = new Test(); 通过new对构造函数进行实例化,此时new隐式的在构造函数中创建this空对象,然后将this添加属性之后返回出去,构造函数实例化的对象t1就是this。但是返回出来的this中的函数f其实是闭包,函数f()的作用域链捆绑着Test构造函数的AO环境,所以t1.g()第一次输出2,第二次输出3;var t2 = new Test();属于构造函数新实例化的对象t2,t1和t2互相不影响,所以t2.g()输出2;
// x, y, z分别输出什么?
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);
console.log(x, y, z); // 1 4 4
// 预编译
GO = {
x:undefined,
--> 1
y:undefined,
--> 0
--> 4
z:undefined,
--> 0
--> 4
add:function(n) { return n = n + 1 };
--> function(n) { return n = n + 3};
}
// 下列函数中哪些能够输出[1, 2, 3, 4, 5];
function foo(x) {
console.log(arguments);
return x;
}
foo(1, 2, 3, 4, 5);
// 函数foo()中arguments代表的实际参数,是个类数组的形式[1, 2, 3, 4, 5];所以直接可以输出
function foo(x) {
console.log(arguments);
return x;
}(1, 2, 3, 4, 5);
// 函数function foo(x){}是函数声明,如果函数声明后面跟上()执行符号则会报语法错误,必须是表达式+()执行符号才符合立即执行函数的功能。如果()内有参数,那么JS引擎会认为(参数)内部是表达式,前面是函数声明,后面是表达式,所以既不会执行函数也不会打印。
(function foo(x){
console.log(arguments);
return x;
})(1, 2, 3, 4, 5);
// 直接是立即执行函数其中的写法之一,立即执行函数也是可以传递参数的,而且传递参数和普通函数的规则一样。
function b(x, y, a) {
a = 10;
console.log(arguments[2]); // 10
}
b(1, 2, 3);
// 实际参数arguments[2]和形式参数a其实不是同一个东西,一个存在堆内存中,一个存在堆内存中。但是系统将形式参数和实际参数做了映射关系,二者随着对方的改变而改变。详细的情况可以看前面函数参数的那章知识。
1. str.charCodeAt(index)
参数:index一个大于等于0,小于字符串长度的整数。如果不是一个数值,则默认是0。如果小于0,等于或者大于字符串长度,返回NaN。
ASCII码表分为表1和表2:
表1: 0 - 127
表2: 128 - 255
ASCII码表里面的字符都是一个字节 1byte
UNICODE码涵盖ASCII码, 0 - 255 一个字节, 255位之后就是两个字节 2byte
// 写一个函数,接收任意一个字符串,算出这个字符串的总字节数。
function strCharCode(str) {
var totalCode = str.length;
// 循环字符串
for(var i = 0; i < str.length; i++) {
var item = str.charCodeAt(i);
if( item > 255 ) {
totalCode += 1;
}
}
return totalCode;
}
var a = '我爱你';
console.log(strCharCode(a));
// 先将字节数设置为字符串的长度,因为一个字符是1byte,如果在循环中发现某个字符是255位之后的,再将总字节数加1即可。