关于js的一切(updating...)

1.js原型链

构造函数:function Person(),Person()即为构造函数
原型对象:每一个构造函数都会带有一个Prototype属性,该属性为一个指针,指向了一个对象,即为原型对象。
实例对象:new Person(),new一个构造函数就会产生一个实例,实例对象有一个内部属性_proto_,指向了原型对象。实例能够访问该原型对象的所有属性和方法。
综上:三者关系为,每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指。即,实例通过内部指针可以访问到原型对象,而原型对象通过constructor指针,又可以找到构造函数。(constructor实质上是实例对象的一个属性)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述实例对象person,继承来自原型对象的属性值age和name,内部属性_proto_指向自己的原型对象(_proto_内部属性不可访问)。
原型对象上3个属性(新增的action,constructor,proto),action为新增的方法(因此实例对象继承action,能够执行action函数),contructor即为构造函数,_proto_指向最终的原型对象即为object(在js中,任何对象的顶端都是object)。
构造函数Person实际就是个函数,而其内部属性prototype指向自己的原型对象,而原型对象中的属性constructor指向构造函数,并因此不断的套圈循环 。

2.js事件循环机制

1.js是单线程的,且只有一个主线程和一个调用栈(先进后出)
在这里插入图片描述
按照先进后出的原则,分别压入调用栈的为,main.js(即为js代码块),b(),a()。所以输出即为a,b,c。
在调用栈中,前一个函数在执行的时候,下面的函数全部需要等待前一个任务执行完毕,才能执行。当任务很多的时候,效率极其低下。因此js引入任务队列的概念。
2.js将任务分为同步任务和异步任务
同步任务:在主线程上,前一个任务执行完后,才能执行接下里的任务。
异步任务:分为宏任务和微任务,不进入主线程,而进入任务队列中。直到任务队列通知主线程可以执行了,该任务才能执行。
常见宏任务有:script主代码块,(setTimeout,setInterval),setImmediate(NodeJs)
常见微任务有:promise,process.nextTick(NodeJs)
注:不同源的任务,会进入不同的任务队列,上述只有setTimeout和setInterval是同源的。
在这里插入图片描述
函数a为宏任务setTimeout,函数c为微任务Promise。
将整个script代码块看做是宏任务,宏任务内部执行同步任务,即b();进入b内部
遇到a宏任务push进宏任务队列中,输出‘b’,遇到c,Promise分为两步,一是构造promise对象,此操作为同步,而其回调为异步,因此先输出‘c’,再将其push进微任务队列中,再输出‘finish’。
执行完同步,优先执行微任务队列,输出’cc’。直到微任务队列没有任务后,
回头看宏任务队列只剩下一个setTimeout,执行setTimeout,输出‘a’。再看微任务队列中没有任务,结束循环。
顺序为 b,c,finish,cc,a。
上述即为js的事件循环机制,Event loop。
在这里插入图片描述
在这里插入图片描述

3.js词法作用域

作用域:源程序代码中定义变量的区域。分为静态作用域和动态作用域
静态作用域:也成词法作用域,函数的作用域在函数定义的时候就决定了。
动态作用域:函数的作用域是在函数被调用的时候决定的。
在这里插入图片描述
静态作用域在定义函数的时候就决定了,因此即便foo()是在bar()中调用,且bar中有同名变量,foo()所输出的value仍为1,而不是局部同名变量2。

4.js的执行上下文栈,变量对象和活动对象

执行上下文栈: js可执行代码分为全局代码,函数代码,eval代码。而当我们执行到一个函数时,所做的准备工作,即为“执行上下文”,用于管理n个执行上下文的栈即为“执行上下文栈”。

变量对象:每一个执行上下文都会分配一个变量对象,变零对象由变量和函数声名构成。它保存了当前作用域的所有函数和变量。(只有函数声名会被加入到变量对象中,而函数表达式并不会)
在这里插入图片描述
在函数声名的方式下,a会被加入到变量对象中,所以在当前作用域下能够打印出a。
在函数表达式下,b作为变量加入到变量对象中,而_b作为函数表达式则不会加入,
因此能输出b,而不能输出_b。

关于全局变量的初始化: 当js编译器开始执行的时候会初始化一个Global Object用于关联全局的作用域。对于全局环境而言,global object就是变量对象(variable object)。变量对象对于程序而言是不可读的,只有编译器才有权访问变量对象。在浏览器端,global object被具象成window对象,也就是说 global object === window === 全局环境的variable object。因此global object对于程序而言也是唯一可读的variable object。

活动对象: 当函数被调用时,一个活动对象就会被创建并且分配给执行上下文
活动对象由特殊对象argument初始化而成,随后,他被当做变量对象用于变量初始化。
在这里插入图片描述
如上图代码,当a被调用时,在a的执行上下文会创建一个活动对象AO,并且被初始化为AO = [arguments],随后AO又被当做变量对象VO进行变量初始化,此时VO = [arguments].concat([name,age,gender,b]);

