闭包、作用域、THIS、OOP

1.作用域

全局作用域

供代码执行的运行环境即全局作用域,在浏览器打开页面的同时,也会形成两个虚拟的内存;

  • 栈内存:1.提供代码运行环境 2.存储基本数据类型值
  • 堆内存:存储引用数据类型值;

在全局作用域形成以后,在这个全局作用域会默认提供最大的window对象;
全局变量:在全局作用域下定义的变量叫全局变量;

私有作用域

私有变量:在私有作用域下定义的变量

  • 1.当前作用域有没有被var;
  • 2.有没有被function
  • 3.是否是形参;
var a=100;
function fn(){
	//在函数私有作用域中定义的变量,不会给window新增键值对;
	var b=10;
	var a=99;
	console.1og(a);//100函数里面可以访问函数外面的变量
}
fn();
console.1og(b);
//函数执行会形成私有的作用域,会保护里面的私有变量不受外界的干扰;

let:let声明的变量不能给window新增键值对;
const声明的常量也不能新增键值对:

const e=1;
let d=1;
console.log(d);
  • 1.私有作用域是在全局作用域下形成创建的;在全局作用域中包含私有的作用域;
  • 2.全局作用域不能访问私有作用域下的私有变量,但是私有作用域能够访问全局作用下变量
  • 3.如果私有作用域存在该私有变量,那么就不再向外获取
function fn(){
	//私有作用域:是给函数体中的代码提供代码的运行环境的;
	//和全局作用域,
	///var a=1e;
	a=11;
	console.1og(10)
	}
fn();
console.1og(a);

上级作用域

  • 全局作用域;私有作用域,块级作用域;
  • 当获取变量所存储的值时,先找自己有没有,如果自己没有,那么向上一级作用域查找,如果上一级也没有,那么会继续向上查找,直到找到全局作用域,如果全局作用域也没有,会报错;这样一级一级向上查找形成一个作用域链;
  • 函数的上一级作用域跟函数在哪定义有关系,跟函数在哪执行无关:
