一、作用域
js中变量的作用域是指一个变量的作用范围。包括:全局作用域
、函数作用域
、块作用域
(ES6后新增)。
1、全局作用域:
(1) 全局作用域在页面打开时被创建,页面关闭时被销毁;
(2) 编写在script标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到;
(3) 在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用;
(4) 全局作用域中声明的变量和函数会作为window对象的属性和方法保存;
// 作用域: 全局, 函数, 块
// 1. 全局作用域,默认的,不可删除
let site = "京东商城";
console.log(site);
// 由全局对象调用的
// 全局对象: 如果在是浏览器中运行js,那么全局对象就是window
console.log(window.site);
2、函数作用域:
(1) 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁;
(2) 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的;
(3) 在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量;
(4) 在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域;
// 2. 函数作用域
function getSite() {
// site是声明在函数外部的全局变量
// 在函数内部可以访问到外部的全局变量
// 私有成员,仅限在当前作用内访问, 外部不可见
let domain = "jd.com";
return `${site} [ ${domain} ]`;
// 这里要返回一个叫site的变量
// 有一个查询的过程, 先在自身的作用域找一个有没有一个叫site
// 如果有则直接返回它
// 如果函数中没有这个site,则自动函数的上一级作用域中去查看site
// 全局正好有一个site,于是就返回了全局的site
// 内部的site ---> 到它的上一级作用域中去查找
// 上面的查询变量的过程,就是是一个链式查询的一个过程,称为:作用域链
}
console.log(getSite());
// console.log(domain);
3、块作用域:
ES6中新增了块级作用域,块作用域由 { }
包括,if语句和for、while语句里面的{ }也属于块作用域。
// 3. 块作用域
// if (), while()
{
// let a = 1;
// const B = "hello";
// var:不支持块作用域
var a = 1;
var B = "hello";
}
console.log(a, B);
二、闭包
如果在一个内部函数里,对在外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。闭包的本质就是在一个函数内部创建另一个函数
。
// 4. 闭包
let c = 100;
function sum(a, b) {
// return a + b;
// 现在已经产生了闭包了
// 函数中的变量按来源有二种类型
// 1. 形参: a, b , 这是函数自有的
// 2. 自由变量: c, c并不是函数自有的
// 当前的函数可以访问到上一级/外部的自由变量
// 闭包: 能够访问自由变量的函数
// 自由变量,函数参数以外的变量
// 理论上讲,所有函数都是闭包
return a + b + c;
}
console.log(sum(4, 5));
// 通过闭包来访问内部的私有变量;
function demo1() {
// 私有变量
let email = "abc@qq.com";
// return email;
return function d() {
// 对于这个子函数来说,email就是它的自由变量
return email;
};
}
// console.log(email);
// let f = demo1();
// console.log(f());
console.log(demo1());
三、类与继承
JS并没有类(class)的概念,通常都是通过构造函数来实现对象(class)属性的定义,然后通过new这个关键字来实例化。
构造函数的三大特点:
(1) 构造函数的函数名的第一个字母通常大写;
(2) 函数体内使用this关键字,代表所要生成的对象实例;
(3) 生成对象的时候,必须使用new命令来调用构造函数;
// 构造函数来模拟类(对象的模板)
function User(name, email) {
this.name = name;
this.email = email;
// show()输出属性
// this.show = function () {
// return { name: this.name, email: this.email };
// };
}
// 构造函数对象的原型对象上的成员,可以被所有实例所共享
// es6
class User1 {
// 构造方法:初始化对象的
constructor(name, email) {
this.name = name;
this.email = email;
}
// 原型方法(共享方法),通过对象来调用的
show() {
return { name: this.name, email: this.email, age: this.#age };
}
// 静态方法: 不需要实例化(new class),直接用类来调用
static fetch() {
// return "static function";
// 静态成员中的this表示的就是当前的类
return this.userName;
}
// 静态属性/变量
static userName = "小马哥";
// 私有成员
#age = 50;
// 还可以声明访问器属性: 伪装成属性的方法,有get,set
// 使用访问器属性来访问私有成员
get age() {
return this.#age;
}
set age(value) {
if (value >= 18 && value < 60) this.#age = value;
else console.log("年龄非法");
}
}
const user1 = new User1("大牛", "tp@qq.com");
// console.log(user1);
console.log(user1.show());
// 静态方法直接用类调用
console.log(User1.fetch());
// console.log(user1.#age);
console.log("my age = ", user1.age);
user1.age = 160;
console.log("my age = ", user1.age);
user1.age = 32;
console.log("my age = ", user1.age);
继承,首先需要一个父类(Father),将父类的实例作为子类的原型,并对父类进行一些扩展。
// 继承,通常是对父类进行一些扩展(添加一些新的属性或方法)
class Child extends User1 {
constructor(name, email, gender) {
// 第一步必须将父类的构造方法来执行一下,否则this用不了
super(name, email);
// 第二步给子类的新成中去初始化
this.gender = gender;
}
// 父类的原型方法
show() {
return { name: this.name, email: this.email, gender: this.gender };
}
}
const child = new Child("大牛", "niu@qq.com", "男");
console.log(child.show());