执行过程
1.进入执行上下文
2.代码执行。
在进入执行上下文前,正如之前所说的会创建一个活动对象AO,这个AO被初始化为
在这里插入图片描述
如果有传入实参,则有值,反之,则为undefined。
在进入上下文时,此时的AO(活动对象) === VO(变量对象 )。
变量对象会包括:
1.函数的所有形参 (如果是函数上下文):
由名称和对应值组成的一个变量对象的属性被创建
没有实参,属性值设为 undefined
2.函数声明:
由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
如果变量对象已经存在相同名称的属性,则完全替换这个属性
3.变量声明:
由名称和对应值(undefined)组成一个变量对象的属性被创建;
如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

举个例子
在这里插入图片描述

在执行时的AO = {									
	arguments:{
		0:1,
		length:1
	}
	a:1,
	b:undefined,
	c:reference to function c(){},		
	d:undefined
}
在执行后的AO = {
	arguments:{
		0:1,
		length:1
	}
	a:1,
	b:3,
	c:reference to function c(){},
	d:reference to FunctionExpression "d"
}

解释:执行foo(1)时,在创建AO时,已经将形参a的值赋值,因此a的值初始化为1。
遇到变量声明,添加变量名–undefined;
遇到函数声明,添加函数名–function-object;
(由于d为函数表达式并非函数声明,因此初始化时将被视为变量声明,而非函数声明)。

总结:
1.全局上下文的变量对象初始化是全局对象

2.函数上下文的变量对象初始化只包括 Arguments 对象

3.在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值

4.在代码执行阶段,会再次修改变量对象的属性值

function foo() {
    console.log(a);
    a = 1;
}

foo(); // a is not defined

function bar() {
    a = 1;
    console.log(a);
}
bar(); // 1

执行foo,输出a时,a还未被声明,因此AO中并没有a这个属性名,所以报错 a is not defined。
执行bar,输出a时,a已经被声明,因此输出为1。
将foo修改为
function foo() {
    console.log(a);
    var a = 1;
}
foo的输出为undefined。这是由于var关键字具有变量提升,实际上foo为
function foo() {
	var a;
    console.log(a);
    a = 1;
}
因此,在初始化AO时,加入了a这个变量名,对应的值为undefined。而在执行时,按照从上而下的顺序,输出a时
a并没有被赋值为1,因此输出为undefined。
同理,将var改为letconst,仍为报错,即AO中不存在a这个属性,因为他们不具备变量提升,只有var有。
console.log(foo);

function foo(){
    console.log("foo");
}

var foo = 1;
//输出为函数foo。
执行上下文时,优先处理函数声明,再处理变量声明,所以上述代码为:
var foo;
foo = function(){
	console.log("foo")
}
foo = 1;
其实就是变量声明要让着函数声明,即使名字一样,也不能覆盖。
但同名变量声明会覆盖之前的,同名函数声明也会覆盖之前的。

4.5 .对执行上下文的补充和总结

在4中,我主要是是对执行上下文的内部做的总结以及代码测试,所以导致内容过长,理解不清晰,在4.5重新对执行上下文做一个更加明确的定义。

  1. 浏览器首次加载脚本,它将创建全局执行上下文,并压入执行栈栈顶(不可被弹出)
  2. 然后每次进入其他作用域都将创建对应的执行上下文并把它压入执行栈的顶部
  3. 一旦对应的上下文执行完毕,就从栈顶弹出,并将上下文控制权交给当前的栈
  4. 这样依次执行,最后回到全局执行上下文

执行上下文包括

  1. 变量对象
  2. 作用域链
  3. this(this是执行上下文环境的属性,不是变量对象的属性)
    1. this没有类似于变量一样一个向上搜寻的过程
    2. 代码中有this,这个this就直接从当前上下文直接拿,不会从作用域链中寻找
    3. this的值取决于进入上下文的情况
    4. 对于global context(全局上下文),this就是全局对象
    5. 对于function context,this的指向会根据每次函数的调用而变化。this由每次的caller提供,caller是通过表达式[call expression]产生,也就是这个函数如何被激活调用的。

5.js的闭包

js的闭包其实就是:闭包是指那些能够访问自由变量的函数
ECMAScript中,闭包指的是:
1.从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
2.从实践角度:以下函数才算是闭包:
I.即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
II.在代码中引用了自由变量

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();
执行过程:
1.进入全局代码,创建全局上下文,压入执行上下文栈
2.全局执行上下文初始化
3.执行checkScope函数,创建checkScope函数执行上下文,checkScope执行上下文被压入执行上下文栈
4.checkScope上下文初始化,创建变量对象,作用域链,this5.checkScope函数执行完毕,checkScope执行上下文被执行上下文栈中弹出
6.执行f函数,创建f函数执行上下文,f执行上下文被压入执行上下文栈
7.f执行上下文初始化,创建变量对象,作用域链,this8.f函数执行完毕,f函数执行上下文从上下文栈中弹出。

