函数的调用方式和 this 丢失
- 函数调用方式
- 普通函数方式调用 this - window
- 对象的方法 this - 对象
- 构造函数方式 this - 内部创建的新对象
- 函数上下文(call|apply) this - 第一个参数
- this 的指向发生了改变
函数的调用方式发生了改变
var name = 'window 的属性';
function demo(){
console.log(this.name);
}
var obj = {
name:"张三",
demo:demo
}
demo();
obj.demo();
var div = document.getElementById('demoId');
console.log(div);
//var div2 = getId('demoId');
//console.log(div2);
//document.getElemenetById 方法内部的实现,在该方法内部使用到了 this
//这个 this 默认指向的是document
//getId 在调用的时候使用普通函数的方式来进行调用, this 指向的是 window
document.getId = document.getElemenetById;
var div3 = document.getId('demoId');
console.log(div3);
//console.log(document.getElemenetById.call(document,''demoId));
//getId('demo');
var getId = (function(){
return function(){
//return document.getElementById.call(document,arguments[0]);
return document.getElemenetById.apply(document,arguments);
}
})();
var div4 = getId('demoId');
console.log(div4);
图书管理员面向对象
//注意点:在原型对象的方法中访问该对象的其他方法,需要使用 this 前缀
function BookListManager(){
this.bookList = null;
}
BookListManager.prototype = {
constructor:BookListManager,
init:function(){
this.bookList = arr || [];
},
getBook:function(name){
for(var i in this.bookList){
if(this.bookList[i].name == name){
return this.bookList[i];
}
}
throw "要查找的对象不存在!"
},
updateBook:function(name,author){
//先找到这个对象
var book = this.getBook(name);
//修改对象
book.author = author;
},
addBook:function(obj){
this.bookList.push(obj);
},
removeBook:function(){
var book = this.getBook(name);
var index = this.bookList.indexOf(book);
if(index == -1){
throw '要删除的对象不存在'
}
//删除该对象
this.bookList.splice(index,1);
}
}
var jack = new BookListManager();
var tom = new BookListManager();
jack.init([{name:'古典文学研究',author:'古典'},{name:'莫言文集',author:'莫言'}]);
jack.addBook({name:'飞鸟集',author:'泰戈尔'});
console.log(jack.getBook('飞鸟集'));
jack.updateBook('飞鸟集','老太太');
jack.removeBook('飞鸟集');
console.log(jack.bookList);
严格模式的简单说明
- js 中有两种开发模式,非严格模式(默认) + 严格模式 ('use strict')
- 严格模式会进行更严格的代码检查
- 以前可以的特性在严格模式下可能被禁止使用
- 以前可以使用的代码方式,在严格模式下会直接报错
- 开启严格模式
- 字符串命令 'use strict';
- 位置 当前作用域的顶端
- 兼容性问题:
严格模式不存在兼容性问题,如果当前的浏览器支持严格模式,那么再扫描到"use strict"命令的时候就会开启严格模式检查,如果不支持严格模式那么就直接忽略 - 使用建议
建议在写代码的时候全部开启严格模式
严格模式使用注意
- 在严格模式下,所有的变量都必须使用 var 声明
- 在严格模式下,禁止使用八进制
- 在严格模式下,禁止使用 with
- 在严格模式下,不能删除全局的变量
- 在严格模式下,不能在 if 语句中声明函数
- 在严格模式下,函数的形参不能出现同名的情况
- 在严格模式下,不能使用 callee|caller
- 在严格模式下,不能使用 eval 和 arguments 作为标识符(变量和函数名称)
- 在严格模式下,修正了 this 的指向
- 在严格模式下,arguments 的表现不一致
- 在严格模式下,对象中不能出现同名的属性
"use strict";
//01 在严格模式下,所有的变量都必须使用var声明
// 在默认情况下,如果不适用var声明变量,那么该变量默认会成为window的属性(全局变量)
// var a = 10;
// b = 20;
// console.log(b);
//02 在严格模式下,禁止使用八进制
// var num = 022; //数值以0开头,以八进制的方式来处理
// var num = 0x22;
// console.log(num);
//03 在严格模式下,禁止使用使用with
// var obj = {name:"张三",age:20};
// with (obj)
// {
// name = "李四";
// age = 99
// }
// console.log(obj);
//04 在严格模式下, 不能删除全局的变量
// 在默认情况下,可以删除全局变量(不能删除成功),静默失败
// var str = "string";
// console.log(delete str);
// console.log(str);
//05 在严格模式下,不能在if语句中声明函数
// if (true)
// {
// function demo() {
// console.log("demo");
// }
//
// demo();
// }
//06 在严格模式下,函数的形参不能出现同名的情况
// function test(a,b,a) {
//// var a = 1;
//// var b = 2;
//// var a = 3;
// console.log(a + b + a); //6 ? 8 ? 4 后面的会把前面的覆盖
// }
//
// test(1,2,3);
//07 在严格模式下,不能使用arguments.callee|caller
//caller 指向的函数的调用者 注意点:window调用该函数,指向的是null
//arguments.callee 常用在递归调用中,指向的是函数自己
// function f1() {
// f2();
// }
// function f2() {
// console.log(f2.caller);
// }
// f1();
// f2();
// console.log((function (n) {
// if (n == 1) {
// return 1;
// }
//
// return arguments.callee(n - 1) + n;
//})(11));
//08 在严格模式下, 不能使用eval和arguments作为标识符(变量和函数的名称)
// var eval = "测试字符串";
// console.log(eval);
// var arguments ="....";
// console.log(arguments);
//09 在严格模式下,修正了this的指向
// 默认情况下,this指向的是window,严格模式指向的undefined
// function func() {
// console.log(this);
// }
//
// func();
// var o = {};
// func.call(null); //严格模式下,指向的是null
//10 在严格模式下,arguments的表现不一致
//在默认情况下,如果函数内部形参被重新设置,那么arguments也会跟着改变
//在严格模式情况下,如果函数内部形参被重新设置,那么arguments不会被改变,他们是相互独立的
//值类型的数据作为函数的参数
function demo(str) {
console.log(str);
console.log(arguments[0]);
//重新设置形参的值
str = "hahahaha";
console.log(str);
console.log(arguments[0]);
}
demo("123456");
//引用类型的数据作为函数参数
function demo(obj) {
console.log(obj);
console.log(arguments[0]);
//重新设置形参的值
obj = {age:20};
console.log(obj);
console.log(arguments[0]);
}
demo({name:"张三"});
严格模式的书写格式
//"use strict";
//"use strict" 正确
//'use strict'; 正确
//'use strict' 正确
//use strict; 错误
//"use Strict"; 错误
//"use strict"; 错误
//"use strict "; 错误
//" use strict"; 错误
严格模式的作用范围
- 位置:当前作用域的最上面
- js 中的作用域:
- script 标签 全局作用域
- 函数内部 局部作用域
// "use strict"; //位置01 对整个作用域中的代码都有用
function demo01() {
// "use strict"; //位置02 仅仅对当前的函数有用,函数后面的代码不受影响
a = 10;
console.log(a);
}
function demo02() {
// "use strict"; //位置03 仅仅对当前的函数有用
b = 20;
console.log(b);
}
demo01();
demo02();
作用域说明
- 作用域
概念:变量或者是函数起作用的范围 - js 的作用域
- js 本身没有块级作用域(try-catch 除外)
- js 中只有函数可以创建作用域
- js 本身是此法作用域 (with|eval 除外)
- 词法作用域:
当代码写好之后,某个变量的作用域就已经确定了 - 动态作用域:
变量的作用域在代码运行之前是不确定的,只有在代码执行的时候才能根据当前的上下文来确定 - 词法作用域的访问规则:
- 单向性的(单向镜),内部的作用域可以访问外层的作用域空间,反过来却不行
- 在访问变量的时候,先在当前作用于中查找,如果找不到那么就在上一级作用域中查找,重复这个过程
- 在分析输出的时候,需要考虑到变量和函数声明的提升
var a = "test-A";
function f1() {
var b = "test-B";
}
function f2() {
var c = "test-C";
}
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i,"_____"); //? 10
try
{
//可能出错的代码
a();
}catch (error){
//如果出错了,那么就执行此处的代码
console.log(error);
}
// console.log(error);
var demo = "测试的字符串01";
function f1() {
var demo = "demo";
var test = 10;
f2();
}
function f2() {
console.log(demo);
console.log(test);
}
// f2(); //测试的字符串
f1(); //demo
变量和函数的提升
- js 代码的执行
编译语言
解释语言(js) - 预先解析阶段
变量和函数声明的提升 - 具体的执行代码
- js 变量和函数声明提升的注意点
- 变量和变量同名 后面的变量会把前面的变量覆盖
- 函数和函数同名 覆盖
- 变量和函数同名 函数声明会正常的提升,而变量的声明可以认为被忽略了
只会提升到当前作用域的最顶端
console.log("_____");
console.log(a); //und
var a = "test-A";
console.log(a); //Test-A
var a = "demo-A";
console.log(a); //demo-A
//模拟变量声明的提升
// var a;
// console.log("_____");
// console.log(a); //und
// a = "test-A";
// console.log(a); //Test-A
// a = "demo-A";
// console.log(a); //demo-A
</script>
<script>
f1(); //Demo
function f1() {
console.log("Test");
}
f1(); //Demo
function f1() {
console.log("Demo");
}
f1(); //Demo
//模拟提升
// function f1() {
// console.log("Demo");
// }
// f1(); //Demo
// f1(); //Demo
// f1();
</script>
<!--<script>-->
<!--console.log(a); //函数-->
<!--var a = "test-A";-->
<!--function a() {-->
<!--console.log("demo-A");-->
<!--}-->
<!--console.log(a); //函数-->
<!--//模拟-->
<!--var a;-->
<!--function a() {-->
<!--console.log("demo-A");-->
<!--}-->
<!---->
<!--console.log(a); //函数-->
<!--a = "test-A";-->
<!--console.log(a); //test-A-->
<!--</script>-->
<script>
console.log(a);
var a = "test-A";
function a() {
console.log("demo-A");
}
// 不管函数在前面还是变量在前面,打印出来的结果都是函数
</script>
思考:
<script>
var demoA ="10";
function foo() {
console.log(demoA); //10 undefined(正确)
var demoA = "20";
console.log(demoA); //20
}
function func() {
console.log(demoA); //10
demoA = "30";
console.log(demoA); //30
}
foo();
func();
console.log(demoA); //30
变量提升是分作用域的
- 在代码执行之前,会把所有的变量和函数生命进行提升
- 在提升的时候,变量和函数声明的提升是分作用域的,只能提升到当前作用域的顶端
- 内层作用域中的变量声明并不会覆盖外层作用域中的同名变量
console.log(demo);
var a = "第一个a";
function demo() {
console.log(a); //? undefined
a = "张三";
console.log(a); //张三
var a = "哈哈哈";
console.log(a); //哈哈哈
}
console.log(a);
demo();
console.log(a); //第一个a(正确) ? 张三 ?哈哈哈
</script>
<script>
var b = 10;
function test() {
console.log(b);
var b = 20;
}
console.log(b); //10
test(); //und
console.log(b);
</script>
函数表达式的提升
- 函数表达式提升,在提升的时候仅仅只会把声明部分(var func02)提升到当前作用域的顶端
<script>
console.log(func01);;
function func01() {
console.log("func01");
}
</script>
<script>
// var func02;
console.log(func02);;
var func02 = function () {
console.log("func02");
}
</script>
笔试题练习
<script>
function foo() {
var num = 123;
console.log(num); //123
}
foo();
//console.log(num); //报错
</script>
<script>
var scope = "global";
foo();
function foo() {
console.log(scope); //?global
scope = "local";
console.log(scope); //?local
}
console.log(scope); //local
</script>
in:检查对象中是否存在指定的属性
<!--<script>-->
<!--function f1(){-->
<!--var a;-->
<!--if("a" in window){-->
<!--a = 10;-->
<!--}-->
<!--console.log(a); //? 10 ? undefined ?报错-->
<!--}-->
<!--f1();-->
<!--</script>-->
<!--<script>-->
<!--if("a" in window){-->
<!--var a = 10;-->
<!--}-->
<!--console.log(a); //? 10 -->
<!--</script>-->
<!--<script>-->
<!--if(!"a" in window){-->
<!--var a = 10;-->
<!--}-->
<!--console.log(a); //?-->
<!--</script>-->
<script>
if("a" in window){
a = 10;
}
console.log(a); //? 报错
</script>
<script>
var foo = 1;
function bar() {
var foo;
if(!foo)
{
foo = 10;
}
console.log(foo); //?
}
bar();
// undefined == null; 相等
// undefined == false; 不相等
// !undefined
console.log(undefined === null); //true
console.log(!undefined == true);
</script>
function Foo() {
getName = function(){
console.log("1");
};
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(); // ? 2
getName(); // ? 4
Foo().getName(); // ? (1) ? 3 ? 2 ? 4
getName(); // ? (1)
new Foo.getName(); // ? 2
new Foo().getName(); // ? 3
new new Foo().getName(); // ? 3
作用域链
- 有多少个作用域 (函数的个数 + 1)
- 相同作用域可以相互访问
- 作用域链:
在 js 中函数可以创建作用域,在函数内部又可以声明函数,在函数的函数内部又可以声明函数,每个函数都会创建一个作用域,这样就会形成一个作用域链 - 在访问变量的时候,总是先在自己的作用域中查找
- 如果没有那么就向上一级作用域查找,如果找到那么就直接使用,如果没有找到那么就继续重复这个过程
- 直到最外层的全局作用域,如果还没有找到那么就报错
var a = "a";
//f1--->全局作用域
function f1() {
var b = "b";
var a = "f1-a"
//f2-->f1--->全局作用域
function f2() {
var c = "c";
var b = "f2--b";
//f3-->f2-->f1--->全局作用域
function f3() {
var d = "d";
//f4-->f3-->f2-->f1--->全局作用域
function f4() {
console.log(a, b, c, d,e);
}
f4();
}
f3();
}
f2();
}
f1();
作用域链绘图
首先先找出全局变量(包含函数) a f1 f2 f3
画出他们的图形,并且连线(有箭头),箭头的方向表示的是是否可以访问(同一级作用域可以互相访问)
-
以上画出0级作用域链 var a = "test-a";
function f1() {
var b = "test-b";
function f4() {function f5() { var c = "test-c"; var d = "test-d" } }
}
function f2() {
var e = "test-e";
// f4();
}
function f3() {
}
f2();