5、作用域、作用域链、闭包
作用域
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
JavaScript(es6前)中的作用域有两种:
- 全局作用域
- 局部作用域(函数作用域)
全局作用域
/**
* @函数全局作用域
* 作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。
*/
// 整个script标签内部
<sctipt type="text/script">
...
</sctipt>
// 独立的js文件
(function fn(){
...
})()
局部作用域
/**
* @局部作用域
* 作用于函数内的代码环境,就是局部作用域。(跟函数有关,简称函数作用域)
*/
function fn(){
// 函数作用域
...
}
块级作用域
/**
* @块级作用域
* 块作用域由{}包括
* 在其他编程语言中(如java、c#等),在if语句,循环语句中创建的变量,仅仅只能在本if语句,本循环语句中使用
* js中没有块级作用域(在ES6之前)
*/
if (true) {
var num = 123;
console.log(num); // 123
}
console.log(num); // 123
变量的作用域
全局变量
/**
* @全局变量
* 在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)
* 1、全局变量在代码的任何位置都可以使用
* 2、在全局作用域下var声明的变量是全局变量
* 3、特殊情况下,在函数内不使用var声明的变量也是全局变量(不建议使用)
*/
var num;
function fn() {
num = 0;
console.log(num); // 0
count = 0;
}
console.log(count); // 0
局部变量
/**
* @局部变量
* 在局部作用域下声明的变量叫作局部变量(在函数内部定义的变量)
* 1、局部变量只能在该函数内部使用
* 2、在函数内部var声明的变量是局部变量
* 3、函数的形参实际上就是局部变量
*/
function fn(target){
var num=0;
console.log(num);
target='haha'
console.log(target);
}
console.log(num); // num is not defined
console.log(target);// target is not defined
全局变量和局部变量的区别
- 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
- 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间
作用域链
只要是代码都一个作用域中,写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;
如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;
根据在**[内部函数可以访问外部函数变量]**的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1(); // 123
// 作用域链:采取就近原则的方式来查找变量最终的值。
var a = 1;
function fn1() {
var a = 2;
var b = '22';
fn2();
function fn2() {
var a = 3;
fn3();
function fn3() {
var a = 4;
console.log(a); //a的值 ?
console.log(b); //b的值 ?
}
}
}
fn1(); // a:4,b:22
闭包
/**
* @闭包
* 闭包(closure)指有权访问另一个函数作用域中变量的函数。
* 简而言之:一个作用域可以访问另外一个函数内部的局部变量。
* 作用:延伸变量的作用范围。
*/
// 闭包函数
function fn1() {
var num = 10;
function fn2() {
console.log(num); // 10
}
fn2();
}
fn1();
// 作用案例
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
f();
案列:
/**
* 1、得到当前li的索引号
*/
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i);
};
})(i);
}
/**
* 2、3秒之后,打印所有li元素的内容
*/
for (let i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
/**
* 3、计算打车价格
* 需求:打车起步价13(3公里内), 之后每多一公里增加 5块钱.
* 用户输入公里数就可以计算打车价格
* 如果有拥堵情况,总价格多收取10块钱拥堵费
*/
var car = (function () {
var start = 13; // 起步价 局部变量
var total = 0; // 总价 局部变量
return {
// 正常的总价
price: function (n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5;
}
return total;
},
// 拥堵之后的费用
yd: function (flag) {
return flag ? total + 10 : total;
},
};
})();
console.log(car.price(5)); // 23
console.log(car.yd(true)); // 33
6、对象、构造函数、实例化
对象
创建对象
/**
* @对象
* 对象是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
* @属性:事物的特征,在对象中用属性来表示(常用名词)
* @方法:事物的行为,在对象中用方法来表示(常用动词)
*/
// 字面量创建对象
var starPerson={
name:'张三',
age:18
}
// 构造函数创建
function Star(name,age){
this.name=name;
this.age=age;
}
var lisi=new Star('李四',20); // 实例化对象
console.log(lisi);
// 类创建对象(ES6)
class Star{
constructor(name,age){
this.name=name;
this.age=age;
}
}
var zs=new Star('张三',22);
console.log(zs);
- 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
- 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
- constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
- 多个函数方法之间不需要添加逗号分隔
- 生成实例 new 不能省略
- 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
构造函数
/**
* @构造函数
* 定义:通过 new 函数名,来实例化对象的函数叫构造函数。
* 任何函数都可以作为构造函数存在。
* 构造函数定义时首字母大写(规范)
* 对new的理解:
* 1、new申请内存,创建对象;
* 2、当调用new时,后台会隐式执行new Object()创建对象。
* 通过new创建的字符串、数字时引用类型,而是非值类型。
*/
// 常用构造函数
var arr=[]; /* 语法糖 */ var arr=new Array();
var obj={};/* 语法糖 */ var obj=new Object();
var date=new Date();
实例化
/**
* @实例化
* 静态的成员变量和方法,都是不需要进行实例化类的。可以直接调用。
* 非静态的成员变量和方法,都要进行实例化类的。才可以调用
*/
// 工厂模式
function obj() {
var person = new Objec(); //创建对象,对象属性赋值
person.name = "zs";
person.sex = "男";
person.hobby = function () {
console.log("打篮球");
};
return zs;
}
var zs = obj();
console.log(zs.name, zs.sex);
zs.hobby();
// 构造函数模式
function obj2(name, age) {
this.name = name;
this.age = age;
this.hobby = function () {
console.log("跑步");
};
}
var lisi = new obj2("李斯", 22);
console.log(lisi.name, lisi.age);
lisi.hobby();
// 原型模式
function obj3() {}
obj3.prototype.name = "王五";
obj3.prototype.love = function (name) {
console.log("喜欢" + name);
};
var wangwu = new obj3();
/* 检测实在实例中还是在原型上 */
console.log(wangwu.hasOwnProperty("name"));
console.log();
wangwu.love("三妹");
// 混合模式
function obj4(age) {
this.age = age;
this.rename = "aaa";
}
obj3.prototype = {
constructor: obj4,
name: "赵六",
age: 23,
love: function (name) {
console.log(name + "看书");
},
};
var zl=new obj4(20);
console.log(zl.hasOwnProperty('age'));
zl.love('zhaoliu');
7、原型、原型链、对象继承
原型
构造函数原型
/**
* @构造函数原型
* 构造函数通过原型分配的函数时所有对象共享的。
* 每一个构造函数都有一个prototype属性,指向另一个对象。
* 注:prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
* 可以把哪些不变的方法,直接定义在prototype对象上,所有对象的实例就可以共享。
*/
function Star(name,age){
this.name=name;
this.age=age;
}
Star.prototype.sing=function(){
console.log('会唱歌');
}
var ldh=new Star('刘德华',55);
var zxy=new Star('张学友',50);
ldh.sing();
zxy.sing();
对象原型
/**
* @对象原型
* 对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,
* 之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
* __proto__对象原型和原型对象 prototype 是等价的。
* __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
*
*/
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star, // 手动设置指回原来的构造函数
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var zxy = new Star('张学友', 19);
console.log(zxy)
原型链
/**
* @原型链
* 每一个实例对象有一个proto对象,指向的构造函数的原型对象。
* 构造函数的原型对象也是一个对象,也有proto属性,一层层往上找就形成了原型链
*/
构造函数和原型对象三角关系
构造函数的prototype属性指向了构造函数原型对象
实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
对象继承
类的继承
/**
* 类的继承
*/
class Father {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log("姓名:" + this.name + ", 年龄:" + this.age);
}
}
class Son extends Father {
constructor(name, age, hobby) {
super(name, age);
this.hobby = hobby;
}
hobby(){
console.log('爱好:'+this.hobby);
}
}
var son = new Son("zs", 18,'篮球');
son.say(); // 姓名:zs,年龄:18
son.hobby(); // 爱好:篮球
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用
8、对象、对象克隆
对象
/**
* @对象
* 第七种数据类型,唯一一种复杂类型
* 1、数据集合,无序的
* 2、键值对的集合(键与值组成一对称为键值对)
* 3、每个value都时对象的属性值
* 4、所有属性名会自动转变成字符串
* 5、js中每一个对象都有一个隐藏属性,存储着其共有属性组成的对象的地址;这个共有属性组成的对象叫做原型,隐藏属性存储着原型的地址
*/
// 1.查看自身所有属性
Object.keys(obj)
// 2.查看自身所有值:
Object.values(obj)
// 3.查看自身所有属性和值:
Object.entries(obj)
// 4.查看自身+共有属性
console.dir(obj)
// 5.判断一个属性是自身的还是共有的
obj.hasOwnProperty('toString')
对象克隆
1.通用对象克隆
/**
* @通用对象克隆
*/
function clone(obj) {
let temp = null;
if (obj instanceof Array) {
temp = obj.concat();
} else if (obj instanceof Function) {
// 函数是共享的
temp = obj;
} else {
temp = new Object();
for (let item in obj) {
let val = obj[item];
// 判断是否为函数,是函数当作一般值处理
temp[item] = typeof val == "object" ? clone(val) : val;
}
}
return temp;
}
2.JSON对象序列化
/**
* @JSON对象序列化
* 弊端:不能赋值函数
*/
var obj={}
var newObj=JSON.parse(JSON.stringify(obj));
3.DOM元素复制cloneNode
/**
* @DOM元素复制
*/
let div=document.getElementById('box');
let box2=div.cloneNode(true);
4.ES6新方法——Object.assign
/**
* @ES6方法
*/
var obj={};
var newObj=Object.assign({},obj);