当执行f函数时,checkScope函数上下文已经从执行上下文栈中弹出,
理论上f是读不到checkScope作用域下的scope值。
而f上下文实际上维护着作用域链
fContext = {
	scope:[AO,checkScopeContext.AO,globalContext.VO]
}
这条作用域链,会让f从自己的AO出发,直到globalContext.VO,直到找到scope这个值停止。
如果checkScope.AO中不存在scope,那它就会访问到globalContext.VO中的scope(即global scope)
即便是checkScopeContext被销毁,其AO仍将保存在f函数的作用域链中。
这实际上就是闭包。

举个例子

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();
//
data[0]的上下文为:
data[0]Context = {
	Scope:[AO,globalContext.VO]
}
在这个function内,并没有定义i,因此它会找到globalContext.VO中的i,
但我们知道只有当执行上下文的时候,function内i的值才会被赋值 ,而此时globalContext.VO.i = 3了,
所以data[0],data[1],data[2]所有的输出均为3。此时你可认为全局变量i即为自由变量。
那么如何达到我们想要的效果呢?
很简单,就是在其两者中间创造一个自由变量
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
其中后面加了(i)代表了我执行了这个匿名函数,并传入了初始值i,
也就是匿名函数在for循环内已经在不断创建上下文,并拥有 i:i键值对。
因此他两者就形成了一个闭包。
data[0]Context = {
	Scope:[AO,匿名函数的上下文.AO,globalContext.VO]
}

6.js的this

简单来说,this永远指向一个对象,且永远指向其直接调用者。
1.在非严格模式下,this默认指向window,而严格模式下,this为undefined。
2.作为对象调用,指向直接调用的对象
3.作为函数调用,在非严格模式下指向window
4.在new操作符下,指向创造的实例对象
5.可被apply,bind,call改变指向obj。

7.js的函数柯里化,偏函数,惰性函数

函数柯里化:柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。
偏函数:局部应用则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。
惰性函数:用于优化被多次调用的函数

var t;
function foo() {
    if (t) return t;
    t = new Date()
    return t;
}
上述代码缺陷:污染全局变量,且每次调用都要进入if语句判断
1.使用闭包
var foo = (funciton({
	var t;
	return fuction(){
		if (t) return t;
	    t = new Date()
	    return t;
    }
})()
缺陷:每次仍然需要调用if语句判断
2.使用惰性函数
var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
};
在函数内部重写函数,即形成了闭包,避免污染了全局,
同时也保证了每次调用不用进行if判断,因为返回的是foo函数,而非 t。

8.js的callee和caller到底是个啥

callee是arguments的属性,他是正在执行的函数的引用。
caller是调用该函数的函数的引用,也就是this的指向。

9.js的defineProperty和proxy