var total=10;
  function sum(){
	var total=100;
	return function(){
	console.log(total);//undefined 100
	//var total=1000;
}
var f=sum();//f接受到了sum返回的小函数:
f();

变量提升1

debugger;添加断点

过程:
变量提升:在当前作用域下,代码执行之前,会首先把带var的和function进行提前声明,带var的只声明,不赋值,
但是function不仅声明,而且还要赋值(定义);
那么这种机制就是变量提升的机制;先对function变量提升

//声明:通知了当前作用域有这么一个变量;
console.log(a);//undefined 
console.log(b)
var a=100;b=99;

变量提升2

变量提升:在当前作用域下,代码执行之前,会首先把带var的和function进行提前声明,带var的只声明,不赋值,
function不仅声明,而且还要赋值(定义);
(注意:当如果function在块级作用域内时,只声明,不赋值了)

  • 1 等号的右边不进行变量提升,变量提升只发生在等号的左边;
  • 2 那么这种机制就是变量提升的机制;先对function变量提升
  • 3 return下面的代码要进行变量提升:return后面的代码时不进行变量提升的:
function fn(){
	console.1og(f); 
	return function f(){}
	console.log(f)
]
  • 4 自执行函数
    如果fn的属性值时一个自执行函数,那么当代码以键值对存储的时候(当代码执行到这一行时,自执行函数就会运行),并且把自执行函数的执行结果赋值给属性名fn
var obj={
fn:(function(){
	console.1og(100);
	//;
	return function(){
	})()
console.1og(200)
obj.fn()
  • 5.如果变量名和函数名重名,不再进行重复声明,但是要重新赋值;

fn();//4
function fn(){
	console.1og(1);
 function fn(){
	console.1og(2);
 fn();//4
function fn(){
	console.1og(3);
 fn=100;
 function fn(){
	console.1og(4);
 fn();报错
  • 6.自执行函数
    当代码执行到这一行时,先开辟一个空间地址,然后再执行:
var fn=function(){};
(function(){})();
  • 7.let声明的变量不进行变量提升;
    在代码执行之前,浏览器余先过滤当前作用域下的let声明的变量:let声明的变量不允许重名:(在同一个作用域)
console.log(num);
1et num=18e;
console.1og(num);

2. THIS

1.在全局作用域,this指向window;this 和window是同一块空间地址:

console.log(this);//window window.
alert()
this.alert();

2.给元素的事件绑定的函数中的this,指向了当前被点击的那个元素:

box.onclick=function(){
	//this:对象:
console.dir(this)}

3.普通函数执行:看函数执行前有没有“.”,如果要是没有,那么函数中的this指向window,如果有“.”,那么点前面是谁,this就指向谁:跟在哪执行有关

var obj={
	m:1,
	fn:function(){
	console.1og(this);
}//this就指向了obj;
var f=obj.fn;
console.1og(f==obj.fn);
obj.fn();
f();

4.回调函数中的this一般都指向window;
5.构造函数中的this指向实例
6.calll,apply,bind 可以改变this指向:

calll,apply,bind 语法

基于call/apply/bind可以改变函数中this的指向(强行改变)

Function.prototype ={
	call
	apply
	bind
}
  • call/apply
    • 第一个参数就是改变的THIS指向,写谁就是谁,特殊:非严格模式下,传递null/undefined指向的也是 window)

    • 唯一区别:执行函数,传递的参数方式有区别,call是一个个的传递,apply是把需要传递的参数放到数组中整体传递

 func.call([context],10,20) 
func.apply([context],[10,20])
  • bind
    call/apply都是改变this的同时直接把函数执行了,而bind不是立即执行函数,属于预先改变this和传递一些内容 =>"柯理化",返回了一个匿名函数,下次再执行这个匿名函数

call的练习题

function fn1() {
	console.log(1);
		}
function fn2() {
	console.log(2);
}
/* function call(context = window, ...args) {
			context.$fn = this;
			let result = context.$fn(...args);
			delete context.$fn;
			return result;
		} => AAAFFF000*/
fn1.call(fn2); //=>执行的是FN1 =>1
fn1.call.call(fn2); //=>执行的是Fn2 =>2
Function.prototype.call(fn1);  //空
Function.prototype.call.call(fn1); //=>1

总结:当执行一个call,则执行call前面的函数,
但是this指向括号中当执行两个及两个以上call,则执行括号中的函数
  • this练习题
var a=1;   3
var obj1={
	a:0,   1   2
	 fn1:(function(a){
	 a=1 2 3  4
	this.a=a; 
	a++; 2
	return function(){
		this  //obj   //Windows
		this.a=a++;   3
		console.1og(a)  3  4
	}
})(a)
};
 obj1.fn1();
  var fnl=objl.fn1;
  fn1(); 
  console.1og(a);  3
  console.1og(obj1.a);   2
var n=2: 4  8
var obj{
	n:3,  6
	fn:(function(n){
	 n=2   4
	n*=2;
	this.n+=2;
	var n=5: 6  7
	return function(m){
		m=3
		this.n*=2;
		console.1og(m+(++n)); 9   10
])(n)
var fn=obj.fn:
fn(3);
obj.fn(3);
console.1og(n,obj.n):    8  6
var num=1;  2   4
var obj={
	num:2,   4
	fn:(function(){
		this.num*=2; 
		num +=3;  NAN  1   3
		var num=1;  1
		 return function(){
			num+=2;  3  4  6   7
			this.num+=2
			console.1og(++num);   4   7
	})()
}; 
var f= obj.fn; 
f(); 
obj. fn(); 
console.1og(window. num, obj. num);   4   4

在这里插入图片描述

数据类型监测

1检测数据类型:typeof

  • 返回结果都是字符串,字符串中包含了对应的数据类型"number"/"string"/"boolean"/"undefined"/"symbol"/"object"/"function"

  • 【局限性】

    • typeof null => "object" null不是对象,它是空对象指针
    • 检测数据或者正则等特殊的对象,返回结果都是"object",所以无法基于typeof判断是数组还是正则

2 检测数据类型2/3:instanceof / constructor
类似的 检测某个实例是否属于这个类
他检测的底层机制:所有出现在其原型链上的类,检测结果都是

  • 【局限性】

    • 由于可以基于__proto__或者prototype改动原型链的动向,所以基于instanceof检测出来的结果并不一定是准确的
    • 基本数据类型的值,连对象都不是,更没有__proto__,虽说也是所属类的实例,在JS中也可以调取所属类原型上的方法,但是instanceof是不认的
arr.instanceof == Array   
arr.constructor==Array

3 检测数据类型4:Object.prototype.toString.call([value])
常用方法: ({}).toString.call([value])

格式:"[object 所属类信息]"
[objectObject/Array/RegExp/Date/Function/Null/Undefined/Number/String/Boolean/Symbol...]

  • 这种方式基本上没有什么局限性,是检测数据类型最准确的方式
({}).toString.call("stknf")

3.OOP

设计模式

单例设计模式

1.表现形式

var obj={
	XXx:XXX,
}

2.作用

  • 把描述同一件事务的属性和特征进行“分组、归类”(存储在同一个堆内存空间中),因此避免了全局变量之间的冲突和污染
  • 在单例设计模型中,OBJ不仅仅是对象名,它被称为”命名空间“[NameSpace]",把描述事务的属性存放到命名空间中,多个命名空间是独立分开的,互不冲突I
  • 单例设计模式命名的由来,每一个命名空间都是Js 中object这个内置基类的实例,而实例之间是相互独立互不干扰的,所以我们称它为单例:”
高级单例模式
  • 在给命名空间赋值的时候,不是直接赋值一个对象,而是先执行匿名函数,形成一个私有作用域AA(不销毁的栈内存),在AA中创建一个堆内存,把堆内存地址赋值给命名空间
  • 这种模式的好处:我们完全可以在AA中创造很多内容(变量or函数,哪些需要供外面调取使用的,我们暴露到返回的对象中(模块化实现的一种思想)
var nameSpace=(function(){
	function fn(){
	
	}
	return {
		fn:fn
	}
})()
单例设计模式应用
  • 模块化开发
    • 团风协作开发的时候,会把产品按照功能板块进行划分,每一个功能板块有专人负责开发
    • .把各个版块之间公用的部份进行提取封装,后期在想实现这些功能,直接的调取引用即可(模块封装)
var weatherRender=(function(){
	var fn=function(){
	};
	return{
		init:function(){
		fn();//=>调取自己模块中的方法直接调取使用即可
		skipRender.fn();//=>调取别人模块中的方法
})();
weatherRender.init();

工厂模式

工厂模式(Factory Pattern)

  • 1.把实现相同功能的代码进行“封装”,以此来实现“批量生产”(后期要实现这个功能,我们只需要执行函数即可)
  • 2.“低剩合高内聚”:减少页面中的冗余代码,提高代码的重复使用到
function createPerson(name, age){
var obj={}; 
	obj. name=name;
	obj. age=age;
	 return obj;
}

面向对象编程

面向对象编程,需要我们掌握:“对象、类、实例”的概念

  • 对象:万物对象 类:对象的具体细分(按照功能特点进行分类:大类、小类)
    实例:类中具体的一个事物(拿出类别中的具体一个实例进行研究,那当前类别下的其它实例也具备这些特点和特征)

js本身基于面向对象编程
本身就是根据Object基类

构造函数

基于构造函数创建自定义类(constructor)

  • 在普通函数执行的基础上"new
    xxx()”,这样就不是普通函数。执行了,而是构造函数执行,当前的函数名称之为“类名”,接收的返回结果是当前类的一个实例
  • 自己创造类名,最好第一个单词大写。
function Fn(){
	
}
var f =new Fn()

实例之间是独立分开,互不干扰

  • JS创建值两种方式
    1.字面量表达式
    2.构造函数模式
var obj1={ }
	var obj2=new Object()
	var obj={};//->字面量方式
	var obi=new Object();//->构造函数模式
	Fn() =>   普通函数执行
	=>不管是哪一种方式创造出来的都是object类的实例,而实例之间
	是独立分开的,所以 var xxx={)这种模式就是Js中的单例模式
	
 - 基本数据类型基于两种不同的模式创建出来的值是不一样的
  • 基于字面量方式创建出来的值是 基本类型值
  • 基于构造函数创建出来的值是 引用类型

NUM2是数字类的实例,NUM1也是数字类的实例,它只是JS表达数字的方式之一,都可以使用数字类提供的属性和方法

var numl=12; 
var num2=new Number(12); 
console. log(typeof numl);//=>"number"
console. log(typeof num2);//->"object"
  • 普通函数执行
    1.形成一个私有的作用域
    2.形参赋值
    3.变量提升
    4.代码执行
    5.栈内存释放问题

  • 构造函数执行原理
    构造函数机制

构造函数执行的细节问题
  • 自己写RETURN会发生什么

    • 1.return是的一个基本值,返回的结果依然是类的实例,没有受到影响
    • 2.如果返回的是引用值,则会把默认返回的实例覆盖,此时接收到的结果就不在是当前类的实例了
    • 3 在构造是不用参数,可以省略小阔号,意思也是创建实例
所属类类型检测
  • 1instanceof 检测某一个实例是否属于这个类
console. log(f instanceof Fn);//=>TRUE 
console. log(f instanceof Array);//=>FALSE
  • 2 in
    检测当前对象是否存在某个属性(不管当前这个属性是对象的私有属性还是公有属性,只要有结果就是true)
console. log('m' in f)://=>TRUE
console. log('n' in f);//=>FALSE
  • 3 hasOwnProperty
    检测当前属性是否为对象的私有属性(不仅要有这个属性,而且必须还是私有的才可以)
console. log(f. hasOwnProperty('m'));//=>true
console. log(f. hasOwnProperty('n'));

原型链设计模式

原型 prototype
【函数]
	普通函数、类(所有的类:内置类、自己创建的类)

【对象]
	普通对象、数组、正则、Math、arguments..…
	实例是对象类型的(除了基本类型的字面量创建的值)
	prototype的值也是对象类型的
	函数也是对象类型的

原型重要三句话

  • 1 所有的函数数据类型都天生自带一个属性:prototype(原型),这个属性的值是一个对象,浏览器会默认给开辟一个堆内存
  • .2 在浏览器给prototype开辟的堆内存中有一个天生自带的属性:constructor,这个属性存储的值是当前函数本身
  • 3 每一个对象都有一个_proto_ 的属性,这个属性指向当前实例所属类的prototype(如果不能确定它是谁实例,都是Object的实例)
原型链查找方法原理

在这里插入图片描述
原型链:
它是一种基于_proto向上查找的机制。当我们操作实例的某个属性或者方法的时候,首先找自己空间中私有的属性或者方法
1.找到了,则结束查找,使用自己私有的即可
2.没有找到,则基于_proto找所属类的prototype,如果找到就用这个公有的,如果没找到,基于prototype上的_proto继续向上查找,一直找到Object.prototype的原型为止,如果在没有,操作的属性或者方法不存在 返回undefined

举例
在这里插入图片描述

类的继承和多态

  • 1 类的多态:重载和重写 (TS才有这方面内容)
    JAVA中重载:函数名相同,但是传参类型、数量不同或者返回值不一样,这相当与把一个函数重载了 (JS中没有类似于后台语言中的重载机制:JS中的重载只的是同一个方法,根据传参不同,实现不同的业务逻辑) 重写:子类重写父类上的方法

  • 2 继承:子类继承父类中的属性和方法(JS中的继承机制和其它后台语言是不一样的,有自己的独特处理方式)
    ES5继承的三个方法

    • 方案一:原型继承 (B子类 => A父类),子类的原型指向父类的一个实例,B.prototype = new A();
 function A() {
			this.x = 100;
		}
A.prototype.getX = function getX() {
	console.log(this.x);
};

function B() {
	this.y = 200;
}
B.prototype.sum=function(){}
B.prototype = new A();
B.prototype.getY = function getY() {
	console.log(this.y);
};
let b = new B;

在这里插入图片描述
-

  • 方案二:CALL继承:把父类当做普通函数执行,让其执行的时候,方法中的this变为子类的实例即可

    • 1.只能继承父类中的私有属性(继承的私有属性赋值给子类实例的私有属性),而且是类似拷贝过来一份,而不是链式查找
    • 2.因为只是把父类当做普通的方法执行,所以父类原型上的公有属性方法无法被继承过来
function A() {
			this.x = 100;
		}
		A.prototype.getX = function getX() {
			console.log(this.x);
		};

		function B() {
			//CALL继承
			A.call(this);  //=>this.x = 100;  b.x=100;
			this.y = 200;
		}
		B.prototype.getY = function getY() {
			console.log(this.y);
		};
		let b = new B;
		console.log(b); 
  • 方案三: 寄生组合继承:CALL继承+变异版的原型继承共同完成的
    • CALL继承实现:私有到私有 原型继承实现:公有到公有
 function A() {
			this.x = 100;
		}
		A.prototype.getX = function getX() {
			console.log(this.x);
		};

		function B() {
			A.call(this);
			this.y = 200;
		}
		//=>Object.create(OBJ) 创建一个空对象,让其__proto__指向OBJ(把OBJ作为空对象的原型)
		B.prototype = Object.create(A.prototype);
		B.prototype.constructor = B;
		B.prototype.getY = function getY() {
			console.log(this.y);
		};
		let b = new B();
		console.log(b);
 * ES6创建类用class 
	 class A {
			constructor() {
				this.x = 100;
			}
			getX() {
				console.log(this.x);
			}                                     
		}
		//=>extends继承和寄生组合继承基本类似
		class B extends A {
			constructor() {
				super(100); //=>一但使用extends实现继承,只要自己写了constructor,就必须写super  <=> A.call(this,100)
				this.y = 200;
			}
			getY() {
				console.log(this.y);
			}
		}

4 闭包

堆栈内存回收机制

堆内存回收:

  • Gc垃圾回收机制;在浏览器内置了一个gc程序,这个程序会每隔一段时间执行一次;

    • 1.标记法:浏览器每隔一段时间要对所有的空间地址类型进行检测,检查该地址是否被占用:如果没有占用,那么久立即回收掉
    • 2.IE计数法:每当到间地址占用一次,该地址标识就会默认+1;如果不再占用,就会-1:如果标记计数为e:立即回收:
  • 什么条件不销毁的作用域,形成闭包

    • 1.返回一个引用的数据类型值
    • 2.返回的值需要被外界接受
    • 3 如果当前作用域有个空间地址,被函数体外的东西占用着,那么这个栈内存就不能销毁;olis占用了这个空间地址
function fn(){
	olis[i]. onclick=function(){
	}
}
 fn()
原文地址:https://www.cnblogs.com/smilestudio/p/12979683.html