zoukankan      html  css  js  c++  java
  • 闭包、作用域、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()
    
  • 相关阅读:
    jq focus 在火狐(Firefox)下无效
    将自己的项目上传到github保管
    $(window).height(),在火狐下面获取的高度并不是可视区域的高度
    [转载]跨域iframe高度自适应
    用css改变默认的checkbox样式
    js,jq新增元素 ,on绑定事件无效
    xshell配色Solarized Dark
    记一次给公司服务器装第二块硬盘的经历
    【shell编程基础3】shell编程的组合应用之二:管道及其命令
    【shell编程基础2】shell组合应用之一:重定向和逻辑
  • 原文地址:https://www.cnblogs.com/smilestudio/p/12979683.html
Copyright © 2011-2022 走看看