Object.defineProperty(obj, prop, descriptor)(ES5):

  • 该方法可在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
  • 对于每一个属性,都有数据描述符和存取描述符两种方式,且只能是其中一种。
  • 对于所添加的每一个属性,其都可为该属性添加getter,setter方法。例如:`
  var obj = {};
  var value  = null;
  Object.defineProperty(obj,"count",{
     configurable:true,      //描述符可修改?
     enumerable:true,        //对象可枚举?
     //数据描述符
     value:1,                //初始值
     writable:false,         //该属性可被赋值运算符修改?
     //存取描述符
     get:function(){
         console.log('get 了');
         return value;
     },
     set:function(val){
         console.log('set 了');
         value = val;
     }
  })
 obj.count = 5;
 console.log(obj.count);
 这里以存取描述符为例(因为我们要对其进行监听)
 可以看到当我们给count赋值,调用了set方法;输出count,调用了get方法。
 由此可见,当我们使用或者修改count属性的时候,则都可以执行一些自定义操作,这就是监听。
 当然,监听属性的变化有啥用呢?
 现假设有一个需求,当我们点击一次button,lablel的数值就+1
 <div id = "demo">1</div>
 <button id = 'button'>+</button>
 <script>
     document.getElementById('button').addEventListener('click',function(){
         var num = + document.getElementById('demo').innerHTML
         document.getElementById('demo').innerHTML = num + 1;

     });
  </script>
  我们可能很容易想到这样去做,但是如果改成defineProperty呢
  <div id = "demo">1</div>
   <button id = 'button'>+</button>
   <script>
      var obj = {};
      var value  = null;
      Object.defineProperty(obj,"count",{ 
          get:function(){
              return value;
          },
          set:function(val){
              value = val;
              document.getElementById('demo').innerHTML = value;
          }
      })
     document.getElementById('button').addEventListener('click',function(){
          obj.count +=1;
      });
   </script>
	这就是vue2.0的响应式的原理。

proxy(es6):

  • 和defineProperty不同,proxy可以定义多种拦截行为,且代理的是整个对象,而不是属性
  var obj = {}
  var proxy = new Proxy({},{
      get: function(obj, prop){
          console.log('get 了')
          return obj[prop];
      },
      set: function(obj, prop, value){
          console.log('set 了');
          obj[prop] = value;
      }

  });
  proxy.name = 10;
  console.log(proxy.name);
  这里很容易看出区别,proxy实际上是创建了一个实例。
  而且我监听不再是一个单一的属性,而是整个对象,对象内的任意一个属性发生改变,都可以执行回调。

10.箭头函数和普通函数的区别

箭头函数,其实就是普通函数的简化版,其实也是匿名函数

  1. 不能作为构造函数,不能被new
  2. 不绑定arguments,用rest参数代替
  3. 不绑定this,以其上层上下文的this代替。指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this

11.前端构建工具Webpack

Webpack是一种前端资源构建工具。它主要功能就是分析模块依赖,并将项目文件打包成静态资源。

  • 编译开发环境的代码,生成兼容性代码。(例如babel编译es6的语法)
  • 分析模块依赖,将零散的模块文件打包合并。

基本工作流程:

  1. 初始化参数。根据配置文件初始化配置
  2. 初始化编译。加载配置的插件,初始化编译器。
  3. 确定入口。将配置文件中标记的每一个入口起点作为构建一个依赖图的起点。
  4. 模块编译与依赖分析。从每一个入口起点出发,根据引用关系(import,require等)递归构建依赖图。
  5. 打包分块。将每一个依赖图合并为一个或多个分块(chunk),分块中包含对应的资源。
  6. 输出。将打包的chunk组创建为对应的bundle,生成到目标位置。
基本配置
module.export = {
  mode: 'production',   // 设置模式为生产环境
  entry: './index.ts',  // 单个入口
  resolve: {
    // 设置支持的扩展名
    extensions: ['.js', '.ts', '.tsx', '.json']
  },
  module: {
    rules: [
      // 配置 ts 和 tsx 的加载器
      {
        test: /\.(ts|tsx)?$/,     // 匹配 .ts 和 .tsx 文件
        loader: 'ts-loader',      // 使用 ts-loader 加载器
        exclude: /node_modules/   // 排除 node_modules 目录
      },
      // 配置 CSS 样式文件的加载器
      {
        test: /\.css$/,           // 匹配 .css 文件
        loader: 'css-loader',     // 使用 css-loader 加载器
        exclude: /node_modules/   // 排除 node_modules 目录
      }
    ],
  },
  externals: {
    "react": "React",             // 将 react 标记为扩展依赖
    "react-dom": "react-dom"      // 将 react-dom 标记为扩展依赖
  },
  output: {
    filename: 'index.js',         // 定义输出文件名
    path: path.resolve(__dirname, "dist")  // 定义输出目录
  },
  // 添加插件
  plugins: [
      new CleanWebpackPlugin()    // 在编译前清空输出目录
  ]
};

上述代码为单页面打包(webpack默认模式),页面数量和入口文件的数量相同;若要实现多页面打包,将entry设置为数组即可。

Loader(加载器)
Loader用于转换模块的源代码,通常使用npm管理,需要安装。由于webpack本身并不具备解析所有资源类型的能力,因此需要各种loader完成这一任务 。
常用的loaders:

  • js兼容性处理。babel-loader(将es6转换为es5)
  • css样式文件。css-loader(加载解析CSS),style-loader(将css注入js)
  • js语法检查。eslint-loader
  • 资源文件。file-loader和url-loader

Plugin(插件)
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务

Webpack 与 gulp 的区别

  • glup是一种基于流(stream)的自动化构建工具,通过配置task生成项目的构建流水线
  • 从打包过程来看,Webpack是基于实际的依赖关系,而gulp是基于项目的物理结构。
  • 从目标导向上看,Webpack是偏向于模块化部署,而glup偏向于产品管理。
  • 从配置角度看,Webpack是基于入口起点,而glup是基于流的。

12.js垃圾回收机制

什么是内存泄漏: 不再用到的内存,如果没有及时释放,就叫内存泄漏
什么是垃圾: 一般来说没有被引用的对象就是垃圾。
js常用两种垃圾回收规则是:

  • 标记清除
    垃圾回收期在运行的时候会给存储在内存中所有变量都加上标记,
    然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记
    之后被加上标记的变量都将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量
  • 引用计数
    跟踪记录每个值被引用的次数,当一个值被引用,次数+1,减持时-1,下次垃圾会回收期会回收次数为0的值的内存。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值