目录
一、声明提升
变量的声明提升
使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不使用var关键字,则变量不会被声明提前。
console.log(a); // undefined
var a = 4;
// 以上代码相当于:
var a;
console.log(a);
a = 4;
函数的声明提升
1.使用函数声明形式创建的函数 function函数(){ } :它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
// 函数声明, 会被提前
fun(); // 会正常执行
function fun() {
console.log("fun函数");
}
2.使用函数表达式创建的函数,不会被声明提升,所以不能在声明前调用
// 函数表达式, 不会被声明提前
fun2(); //会报错
var fun2 = function() {
console.log("fun2函数");
}
二、作用域
作用域: 指一个变量作用的范围, 在JS中一共有两种作用域。
全局作用域
1.直接编写在script标签中的JS代码,都在全局作用域
2.全局作用域在页面打开时创建,在页面关闭时销毁
3.在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器
创建, 我们可以直接使用
4.全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到
5.1.在全局作用域中, 创建的变量都会作为window对象的属性保存
var a = 10;
console.log(window.a); // 10
5.2在全局作用域中,创建的函数都会作为window对象的方法保存
function fn() {
console.log('11');
}
window.fn(); // 11
函数作用域
1.调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁。
2.每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的。
3.1在函数作用域中可以访问到全局作用域的变量
var a = 10;
function fun() {
console.log(`a=${a}`); // a=10
}
fun();
3.2在全局作用域中无法访问到函数作用域的变量
function fun1() {
var b = 2;
}
console.log(b); // 报错
4.当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域中寻找,直到找到全局作用域,如果全局作用域中依然没有找到,则会报错!!
var c = 10;
function fun3() {
var c = "作用在fun3";
function fun4() {
console.log(`c为: ${c}`); // c为: 作用在fun3
}
fun4();
}
fun3();
5. 在函数里面访问全局变量: window.变量
var d = 20;
function fn1() {
var d = 3;
// console.log(d); 3
console.log(window.d); // 20
}
fn1();
6.在函数作用域也有声明提前的特性
6.1使用var关键字声明的变量,会在函数中所有的代码执行之前被声明
function fn2() {
console.log(m); // undefined
var m = 34;
}
fn2();
// 以上代码相当于
function fn2() {
var m;
console.log(m);
m = 34;
}
fn2();
6.2 函数声明也会在函数中所有的代码执行之前执行
function fn4() {
fn5(); // 221
var a = 35;
function fn5() {
console.log(221);
}
}
fn4();
7.在函教中,没有使用var声明的变量,会向上一级寻找,如果都找不到,就会成为全局变量!!
var n = 43;
function fn6() {
n = 12;
}
fn6();
console.log(n); // 12
8.定义形参就相当于在函数作用域中声明了变量
var e = 250;
function fnn(e) {
console.log(e);
}
fnn(); // undefined 有形参但没传递实参 所以是undefined
三、预编译
预编译类型 | 含义 |
全局预编译 | 在开始执行语句时对全局声明的变量进行预编译 |
局部预编译 | 在函数调用的前一刻开始预编译 |
JS解释器执行js代码的步骤: 从上到下顺序执行
语法检查 | 对字母大小写、括号匹配等语法检查 | |
预编译 | 全局预编译 | 创建一个GO对象,即全局的执行期上下文 |
把全局下声明的变量、函数名作为属性追加到该对象里面 | ||
局部预编译 | 创建一个AO对象,即函数的执行期上下文 | |
把函数内部声明的变量、函数名和形参作为属性,追加到该执行期上下文对象里面; | ||
把实参的值赋值给对应的属性 | ||
把函数内部声明的函数作为值赋值给对应的属性 | ||
执行语句 | 赋值语句、函数调用语句、循环语句、判断语句等 |
【面试题1】
function fn(o) {
o.siteUrl = "a";
o = new Object();
o.siteUrl = "b";
}
var cr = new Object();
fn(cr);
console.log(cr.siteUrl);
/*
代码解析:
全局预编译:首先进行全局预编译,将全局下的变量、函数声明放在GO中
GO:{
fn: function() {},
cr: undefined
}
局部预编译:全局预编译之后,开始执行语句,遇到函数的地方要进行局部预编译,
将函数里面的变量、形参、函数声明放在AO中,将传递的实参赋值给形参,
其余变量都为undefined
AO:{
无声明的变量,不需要局部预编译
}
*/
图解:
四、this的理解
根据函数的调用对象不同,this会指向不同的对象,即谁调用,就指向谁
var name = '和尚';
function fn() {
console.log(this.name); // window
}
// fn();
var obj = {
name: "八戒",
sing: fn
};
obj.sing(); // 八戒 this指向obj
var obj1 = {
name: "悟空",
sing: fn
};
obj1.sing(); // 悟空 this指向obj1
五、工厂方法创建对象
function creat(namer, age, gender) {
var obj = new Object();
obj.namer = namer;
obj.age = age;
obj.gender = gender;
obj.sing = function() {
console.log(this.name);
}
return obj;
}
var obj1 = creat('刘德华', 12, "男");
var obj2 = creat('张学友', 18, "男");
var obj2 = creat('张学友', 18, "男");
console.log(obj1); // {namer: "刘德华", age: 12, gender: "男", sing: ƒ}
console.log(obj2); // {namer: "张学友", age: 18, gender: "男", sing: ƒ}
六、构造函数
1.概念:构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是:
- 构造函数习惯上首字母大写
- 构造函数和普通函数的区别就是调用方式的不同
- 普通函数是直接调用,而构造函数需要使用new关键字来调用
2.构造函数的执行流程:
(1)立刻创建一个新的对象
(2)将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
(3) 逐行执行函数中的代码
(4)将新建的对象作为返回值返回
3.使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类,我们将通过一个构造函数创建的对象,称为是该类的实例。
function Person(namer, age, gender) {
this.namer = namer;
this.age = age;
this.gender = gender;
this.sing = function() {
console.log('唱歌');
};
// 创建实例
var liudehua = new Person('刘德华', 12, '男');
var zhangxueyou = new Person('张学友', 16, '男');
var ljr = new Person('梁静茹', 23, '女');
console.log(liudehua); // {namer: "刘德华", age: 12, gender: "男", sing: ƒ}
console.log(zhangxueyou); // {namer: "张学友", age: 16, gender: "男", sing: ƒ}
console.log(ljr); // {namer: "梁静茹", age: 23, gender: "女", sing: ƒ}
ljr.sing(); // 唱歌 调用方法
// 所有的对象都是Object的后代,所以任何对象和Object做instanceof检查时都会返回true
console.log(ljr instanceof Object); // true
七、原型
1.原型对象:就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中 !!
2.我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象
function Class() {
}
var mc = new Class();
console.log(Class.prototype); // {constructor: ƒ}
3. 如果函数作为普通函数调用, prototype没有任何作用
4.当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过_proto__来访问该属性
console.log(mc.__proto__); // {constructor: ƒ}
console.log(mc.__proto__ == Class.prototype); // true
5.当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用
// 给Class的原型对象添加一个属性
Class.prototype.namer = 'koko';
console.log(mc.namer); // koko mc中也添加了这个属性
// 给Class的原型对象添加一个方法
Class.prototype.sing = function() {
console.log("你好");
}
mc.sing(); // 你好 mc中也添加了这个方法
6.使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
function MyClass() {
}
// 向MyClass的原型对象中添加一个namer属性
MyClass.prototype.namer = '我是原型中的属性';
// 实例一个对象
var mc = new MyClass();
mc.age = '18'
console.log(mc.namer); // 我是原型中的属性
console.log('namer' in mc); // true
7.可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,有则返回true
console.log(mc.hasOwnProperty('namer')); // false
console.log(mc.hasOwnProperty('age')); // true
8.构造函数本身就是Function这个内置的构造函数的实例化对象
console.log(Person.__proto__ === Function.prototype); // true
9.Function是自己的实例化对象
console.log(Function.prototype == Function.__proto__); // true
10.原型链:原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时,会先在自身中寻找,自身中如果有,则直接使用,如果没有则去原型对象中寻找,如果原型对象中有,则使用,如果还没有则去原型的原型中寻找,直到找到Object对象的原型,object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined。
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); // true
// console.log(mc.__proto__.__proto__.__proto__); // null
console.log(mc.hello); // undefined
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样每个对象都具有这些属性和方法了。
function Person(namer, age, gender) {
this.namer = namer;
this.age = age;
this.gender = gender;
// this.sing = fn;
}
// 向Person原型对象中添加方法 所有创建的实例都可以访问
Person.prototype.sing = function() {
console.log('唱歌');
}
var liudehua = new Person('刘德华', 12, '男');
var zhangxueyou = new Person('张学友', 16, '男');
var ljr = new Person('梁静茹', 23, '女');
console.log(liudehua); // {namer: "刘德华", age: 12, gender: "男", sing: ƒ}
console.log(zhangxueyou); // {namer: "张学友", age: 16, gender: "男", sing: ƒ}
console.log(ljr); // {namer: "梁静茹", age: 23, gender: "女", sing: ƒ}
liudehua.sing(); // 唱歌 调用方法
ljr.sing(); // 唱歌