前端开发系列016-基础篇之Javascript面向对象(五)
一、严格模式
说明 在JavaScript
中有严格模式和非严格模式两种运行环境
关键字 “use strict”;
实现严格模式 只需要在脚本代码中添加上上述关键字即可。
关键字具体说明
(1) 注意区分大小写,必须全部都是小写的
(2) 注意空格,整个字符串总共10个字符
(3) 单引号和双引号无所谓,但是需要有引号
(4) 必须写在作用域的最顶部,注意其具体的位置
(5) 可以加分号,也可以不加,但是必须是一个字符串
注意 下面的关键字写法均是错误的
"USE strict";
" use strict ";
"("USE strict").toLowerCase();"
➥ 严格模式使用注意
> 〇 修正this的值
> ① 所有的变量必须使用var 关键字声明
> ② 不能使用`delete`关键字删除全局变量
> ③ 在对象中不允许有同名的属性
> ④ 函数的参数必须唯一(不能出现同名的参数)
> ⑤ `arguments`对象的行为不同,严格模式下和实参相对独立
> ⑥ 禁用了`argument.callee`和`caller函数`
> ⑦ 不能在if语句中声明函数
> ⑧ 禁止使用`eval`和`argument`作为标识符
> ⑨ 禁用了`with`语句和八进制字面量
001 所有的变量都必须使用var关键字声明
a = 10; //错误的演示
console.log(10);
002 不能使用delete关键字删除全局变量
//在非严格模式下,删除失败(静默失败) 失败了不吭声,严格模式下直接报错
var a = 10;
delete a;
console.log(a);
003 在对象中不允许有同名的属性
//在非严格模式下,会使用后面的属性赋值作为最终值,在严格模式下则直接报错
var obj = {
name:"张三",
name:"李四"
}
console.log(obj);
004 函数的参数必须唯一(不能出现同名的参数)
//在非严格模式下,如果函数在定义的时候,使用了多个同名的参数,则在函数内部形参的实际值为最后一个传入的实参值
//在严格模式下,直接报错
// function func(a,a,a) {
// console.log(a);
// console.log(arguments);
// }
function func(a,b,c) {
console.log(a);
console.log(arguments);
}
func(1,2,3);
005 arguments对象的行为不同
(1)严格模式下,在函数内部修改了对象的指向,对arguments的值不会产生影响
(2)在严格模式下,形参的值和arguments的值是相互独立的,在函数内部修改了形参的值对arguments不受影响
(3)在非严格模式下,修改了形参的值,arguments中的数据会跟着改变
//测试引用类型的值作为函数的参数
function funcName(obj) {
console.log(obj);
console.log(arguments[0]);
//在函数内部修改形参的值
obj = {age:20};
console.log(obj);
console.log(arguments[0]);
}
funcName({name:"张三"});
//测试基本类型数据作为函数的参数
function fun(str) {
console.log(str);
console.log(arguments[0]);
str = "hello";
console.log(arguments[0]);
}
fun("hi");
006 禁用了argument.callee和caller函数
argument.callee
是对函数自身的引用 argument.calller
是对调用函数的引用
var num = (function (n) {
if (n ==1)
{
return 1;
}
return arguments.callee(n-1) + n;
})(10);
console.log(num); //55
007 不能在if语句中声明函数
//如果在if语句中声明函数,则会产生语法错误
if (true)
{
console.log("________");
function demo() {
console.log("呵呵呵呵");
}
demo();
}
008 禁止使用eval和argument作为标识符
var eval = "测试的字符串";
console.log(eval);
var arguments = "参数列表";
console.log(arguments);
009 修正this的值
在严格模式下,函数this的值始终是指定的值,无论指定的是什么值
var name = "测试的name";
function demoTest() {
//在非严格模式下,打印出来的this为全局的对象window
console.log(this); //在严格模式下打印出来的this为undefined
}
demoTest();
010 禁用了with语句
var o = {name:"暂时干",age:20};
with(o)
{
name = "lisi";
age = 48
}
console.log(o);
11 禁用了八进制
//以0开头的数据常常引起混乱
//var num = 023; //2*8 + 3 ==> 19
//console.log(num); //19
严格模式书写格式
01 必须使用单引号或者是双引号括住字符串
02 必须使用小写,不能出现大写字符
03 必须是10个字符,不能多和也不能少
04 字符串后面的分号可以省略
05 必须写在当前作用域的最顶上
//"use strict"; //正确写法
//"use strict" //正确写法 分号可以省略
//'use strict'; //正确写法 可以使用单引号
//"use strict"; //错误写法 必须是10个字符
//"use Strict"; //错误写法 所有的字符都必须小写
"use strict";
a = 10;
// "use strict"; //错误写法
b = 20;
console.log(a);
作用范围
① 函数的顶部(只对当前的函数有效)
② script标签的顶部,只对当前的标签有效,对页面中其他的script无效
//位置01 对func01和func02都有效
//"use strict";
function func01() {
//位置02 对func01有效,对func02无效
//"use strict";
a = 10;
console.log(a);
}
function func02() {
//位置03 对func02有效,但对func01无效
//"use strict";
b = 20;
console.log(b);
}
func01();
func02();
二、作用域和闭包
作用域 变量其作用的范围就是它的作用域。
块级作用域 JavaScript中没有块级作用域
for (var i = 0; i < 10; i++) {
var num = i;
}
console.log(i);
console.log(num);
//**说明** 如果有块级作用域,那么i和num打印的结果应该为undefined
词法作用域 词法作用域指的是在代码写好的那一刻,变量的作用域就已经确定了。
动态作用域 变量的作用域由执行时的环境所决定,主要关注的是当前的函数调用栈
。
JavaScript语言是词法作用域的
在JavaScript中唯一能够创建作用域的东西是函数
var a = "这是第一个a";
function func02() {
var a = "这是第二个a";
func01()
}
func02(); //打印结果为:这是第一个a
var a = "这是第一个a";
function func01() {
console.log(a);
}
function func02() {
var a = "这是第二个a";
func01()
}
func01(); //打印结果为:这是第一个a
func02(); //打印结果为:这是第一个a
var a = "这是第一个a";
function func02() {
var a = "这是第二个a";
func01()
}
function func01() {
console.log(a);
}
func01(); //打印结果为:这是第一个a
func02(); //打印结果为:这是第一个a
</script>
➥ 词法作用域的规则
> ① 在函数内部允许访问外部的变量
> ② 在`JavaScript`中只有函数可以创建作用域
> ③ 作用域规则首先应用提升规则分析(代码的预解析阶段)
> ④ 如果当前作用域中有该变量,则不考虑外部作用域的同名变量
三、作用域链
作用域链的结构
01 在js中函数可以创建作用域
02 函数中又可以创建函数(即又可以开辟新的作用域)
03 函数内部的作用域可以访问外部的作用域
04 如果有多个函数嵌套,那么就会构成一个链式的访问结构,也就是作用域链
05 注意:函数内部的作用域可以访问外部的作用域,但是外部的作用域却不能访问内部的作用域
function f1() {
//f1--->全局作用域
function f4() {
//f4-->f1--->全局作用域
function f5() {}
}
}
function f2() {
//f2-->全局作用域
function f6() { }
}
function f3() {}
作用域注意点
01 在获取值和设置值的时候都是访问变量
02 并非在函数内部写了变量,这个变量就属于当前函数,而是必须使用var 关键字声明的变量才属于当前函数
03 函数在声明的时候,里面的代码并不会执行,只有在函数调用的时候才会执行
04 声明函数时候的函数名,其实也是一个变量名,可以通过这个变量名来进行设置和赋值
05 注意:在变量内部使用var 关键字声明一个变量并不会把同名的全局变量覆盖掉
var a = 10;
function f1() {
var a = 20; //注意:该行代码并不会覆盖掉全局变量中的变量a
console.log(a);
}
f1(); //20
console.log(a); //10
代码说明 在函数中使用var关键字声明变量a并不会覆盖全局作用域中的a。 注意需要同时考虑变量在当前作用域的提升以及访问变量时的搜索原则。
function f1() {
console.log(1);
}
var f1 = "demo字符串"; //该行代码会把f1函数覆盖掉
f1 = "demoTest字符串";
console.log(f1);
//f1();
代码说明 函数的名称也是变量,如果在代码中出现同名的变量,那么函数的实现会被覆盖掉。
作用域中变量搜索原则
01 在使用变量的时候,首先在自己的作用域中查找
02 如果找到了就直接使用,如果没有找到,那么就到上一级作用域中去查找
03 重复上面的步骤,直到0级作用域,如果还是找不到那么就返回undefined(报错)
四、变量和函数声明的提升
JS中的代码执行分为两个步骤: ① 预解析和 ② 执行。
其中JavaScript
解析器引擎在预解析阶段会对使用var关键字声明的变量和function声明的代码块进行提升操作,把这些变量提升到当前作用域的顶端。
这里简单列出代码提升的几种情况 => (函数 && 变量)
001 函数提升
func();
function func() {
console.log("测试的函数");
}
02 变量提升
console.log(a); //打印出来的结果为undefined
var a = 10;
//var a; //注意:只会对变量的声明进行提升
//console.log(a);
//a = 10
03 函数同名情况提升
func01(); //打印last
function func01() {
console.log("first");
}
func01(); //打印last
function func01() {
console.log("last");
}
//模拟提升后的情况
// function func01() {
// console.log("first");
// }
// function func01() {
// console.log("last");
// }
// func01();
// func01();
说明 预处理的时候,同名的函数都会进行提升,但是后面的会覆盖掉前面的.
04 变量名和函数同名的情况
console.log(a); //打印function
function a() {
console.log("我是一个函数");
}
var a = 20;
console.log(a); //打印20
//变量和函数提升后的结果 错误
// function a() {
// console.log("我是一个函数");
// }
// var a ;
// console.log(a);
// a = 20;
// console.log(a);
//变量和函数提升后的结果 正确
// function a() {
// console.log("我是一个函数");
// }
// console.log(a);
// var a = 20;
// console.log(a);
总结 如果出现变量和函数同名的情况,那么只会提升函数到当前作用域顶端而忽略变量的提升操作。
声明提升注意点
001变量的提升是分作用域的
console.log(a); //undefined
var a = 10;
//模拟提升
// var a;
// console.log(a); //undefined
// a = 10;
var num = 10;
function func() {
var num = 20;
console.log(num);
}
console.log(num); //10
func(); //20
//模拟提升
// var num;
// function func() {
// var num;
// num = 20;
// console.log(num);
// }
// num = 10;
// console.log(num); //10
// func(); //20
var num = 10;
function func() {
console.log(num);
var num = 20;
}
console.log(num); //10
func(); //undefiend
//模拟提升
// var num;
// function func() {
// var num;
// console.log(num);
// num = 20;
// }
// num = 10;
// console.log(num); //10
// func(); //undefiend
var num = 10;
function func() {
console.log(num);
num = 20;
}
console.log(num); //10
func(); //10
//模拟变量提升
// var num;
// function func() {
// console.log(num);
// num = 20;
// }
// num= 10;
// console.log(num); //10
// func(); //10
// console.log(num); //20
002 函数表达式的提升
说明 在使用函数表达式创建函数时整个函数表达式并不会进行提升,只会对var声明的变量提升。
func();
var func = function () {
console.log("会不会被调用");
}
//以上如上代码将报错
//模拟提升的过程
// var func;
// func(); //找不到这个函数
// func = function () {
// console.log("会不会被调用");
// }
五、闭包
闭包 通过某种方式实现的一个封闭的、包裹的对外不公开的结构 | 空间。
原理 变量的访问原则(即上一级的作用域无法访问下一级的作用域),其实函数本身也是闭包。
闭包要解决的问题 提供一种间接的方式能够访问到函数内部的数据(变量)
实现思路
01 我们需要能够在函数外部访问函数内部的变量,正常情况无法访问;
02 在函数内部如果新创建函数,那么安装作用域链的原则,这个新创建的内部函数能够访问到函数中的这些变量。
03 我们如果能够操作函数中新创建的函数,那么就能够操作函数中的变量(如访问和设置等)
04 如果要能够操作函数中新创建的函数,那么需要在函数中把新创建的函数返回。
05 调用函数,接收并得到其返回值(是一个函数)
06 调用返回值(函数),通过函数传参的方式来设置函数中的变量。
07 调用返回值(函数),通过在函数内部再次return的方式来访问函数中的变量。
闭包的基本模式
在函数内部创建函数(内部函数),在这个内部函数中,可以操作外部函数中的变量
01 在函数(外部)中创建函数(内部函数),在该函数(内部函数)中操作外部函数中的变量
02 在外部函数中,把内部函数作为返回值返回
03 调用外部函数,并接收其返回值(是一个函数)
04 调用接收到的返回值(内部函数),来间接的操作外部函数中的变量
function func() {
var num = 10;
return function (n) {
num = n;
console.log(num);
}
}
var funcName = func();
funcName("哗啦哗啦");
001 获取单个数据(考虑赋值)
function func() {
var num = 123;
return function (a) {
if (a !== undefined)
{
num = a;
}
return num;
}
}
var f1 = func();
var x = f1(456);
var y = f1();
console.log(x);
console.log(y);
说明 上面的代码能够支持通过闭包对函数中的变量num进行访问(取值)或赋值的操作。
002 获取多个数据(数组)
function func() {
var name = "张学友";
var age = 40;
return [
function getName() {
return name;
},
function getAge() {
return age;
}
]
}
var foo = func();
console.log(foo[0]()); //张学友
console.log(foo[1]()); //40
说明 上面的代码能够满足返回多个变量值的需求,但是要数组操作的方式并不常见,且和使用习惯不符合。
003 利用对象返回并设置对个变量值
function foo() {
var name = "张学友";
var age = 45;
return {
getName:function () {
return name;
},
getAge:function () {
return age;
},
setName:function (nameValue) {
name = nameValue;
},
setAge:function (ageValue) {
age = ageValue;
}
}
}
var func = foo();
console.log(func.getName()); //张学友
console.log(func.getAge()); //45
func.setName("张三");
func.setAge(30);
console.log(func.getName()); //张三
console.log(func.getAge()); //30
闭包作用的说明
(1)创建一个私有的空间保护数据,外界如果需要访问数据必须通过函数提供的指定方法
(2)在这些指定的方法中,我们可以设置一些校验的逻辑,以保证对数据访问和设置的安全性
关于闭包的一些练习
//备注:001
function foo() {
var num = 123;
console.log(num);
}
foo();
console.log(num);
//备注:002
var scope = "global";
foo();
function foo() {
console.log(scope);
var scope = "local";
console.log(scope);
}
//备注:003
function f1(){
if("a" in window){
var a = 10;
}
console.log(a);
}
f1();
//备注:004
if("a" in window){
var a = 10;
}
console.log(a);
//备注:005
if(!"a" in window){
var a = 10;
}
console.log(a);
//备注:006
var foo = 1;
function bar() {
if(!foo)
{
var foo = 10;
}
console.log(foo); //10
}
bar();
//备注:007
function Foo() {
getName = function(){
console.log("1");
};
this.show = function () {
console.log("hello");
}
return this;
}
Foo.getName = function() {
console.log("2");
};
Foo.prototype.getName = function(){
console.log("3");
};
var getName = function() {
console.log("4");
};
function getName(){
console.log("5");
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.show()
new Foo.getName()
new Foo().getName();
new new Foo().getName();