js的再学习(三)

关于js语句中分号的问题

  • 一般语句下,可以在句尾省略分号:但是下面两种情况除外(可在行首加分号):
    • 小括号开头的前一条语句
    • 中括 号开头的前一条语句

原型以及原型链图解

  • 原型
    • 原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,实例对象__proto__属性,该属性指向构造函数的原型对象,函数对象有prototype属性,该属性指向函数对象的原型对象,原型对象有constructor属性,该属性指向构造函数。
      var p = new Person()的过程如下:
      1. var p = {}; 初始化一个对象p。
      3. p._proto_=Person.prototype;,将对象p的 __proto__ 属性设置为 Person.prototype
      4. Person.call(p,”张三”,20);调用构造函数Person来初始化p,主要执行constructor构造方法,初始化属性,并且绑定this
      5. 返回新对象
      	```
      
  • 显示原型prototype以及隐式原型__proto__
    • 函数对象不仅有__proto__(隐式原型)还有prototype显示原型
    • 实例对象只有__proto__隐式原型
  • 原型链其实 是对象的隐式原型链,用来查找属性值
    原型链

变量提升以及函数提升

  • 变量提升
    • 通过var 定义(声明)的变量,在定义语句之前就可以访问到,只不过值是undefined
    // 实际上变量的赋值操作分为两步,一步是声明变量,一步是赋值(使用`var` 赋值变量,因为es6后新增了`let, const` 声明变量的方式,不存在变量提升了)
    // 在解析代码时,会把当前代码块中的声明语句提前到代码块的最初位置
    // 而实际赋值还是之前赋值的地方,这样就导致有时我们可以在数据声明之前访问到(不过值是undefined)
    var a = 3
    function fu(){
    	console.log(a) // undefined
    	var a = 4
    }
    // 上面的语句可以看作下面的
    function fu(){
    var a;
    console.log(a)
    a =4
    }
    
  • 函数声明提升
    • 通过function声明式生产的函数,在之前就可以直接调用(和变量提升一样,会在代码块的顶部就把函数给声明了)
    fn()
    function fn(){
      console.log('fn')
    }
    // 通过变量声明及匿名函数定义的 不存在函数提升
    fn2()
    // 这个时候是变量提升,不是函数提升
    var fn2 = function(){
    }
    
  • 变量提升以及函数提升是如何产生的
    1. 在js中js引擎会优先解析var变量和function定义!在预解析完成后从上到下逐步进行!
    2. 解析var变量时,会把值存储在执行环境中,而不会去赋值,值是存储作用!例如:
      alert(a); var a = 2; 这时会输出undifiend,意思是没有被初始化没有被赋值!
    3. 在解析声明式function时会把函数整体定义,这也就解释了为什么在function定义函数时为什么可以先调用后声明了!其实表面上看是先调用了,其实在内部机制中第一步实行的是把以function方式定义的函数先声明了(预处理)
  • 函数提升变量提升先后问题
    • 预处理阶段,当函数名变量名重名时,优先保留函数]
  • 练习
    function a(){}
    var a;
    console.log(typeof a ) // function
    /------/
    if(!(b in window)){
    var b =1
    }
    console.log(b) // undefined
    /------/
    var c =1
    function c(c){
    	console.log(c)  // c is not a function
    }
    c(2)
    

执行环境上下文

  • 每次当控制器转到可执行代码的时候,就会进入一个执行上下文执行上下文是用于跟踪代码的运行情况。执行上下文可以理解为当前代码的执行环境,是在代码执行时产生的。
  • JavaScript中的运行环境大概包括三种情况
    • 全局执行上下文
    • 函数执行上下文
    • eval()
  • 代码在执行过程中会形成一个执行上下文栈

全局执行上下文(以及this的关系,执行环境上下文不等同于this) this 的值是通过当前执行上下文中保存的作用域(对象)来获取到的

  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
    • var 定义的全局变量==》undefined添加为window属性
    • function声明的全局函数==》赋值fun,添加为window的方法
    • this=》赋值(window)

函数执行上下文(函数调用时产生)

  • 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
  • 对局部数据进行预处理
    • 形参变量=》赋值(实参)=》添加为执行上下文的属性
    • arguments==》赋值(实参列表),添加为执行上下文的属性
    • var 定义的局部变量==》undefined,添加执行上下文的属性
    • function声明的函数=》fun,添加为执行上下文的方法
    • this=》赋值(调用函数的对象)

执行上下文栈

  • 在全局代码执行前,js引擎会创建一个栈来存储管理所有的执行上下文对象
  • 在全局执行上下文(window)确定后,将其添加到栈中
  • 在函数执行上下文创建后,将其添加到栈中(压栈)
  • 当前函数执行完成后,将栈顶的对象移出(出栈)
  • 当所有的代码执行完毕后,栈中只剩下window

执行上下文的练习

console.log('glabl',i)
var i = 1
foo(i)
function foo(i){
	if(i==4){
		return 
	}
	console.log('start',i)
	foo(i+1)
	console.log('end',i)
}
console.log('globl',i)
// 上面的执行顺序是,在执行上下文栈中共有五个执行上下文
// glabl undefined 
// start 1 
// start 2 
// start 3 
// end 3 
// end 2 
// end 1
// globl 1

作用域以及作用域链

  • 什么是作用域
    • 作用域是在运行时代码中的变量,函数和对象的可访问区间以及这个区域内代码书写的结构信息
    • 作用域在编码的时候就已经确定了,而不是和执行上下文(在函数调用时产生)一样
    • 作用域是静态的,只要代码定义好了就一直存在,且不会再变化。执行上下文是动态的,调用函数时创建,函数调用结束后就会自动释放
  • 作用域分为全局作用域以及函数作用域,当然在es6后处理了块级作用域
  • 块语句(大括号“{}”中间的语句),如 if 和 switch 条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域。
  • 所有末定义直接赋值变量自动声明为拥有全局作用域
  • 作用
    • 隔离变量:不同作用域下的同名的变量不冲突
  • 什么是作用域链
    • 作用域是分层的,内层作用域可以访问外层作用域的变量,如果内层作用域没有找到所需变量,内层作用域向上一层层查询外部作用域的变量,直到找到全局作用域。这种一层一层的关系,就是 作用域链
  • 练习
    var x = 10
    function fn(){
    	console.log(x)
    }
    function show(f){
    var x = 20
    f()
    }
    show(fn)
    // 最后结果是10
    var obj = {
        fn2: function(){
            console.log(fn2)
        }
    }
    obj.f2 // 会把f2这个函数体打印
    obj.fn2() //  fn2 is not defined 
    

作用域以及执行环境上下文的关系

  • JavaScript属于解释型语言,JavaScript的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样
    • 解析阶段
      • 词法分析
      • 语法分析
      • 作用域规则确定
    • 执行阶段
      • 创建执行上下文
      • 执行函数代码
      • 垃圾回收
  • JavaScript解析阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向执行时确定的。而作用域访问的变量是编写代码的结构确定的。
  • 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变
  • 一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。

闭包

  • 什么是闭包
    • 理解1:包含嵌套的内部函数
    • 理解2:包含被引用变量(函数)的对象
  • 如何产生闭包
    • 当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包
      	function fn1(){
      		var a = 2
      		function fn2(){
      			console.log(a)
      		}
      	}
      	fn1() // 外部函数执行才会产生闭包
      
  • 常见的闭包
    • 将函数作为另一个函数的返回值
      	function f1(){
      		var a= 2
      		function f2(){
      			a++
      			console.log(a)
      			}
      		return fn2
      	}
      	var f = f1()
      	f() // 3
      	f() // 4
      	f = null //闭包在不需要的时候,需要及时释放
      
    • 将函数作为实参传递给另一个函数调用
      	function showDealy(msg,time){
      		setTimeout(function(){
      			alert(msg)
      		},time)
      	}
      	showDelay('哈哈'2000)
      
  • 闭包的作用(作用)
    • 使函数内部的变量在函数执行完之后,仍然存在内存中(延长了局部变量的生命周期)
    • 让函数外部可以操作 到内部的数据
  • 使用闭包定义JS模块
    // 第一种方式
    function myModule(){
    	var msg = 'hhh'
    	function fn1(){
    		console.log(111,msg)
    		}
    	function fn2(){
    		console.log(222,msg)
    		}
    		return {
    		fn2:fn2,
    		fn2:fn1
    		}
    	}
    	// 第二种方式
    	(function(windwo){
    		var msg = 'hhh'
    		function fn1(){
    			console.log(111,msg)
    		}
    		function fn2(){
    			console.log(222,msg)
    		}
    		window.myModule = {
    		fn2:fn2,
    		fn2:fn1
    		}
    	})(windwo)
    
  • 闭包的缺点
    • 函数执行完,函数内部的局部变量没有释放,占用的内存时间会变长
    • 容易造成内存泄漏
    • 缺点的解决方式:能不用就不用,及时释放

内存溢出以及内存泄漏

  • 内存溢出
    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
  • 内存泄漏
    • 内存泄漏: 占用的内存没有及时释放
    • 内存泄漏积累多了就会容易导致内存溢出
    • 常见的内存泄漏: 意外的全局变量没有及时清理定时器以及回调函数没有及时释放闭包

关于闭包的练习

var name = 'window'
var object = {
	name:'object',
	getName:function(){
		return function(){
			return this.name
			}
		}
	}
object.getName()() // 输出 window
var name = 'window'
var object = {
	name:'object',
	getName:function(){
		var that = this
		return function(){
			return that.name
			}
		}
	}
object.getName()() // 输出 object

对象

创建对象的不同方式

  1. Object构造函数
    var p= new Object();
    p.name = 'Nike';
    p.age = 29;
    
  2. 字面量
    • 创建方式:是第一种创建方式的简写
      var Person = {};//相当于var Person = new Object();
      var Person = {
       name:'Nike';
       age:29;  
      }
      
    • 缺点:和第一种一样,创建多个具有相同属性的对象,会造成代码冗余
  3. 工厂模式
    • 创建方式
      function createPerson(name,age,job){
       var o = new Object();
       o.name = name;
       o.age = age;
       o.job = job;
       o.sayName = function(){
        alert(this.name); 
       };
       return o; 
      }
      var person1 = createPerson('Nike',29,'teacher');
      var person2 = createPerson('Arvin',20,'student');
      
    • 缺点: 通过工厂函数创建时,返回的是一个对象。那么我们就无法判断返回的对象究竟是一个什么样的类型
  4. 自定义构造函数方式
    • 创建方式如下:相比较于工厂模式,通过该模式可以识别的对象的类型
      function Person(name,age,job){
       this.name = name;
       this.age = age;
       this.job = job;
       this.sayName = function(){
       alert(this.name);
       }; 
      }
      var person1 = new Person('Nike',29,'teacher');
      var person2 = new Person('Arvin',20,'student');
      
    • 缺点:每个方法都要在每个实例上重新创建一遍,方法指的就是我们在对象里面定义的函数。如果方法的数量很多,就会占用很多不必要的内存
  5. 原型的方式
    • 创建方式
      function Person(){}
      Person.prototype.name = 'Nike';
      Person.prototype.age = 20;
      Person.prototype.jbo = 'teacher';
      Person.prototype.sayName = function(){
       alert(this.name);
      };
      var person1 = new Person();
      var person2 = new Person();
      person1.name ='Greg';
      alert(person1.name); //'Greg' --来自实例
      alert(person2.name); //'Nike' --来自原型
      
    • 缺点:原型上的属性是共享的,在创建时不能单独自定义自己的属性值
  6. 自定义构造函数+原型的方式
    • 创建方式
      function Person(name,age,job){
       this.name =name;
       this.age = age;
       this.job = job;
      }
      Person.prototype = {
       constructor:Person,
       sayName: function(){
       alert(this.name);
       };
      }
      var person1 = new Person('Nike',20,'teacher');
      
    • 通过构造函数方式创建属性,然后配合原型创建方法相结合的方式。就能解决原型模式以及构造函数模式的不足

继承的几种模式

  1. 原型链继承:让子类原型对象作为父类的实例继承父类方法,主要代码Son.prototype = new Parent();Son.prototype.constructor = Son
function Person(name,age){
	this.name = name
	this.age = age
}
Person.prototype.sayName = function(name){
	console.log(name)
}
function Student(price){
	this.price = price
}
Student.prototype = new Person() // 子类的原型变为父类的实例,继承父类的方法
Student.prototype.constructor = Student // 因为上面那条语句,改变了Student.prototype。
Student.prototype.sayPrice = function(price){
	console.log(price)
}
var stu1 = new Student()
var stu2 = new Student()
stu1.sayName() // 继承父类型的方法
stu1.name = 'name' // 可以改变原型
stu2.name = 'name2' // 
// 优点:所有子类的实例都能访问父类原型的方法
// 缺点:1. 父类构造方法中的属性被所有子类共享,子类更改属性,其他子类也受影响
//       2. 子类实例不能给父类型构造函数传参
  1. 构造函数继承,通过在子类改变子类实例的this指向为父类,继承父类的属性
function Person(name,age){
	this.name = name
	this.age = age
}
function Student(name,age.price){
	Person.call(this,name,age) // 通过改方式继承父类的属性
	this.price = price
}
var stu = new Student('222','2222',2222)
console.log(stu.name,stu.age,stu.price)
// 优点: 子类可以给父类构造函数传参 ,父类构造函数中的引用属性不会被共享
// 缺点:子类不能访问父类原型上定义的方法

  1. 组合继承(原型链实现对父类型对象方法的继承,利用构造函数初始化相同属性)
function Person(name,age){
	this.name = name
	this.age = age
}
Person.prototype.sayName = function(name){
	console.log(name)
}
function Student(name,age.price){
	Person.call(this,name,age) // 通过改方式继承父类的属性
	this.price = price
}
Student.prototype = new Person() // 子类的原型变为父类的实例,继承父类的方法
Student.prototype.constructor = Student // 因为上面那条语句,改变了Student.prototype。constructor为Person,所以需要修正constructor指向
Student.prototype.sayPrice = function(price){
	console.log(price)
}
// 优点
// 1. 可以使用父类原型上的方法
// 2. 子类可以给父类的构造函数中传递参数,创建只属于当前实例的属性
// 3. 父类构造函数中的属性不被共享
  1. 原型式继承
function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
// 缺点:
// 1. 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
// 2. 无法传递参数
  1. 寄生式继承:在原型式继承的基础上,增强对象,返回构造函数
function createAnother(original){
  var clone = object(original); // 通过调用 object() 函数创建一个新对象
  clone.sayHi = function(){  // 以某种方式来增强对象
    alert("hi");
  };
  return clone; // 返回这个对象
}
// 缺点:和原型式继承一样
  1. 寄生组合式继承:结合借用构造函数传递参数和寄生模式实现继承
function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
  prototype.constructor = subType;                    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  subType.prototype = prototype;                      // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

// 新增子类原型属性
SubType.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

  1. 混入方式继承多个对象
  2. ES6 class方式继承

进程以及线程

浏览器内核

  • 浏览器内核:支撑浏览器运行的最核心的程序
  • 不同的浏览器可能内核不一样
    • chrome ,safari :webkit
    • firefox: gecko
    • ie : trident
    • 360,搜狗等 :trident, webkit
  • 内核有很多模块组成
    • js引擎模块:负责js程序的编译与运行
    • html/css文档解析模块:负责页面文本的解析
    • DOM/CSS模块:扶着dom/css在内存中的相关处理
    • 布局和渲染模块:负责页面的布局和效果的绘制
    • 。。。
    • 定时器模块:负责定时器的管理
    • 事件响应模块:负责事件的管理
    • 网络请求模块: 负责ajax模块

定时器真是定时执行的吗

  • 定时器真是定时执行的吗
    • 定时器并不能保证真正的定时执行
    • 一般会延迟一点,意外情况下会延迟很久,主要情况要看事件循环机制的
  • 定时器回调函数是在分线程执行的吗
    • 在主线程中执行的,js是单线程的
  • 定时器是如何实现的
    • 事件循环模型

事件循环机制

H5中的web workers

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值