总预览
1.变量的内存的存储结构
2.值类型与引用类型的赋值
3.函数的参数以及arguments
4.队列数据结构和栈的数据结构
5.JavaScript事件循环机制,同步以及异步
6.执行上下文相关的概念
7.作用域链
8.JS函数的作用域以及变量的提升
9.函数四种调用模式与this
10.函数的递归调用
1.变量的内存的存储结构
值类型(基本类型、简单类型):数值、字符串、布尔值、null、undefined。(副本,各是各的,彼此不影响)
引用类型(复杂类型):对象、数组、函数。 (给的是引用地址,任何一个变2个都会变化)
2.值类型和引用类型的赋值
3. 函数的参数以及arguments
参数匹配是从左向右进行匹配。如果实参个数少于形参,后面的参数对应赋值undefined。
实参的个数如果多于形参的个数,可以通过arguments访问
3.1 arguments
arguments:是一个类数组(有下标,可以通过下标来获取一些数据)
<script>
function max(a, b) {
console.log(arguments); //Arguments(2) [9, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 每个函数内部都可以直接访问arguments,里面就是存放着我们传递给函数的参数
// arguments 不是一个数组。它的属性:0、1、2...
// arguments有一个length属性,属性值就是传递实参的个数。
for(var i = 0; i < arguments.length; i++) {
console.log(arguments[i]); // 打印所有的参数。//9,10
}
if( a > b ) {
return a;
} else {
return b;
}
}
console.log(max.length); // 函数的length属性是指形参的个数。
var t = max(9,10);
console.log(t);//10
(2)使用arguments来封装一个max,来演示max内部实现机制
// 函数在未定义之前就可以直接使用,因为声明的函数会提示到代码的顶部。
var m = myMax(10, 9, 2, 33, 22, 18);
console.log(m); // m => 33
// 要你实现max方法,可以接受任意多个参数。返回这些参数中最大的那个值?
function myMax() {
// 如果调用函数的地方没有传递参数,那么直接返回NaN
if(arguments.length <=0 ) {
return NaN; // return语句执行,当前函数立即结束。
}
var max = arguments[0];
// 获取实参:使用arguments,它是一个类数组。length属性是实参的个数。
for(var i = 1; i < arguments.length; i++) {
if(arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
</script>
结果:
3.2JavaScript中通过arguments实现函数重载
JavaScript中函数没有重载,因为后面定义的函数会覆盖掉前面名字相同的函数,重载的定义就是函数名相同,根据参数的不同执行不同的函数
参考示例:
创建一个矩形的类型。构造函数接受一个参数,返回一个正方形,接受两个参数返回一个矩形。
<script>
// arguments模拟函数重载
// 创建一个矩形的类型。构造函数接受一个参数,返回一个正方形,接受两个参数返回一个矩形。
function React() {
//如果一个参数,返回一个正方形
if(arguments.length == 1) {
this.width = arguments[0];
this.height = arguments[0];
}
//如果两个参数,返回一个矩形
if(arguments.length > 1) {
this.width = arguments[0];
this.height = arguments[1];
}
// 由于跟原型上的toString方法重名,那么会覆盖Object原型上的toString方法
this.toString = function() {
// console.log('width: ' + this.width + ' height: ' + this.height)
return 'width: ' + this.width + ' height: ' + this.height;
}
}
var r1 = new React(10);
console.log(r1.toString());
var r2 = new React(10, 9);
console.log(r2.toString());
</script>
3.3如果参数多于4个,那么开发人员很难记忆,最好将参数封装成对象来接受,对象的属性是无序的,可以方便开发人员使用。
<script>
// 函数参数的封装
// 一个函数:封装一个矩形。矩形:x y坐标, width、 height、背景色、文字信息、文字的坐标、文字字体颜色
// 全部用形参来处理矩形的参数
function rect(x, y, width, height, bgColor, text, text_color, text_x, text_y) {
}
// 如果函数的形参非常多问题:
// 1、开发人员很难记忆形参的具体情况
// 2、传递参数的时候,如果顺序不小心写错了,那么就会导致函数内部出现错误。
// 3、编写代码不方便。
// 解决办法:把这些形参封装成一个对象进行传递。
function rect2(rectObj) {
// 拿到矩形的x、y坐标
console.log(rectObj.x + ' ' + rectObj.y);
}
// rectObj
var rectObj = {
x: 19,
y: 20,
width: 200,
height: 300,
bgColor: '#ccc',
text: 'laoma'
};
rect2(rectObj);
</script>
4.队列数据结构和栈的数据结构
队列先进先出,后进后出(一头进另一头出)
栈先进后出(就只有一个头控制着进和出))
5.JavaScript事件循环机制以及同步和异步
JavaScript是单线程 的,所有同一时间只能执行一段代码。JavaScript中的代码都是排队执行,不会同步执行多个任务JavaScript执行线程和UI线程是互斥的。
浏览器:
JavaScript执行线程:负责执行js代码
UI线程:负责UI展示,负责展示给用户看到的页面。
JavaScript事件循环线程。
5.1 同步和异步
JavaScript中的任务分为同步任务和异步任务。
同步任务:一般的赋值操作、循环、分支语句等都是同步的任务。
异步任务:DOM事件、Ajax、BOM的一些API等
3执行到一半的时候又回去了,是js事件让其回到队列任务里面的。
6.执行上下文相关的概念
6.1执行环境上下文
EC:函数执行环境(或执行上下文),Execution Context
ECS:执行环境栈,Execution Context Stack
VO变量对象(Variable object)=》(全局的执行环境)是说JS的执行上下文中都有个对象用来存放执行上下文中可被访问但是不能被delete的函数标示符、形参、变量声明等。它们会被挂在这个对象上,对象的属性对应它们的名字对象属性的值对应它们的值但这个对象是规范上或者说是引擎实现上的不可在JS环境中访问到活动对象
AO激活对象(Activation object)==》(函数内部的执行环境):有了变量对象存每个上下文中的东西,但是它什么时候能被访问到呢?就是每进入一个执行上下文时,这个执行上下文儿中的变量对象就被激活,也就是该上下文中的函数标示符、形参、变量声明等就可以被访问到了
scope chain:作用域链
6.2执行环境栈
JavaScript执行在单线程上,所有的代码都是排队执行
一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
浏览器的Js执行引擎总是访问栈顶的执行上下文。
全局上下文只有唯一的一个,它在浏览器关闭时出栈
<script>
// 代码执行之前的时候,就会立即创建一个全局的执行上下文Global Excution Context
// 创建完了全局的执行上下文之后,把全局执行上下文压入 执行环境栈中。
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
f3();
}
function f3() {
console.log('f3');
f4();
}
function f4() {
console.log('f4');
}
f1(); // 代码进入执行f1函数,函数内的代码在执行之前,js执行引擎立即创建一个f1的执行环节 f1 Excution Context,立即把这个执行环境压入到 执行环境栈中ecs。
// f1()函数执行完成之后,从执行环境栈中弹出 f1的执行上下文。
// f2()函数执行之前,创建f2的执行上下文。亚栈到 执行环境栈中(ecs)
f2(); // f2() →f3()→f4();
// 因为f2调用了f3函数。f3函数执行之前也创建了一个f3的执行上下文,并压栈到 执行环境栈中。
// f3函数调用了f4函数,创建f4的ec。 并把f4的ec 压入到ecs中。
// f4执行完成,f4的ec出栈。
// f3执行完成,f3的ec出栈
// f2执行完成,f2的ec出栈
</script>
图示:
6.3执行上下文的生命周期
总的的生命周期:创建→执行→出栈等待销毁
<title>函数上下文的创建</title>
</head>
<body>
<script>
// 做变量声明
var a1 = 19,
a2 = 20,
a3 = 'sss',
b1 = { name: 'laoma'};
// 函数调用
a1 = f1(a1, a2); // 函数可以先调用后声明?
// 函数声明
function f1(a, b) {
// f1函数的执行上下文:
// 第一步:扫描参数, a = 19, b = 20
// 第二步: 扫描函数声明 f2 = function(){}
// 第三步: 扫描变量声明 t=undefined, m = undefined, i= undefined
var t = 0,
m = 10;
for(var i = 0; i < a; i++) {
console.log(i);
}
function f2() {
console.log(f2);
}
return a + b;
}
</script>
7.作用域链
作用域链是一个数组
作用域链是控制变量作用域的有序访问的js内部实现。
作用域链存储在函数的执行上下文中,作用域链中存放的是执行环境中的vo或者ao。
当前函数的作用域对象都是在最前端,而且全局的在最末端
变量(标识符)的搜索都是从作用域链的最前端向后搜索,直到全局作用域 ,标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。
8.JS函数的作用域以及变量的提升
JS是没有块级作用域的,只有函数作用域和全局作用域。for循环内部定义的变量是函数级别的作用域。所以for里面定义的变量整个函数里面都是可以访问到的
for循环、while循环中定义的变量的作用域是函数级别的作用域
C、C#、C++、Java: 在for循环内部定义的变量,只能在for循环内部访问。但是在js中,因为没有块级作用域,所以在for循环内部定义的变量在整个所在的函数内部是可以访问的。
<script>
// 没有块级作用域
function f1() {
var t1 = 9; // 只能在f1函数内部访问到。
for(var i = 0; i < 10; i++) { // i变量在for循环中定义的。
console.log(i); // java/c#/c/c++
}
console.log(i);
}
// 因为js没有块级作用域,所以for内定义的变量可以在整个函数内都是可以访问到的。
f1()
// console.log(t1); //外面访问不到f1函数内部的变量,所以会报错。
</script>
// 特殊情况:变量声明和函数声明 同时拥有一个名字的时候,函数优先级高。
console.log(b); // b=> function
var b = 9; // 因为js是动态语言,把b重新赋值9,把之前的function覆盖。
function b() {
}
console.log(b); // b=>9
8.1JS常见的变量的提升
<script>
// 在浏览器中全局对象就是window
//在全局作用域中声明的变量和函数声明都会作为window的属性存在。
//案例1
var a = 9;
function b() {
console.log(a);//9
}
console.log('a' in window);//true
console.log(window.a);//9
console.log(window['a']);//9
console.log(window.b());//undefined,没写return的时候就是undefined,//9,undefined
// 案例2
// ("a" in window) => true
if (!!("a" in window)) {
var a = 1; // 定义了一个a变量,a =undefined,js没有块级作用域,又因为 // ("a" in window) => true,所以a为全局变量
}
console.log(a);//1
案例2的对比,所以if,for,while定义的变量的作用域的范围都是函数内有效,
function s(){
if (!("a" in window)) {
var a = 1; // 定义了一个a变量,a =undefined,js没有块级作用域
}
console.log(a);//1
}
s()
console.log(a);//Uncaught ReferenceError: a is not defined
// 案例3
console.log(a); // a => function a() {}
var a = 20; // 对a进行重新赋值
console.log(a); // a => 20
function a() { // 创建global EC的VO的时候,函数的声明优先级要高于变量的声明。
}
// 案例4:
f();
console.log('b =' + b); // 9
console.log('c =' + c); // 9
console.log(a); // 出一个异常错误,使用未定义的变量a
function f() {
var a = b = c = 9; // a是局部变量,b\c是全局变量。
// var a = 9, b = 9, c = 9; //定义三个局部变量
console.log(a); // 9
console.log(b); // 9
console.log(c); // 9
}
// 案例5:
f();
function f() {
for(var k = 0; k < 10; k++) {
console.log(k);// 0-9
}
console.log(k);// 10,js没有块级作用域
}
</script>
9.函数四种调用模式与this
示例1:构造器调用模式和方法调用模式
<script>
// 构造器调用模式 . 构造函数
// 关键字是: new
function Cat() {
// 第一步:创建一个空对象(新对象)
// 第二步:给函数上下文赋值 新对象 this = 新对象。
this.age = 19;
this.name = "cat"; // 在构造函数内部定义的this的属性。
this.run = function() {
console.log(this.name + ' run...');
}
// 如果构造函数没有返回值。那么就返回 this(新对象);
// return 3; // 即使有返回值,如果返回值类型是简单类型,那么会被忽略。2222run...
// return null;//被忽略2222run...
// 如果返回值是一个引用类型(去掉null)那么新对象就会被抛弃,把这个引用类型
// 返回。
return {
name: 999,
run: function() {
console.log('自己返回的{}');//自己返回的{},上面的console.log(this.name + ' run...');会被抛弃
}
};// return了一个对象的字面量,return了一个引用类型。
}
var cat = new Cat();//构造函数掉用模式,如更此时上面有this的话,这个this就是返回的cat对象
// 如果使用new关键字+构造函数执行的话触发了构造函数执行模式。
cat.age = 20;
cat.name = '2222';
cat.run();// 方法调用模式。
</script>
示例2:函数调用模式
<script>
//函数调用模式
//示例1
function f(a, b) {
console.log(a +' ' + b);
this.a = 19; // this === window//true即window.a
console.log('a' in window);//true
console.log(this); // window,全局对象,严格模式:undefined
}
f(2, 3); // 直接调用函数: f();函数调用模式。
//示例2
function Dog() {
this.age = 19;
console.log(this);
}
Dog(); // 函数执行模式 => window
var d = new Dog(); // 构造函数执行模式 => d对象
</script>
示例2结果:
示例3:apply和call方法借用模式
<script>
// call和apply调用模式
function sum(a, b) {
console.log(this);
return a + b;
}
sum(1, 2); // this=> window
var t = {
name: 'laoma'
};
var m = sum.call(t, 2,3,9,10);
console.log(m); // => 5
var m2 = sum.apply(t, [1,2]);//apply可以打散数组
console.log(m2); // => 3
// 如果你传递的第一个参数是简单类型(值类型,原始类型),那么都会出现一些变化
// 如果传第一个参数是:null、undefined=> window
// 如果是number、string、boolean转成对应的包装类型
sum.call(null, 3,5);
sum.call(undefined, 3,5);
sum.call(3, 3,5);
sum.call("3", 3,5);
sum.call(true, 3,5);
</script>
结果:
4.函数四种调用模式案例
1.定义按钮类,要求按钮类的构造函数可以接受参数初始化按钮的宽度、高度、坐标xy
2.借用Math的min方法实现求数组[2,9,33]中的最小值
3.把类数组转换成真正的数组。
var t = {}; t[0] = 1; t[1] = true; t.length = 2;
4.判断代码输出的内容:
function Dog() {
console.log(this);
}
Dog();// 函数调用模式
var d = new Dog(); //构造函数调用模式 this == d,d是一个对象
Dog.call(null); // 借用调用模式 window
<script>
// 1.定义按钮类,要求按钮类的构造函数可以接受参数初始化按钮的宽度、高度、坐标xy
function Btn(width, height, x, y) {
// 构造函数内部初始化值。
this.width = width; //给this对象上的width属性赋值 width参数的参数值。
this.height = height;
this.px = x;
this.py = y;
}
var b = new Btn(100, 100, 30, 30);
// 2、借用Math的min方法实现求数组[2,9,33]中的最小值
// var m = Math.min(10, 29, 3);
// console.log(m);
var m = Math.min.apply(null, [2, 9, 33]);
console.log(m);
// 3.把类数组转换成真正的数组。
var t = {}; // 类数组,跟数组很相似,但不是数组。
t[0] = 1;
t[1] = true;
t[2] = 'laoma';
t.length = 3;
var m = [1, 2, 3];
m.slice();// 如果什么都不传,默认是从0索引开始截取到数组最后。
// 第一个参数是:截取开始的位置,startIndex
// 第二个参数是: 截取结束的位置+1。endeIndex
// 如果我借用 数组的slice方法,然后把this指向到t对象,那么slice方法就会返回t对象的
// 对应的数组。
var k = Array.prototype.slice.call(t,0);
//0是slice的起始参数
// 通过一个slice方法可以把类数组转成真正的数组。
console.log(k);//(3) [1, true, "laoma"]
</script>
10.函数的递归调用
<script>
// 18-函数的递归调用.html
// 求1 到100的和
// 常规的写法
// var sum = 0,
// n = 100;
// for(var i = 1; i <= n; i++) {
// sum += i;
// }
// console.log(sum);
// 用递归实现。函数调用函数自己。
function sumNum(num) {
// 递归一定要有个结束自己调用自己的出口。
if(num <= 1) {
return num; // 函数只要执行到return当前函数立即结束执行。折回的出口
}
// 实现函数调用函数自己。
return sumNum(num -1) + num;
// arguments.callee在严格模式下回报错,所以不要这么用
// return arguments.callee(num -1) + num;
}
console.log( sumNum(100) );//5050
</script>
函数表达式方式定义的时候,还可以用命名函数表达式
例如:
var f = function s(){};// 在s函数内部,可以直接使用s变量。
案例:
求1到100的和,请使用递归。
求num的阶乘,用递归实现。
求f(n)的斐波那契数列的值。 f(n) = f(n-1) + f(n-2); n>=3
<script>
// 19-函数的递归案例.html
// var f = function() {
// console.log('sss');
// };
// f(); // 调用函数表达式执行。
// // 函数命名表达式。fun相当于在函数内部添加一变量fun,fun指向函数自身
// // fun === arguments.callee
// // fun的作用域在 函数内部。不会影响函数外面的声明的参数或者函数。
// var m = function fun() {
// console.log('fun');
// // fun();
// };
// m();
// 求num的阶乘,用递归实现。
// function factorial(num) {
// // 定义一个出口。
// if(num == 1) {
// return 1;
// }
// return num * factorial(num -1);
// }
// console.log(factorial(4));
// 求f(n)的斐波那契数列的值。 f(n) = f(n-1) + f(n-2); n>=2
// f(0) = 0, f(1) = 1 f(2) = 1, f(3) = 2....f(4)= 3 f(5) = 5 f(6)= 8)
function fibonacci(n) {
if(n == 1) {
return 1;
}
if(n == 0) {
return 0;
}
return fibonacci(n-1) + fibonacci(n-2);
}
console.log(fibonacci(6));
</script>