前端基础原理知识汇总

一. 手写原理实现类

1. 防抖和节流

控制一些持续触发的事件执行的时机的一种方法。 比如: resize, scroll, mousemove等。

防抖(debounce)

防抖: 触发事件后指定时间内其函数只执行一次, 若在指定的时间内又触发了事件,则会重新计算函数的执行时间。(防抖函数也可分为:立即执行和非立即执行两种类型)

  • 非立即执行:
function debounce(fun, wait) {
    let timeout
    return function() {
        let context = this;
        let args = arguments;
        
        !!timeout && clearTimemout(time)
        timeout = setTime(()=>{
           fun.apply(context, args)
        }, wait)
    }
}

非立即执行: 在事件触发后等待自定义时间后才执行, 在等待时间内如果又触发了事件,则清空之前的计时器并重新计算函数执行时间。

  • 立即执行:
function debounce(fun, wait) {
   let timeout;
   return function() {
      let context = this;
      let args = arguments;
      !!timeout && clearTimeout(timeout);
      
      let callNow = !timeout;
      timeout = setTimeout(()=>{
         timeout = null;
      }, wait)

      !!callNow && fun.apply(context, args)
   }
}

立即执行: 触发函数后立即执行, 然后在设定的时间内不会触发事件。当超过设定时间时计时器将会重置计时器,允许后续第一个调用函数的事件执行。

节流(throttle)

节流: 连续触发事件但在指定时间内,只执行一次函数。 节流可以稀释函数的执行频率。(节流包含两种实现:时间戳和定时器)

  • 事件戳版
function throttle (func, wait){
   var previous = 0;
   return function(){
      let now = Date.now();
      let context = this;
      let args=arguments;
      if(now - previous > wait) {
         func.apply(context, args);
         previous = now;
      }
  }
}

在持续触发事件的过程中,函数会立即执行且每个自定义时间执行一次。

  • 定时器版
function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }
    }
}

该方式在持续触发事件的过程中, 函数不会立即执行,并且每个指定时间执行一次,在停止触发事件后,函数还会再执行一次

2. 深拷贝

堆和栈的区别

其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。堆和栈都是内存中划分出来用来存储的区域
栈(stack) 为自动分配的内存空间, 它由系统自动释放; 而堆(heap)则是动态分配的内存, 大小不定也不会自动释放。

  • 引用类型存放在堆中
    引用类型变量实际上是一个存放在栈内存的指针, 这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况进行特定的分配。
  • 基本类型存放在栈中
    基本类型的两个变量是两个独立互不影响的变量。

深拷贝简单实现

  • 浅拷贝实现
function cloneShallow(source) {
   var target = {}
   for(var key in source) {
      if(Object.prototype.hasOwnProperty.call(source. key)) {
         target[key] = source[key];
      }
   }
   return target
}
  • 简单深拷贝(在浅拷贝基础上加递归)
function cloneDeep(source) {
   var target = {}
   for(let key in source) {
      if(Object.prototype.hasOwnProperty.call(source, key)) {
         // typeof null === 'object'
         if(!!source[key] && typeof source[key] === 'object) {
            target[key] = cloneDeep(source[key])
         } else {
            target[key] = source[key]
         }
      }
   }
   return target
}

hasOwnProperty: 所有继承了Object 的对象都会继承到hasOwnProperty 方法。 该方法判断对象自身是否存在指定属性, 且该方法会忽略掉那些从原型上继承来的属性

3. 手写call, apply, bind 的实现

  • call(context, param1, param2,…)
  // 一个用于生成唯一值的Symbol 方法
  function mySymbol(obj) {
     let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)
     if(obj.hasOwnProperty(unique)) {
         return mySymbol(obj)
     } else {
         return unique
     }
  }

  Function.prototype.myCall = function(context) {
      context = context || window
      let fnName = mySymbol(context)
      context[fnName] = this // 给context 添加一个方法,指向this (this就是 “ obj.fnName.myCall() ” 中的fnName方法)
      // 处理参数, 去掉第一个参数this, 其它传入fn函数
      let args = [...arguments].slice(1) // 把[...arguments] 转成一个数组, slice 返回一个新数组
      let result = context[fnName](...args) // 执行fn
      delete context[fnName] // //删除挂载在借用对象上的fn属性
      return result
   }
  • apply(context, [.,.,…])
  // 一个用于生成唯一值的Symbol 方法
  function mySymbol(obj) {
     let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)
     if(obj.hasOwnProperty(unique)) {
         return mySymbol(obj)
     } else {
         return unique
     }
  }

  Function.prototype.myApply = function(context) {
      context = context || window
      let fnName = mySymbol(context)
      context[fnName] = this // 给context 添加一个方法,指向this (this就是 “ obj.fnName.myCall() ” 中的fnName方法)
      // 处理参数, 去掉第一个参数this, 其它传入fn函数
      let args = [...arguments].slice(1) // 把[...arguments] 转成一个数组, slice 返回一个新数组
      let result = context[fnName](...args) // 执行fn
      delete context[fnName] // //删除挂载在借用对象上的fn属性
      return result
   }
  • bind(context, ,)

特点: 函数调用改变this; 返回一个绑定this 的函数; 接收多个参数; 支持柯里化形式参数 fn(1)(2)

Function.prototype.bind = function(context) {
   // 返回一个绑定this的函数, 我们需要在此保存this
   let fnName= this;  // 这里this 就是需要bind的函数
   // 可以支持柯里化参数, 保存参数
   let  arg = [...arguments].slice(1)
   // 返回一个函数
   return function(){
      let newArgs = [...arguments]
      // 返回函数绑定this, 传入两次保存的参数
      // 考虑返回函数有返回值做了return
      return fnName.apply(context, arg.concat(newArgs))
   }
}

// 使用:
let fn = Person.say.bind(Person1)  // 返回一个绑定多个作用域的函数
fn()
fn(name, age, xxxx, .....)

4. jsonp 的实现

JSONP(JSON with Padding)它是一个非官方的协议。其跨域利用script的src属性, 这个属性不受同源策略影响,可以访问不同服务下的资源。

  • 先来看一个jsons 的client to server 端的简单实现:
// 客户端
<scritp>
    function callback(data){
		console.log(data);
	}
	var scriptDom = document.createElement('script');
	scriptDom.src = "http://localhost:8082/getdata?cb=callback";
	document.body.appendChild(scriptDom);
</script>

// server 端(基于express):
app.get('/getdata',function(req,res){
	//同步读取json文件
	var data = fs.readFileSync('server2/data.json').toString();
	var qs = url.parse(req.url).query;
	var cb = querystring.parse(qs).cb;
	var jsonp = cb+"("+data+")";
	res.send(jsonp);
}

如上例子允许后会在浏览器控制台输出: 服务端返回的 Object:{} 数据对象

  • JSONP 实现
//  JSONP 实现
function JSONP({
   url,
   params,
   callbackKey, // cb参数名
   callback       // 回调函数
}){
    // 唯一id, 不存在则初始化
    JSONP.callbackId = JSONP.callbackId || 1
    params = params || {}
    // 传递的callback 名,和厦门预留的一致
    params[callbackKey] = `JSONP.callbacks[${JSONP.callbackId}]`
    // 避免全局污染
    JSONP.callbacks = JSONP.callbacks || []
    // 按照id放置 callback
    JSONP.callbacks[JSONP.callbackId] = callback
    const paramsKeys = Object.keys(params)
    const paramString = paramKeys.map(key => `${key}=${params[key]}`).json('&')
    const script = document.createElement('script')
    script.setAttribute('src', `${url}?${paramString}`)
    document.body.appendChild(script)
    // id 占用,自增
    JSONP.callbackId ++
}

// 使用JSONP

JSONP({
   url: 'http://xxxxx/ajax/jsonp/xxxxxx',
   params: {
       key: 'test1',
   },
   callbackkey: '_cb',
   callback(result) {
       console.log(result.data)
   }
})
JSONP({
   url: 'http://xxxxx/ajax/jsonp/xxxxxx',
   params: {
       key: 'test2',
   },
   callbackkey: '_cb',
   callback(result) {
       console.log(result.data)
   }
})

JSONP 调用后通过控制台可以卡到请求都是:
http://xxxxxxx/ajax/jsonp/xxxxx?key=test1&_cb=JSON.callbacks[1]这样的,得到的 js 也是 JSON.callbacks1, 这样就避免回调命名冲突问题,也避免了全局域的污染
注: == 上面代码存在一个问题那就是参数部分如下情况:
params: {
a: ‘545&b=3’
b: ‘5’,
},
参数 a 中包含了b的值,其实用户这是希望a的值为: 545&b=3。 解决的办法,进行
URI编码==, encodeURIComponent(‘trdgd&b=2’) 的结果为: trdgd%26b%3D2
JSONP部分实现可改为

const paramString = paramKeys  
  .map(key => `${key}=${encodeURIComponent(params[key])}`)
  .join('&')

这里值得一提的是,由于最终的 URL 不能包含 ASCII 码以外的字符,所以其实当使用中文或者特殊字符时其实会被自动编码。而 +,空格,/,?,%,#,&,= 等字符在 URL 中则会出现歧义,只有手动编码后才能让服务器端正确解析

5. 实现一个New

new 操作符都做了些什么? 可以用四步来总结:

  • 创建一个空对象;
  • 链接到原型;
  • 绑定this值;
  • 返回新的对象

模拟一个new方法的实现:

// 一个new 的实现
function myNew(){
    // 创建一个空对象
    let obj = new Object()
    // 获取构造函数
    let Constructor = [].shift.call(arguments);
    // 链接到原型
    obj.__proto__ = Constructor.prototype;
    // 绑定this值
    let result = Constructor.apply(obj.arguments); // 使用apply, 将构造函数中的this指向新对象, 这样新对象就可以访问构造函数中的属性和方法。
    // 返回新对象
    return typeof result === 'object' ? result : obj // 如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象
}

// 使用测试
function Product(name, type) {
    this.name = name;
    this.type = type;
}

// 通过new 创建构造实例
let pro1 = new Product('礼盒', '水果');
console.log(pro1.name,  pro1.type) //  礼盒, 水果

// 通过myNew方法创建实例
let pro2 = myNew(People, '礼盒', '水果');
console.log(pro2.name, pro2.type); // 礼盒, 水果

二. 基础原理概念类

JS原型与原型链

在这里插入图片描述

JavaScript 原型

ES2019规范是当前最新的语言规范,可以作为本主题的权威素材来源。

  • prototype 的定义
    1. 定义描述
    在规范里, prototype被定义为:给其它对象提供共享属性的对象(prototype自己也是对象,只是被用以承担某个职能)。
    这里有个概念:prototype对象这种说法是一个简略的描述, 真实的描述是:“xxxx对象的prototype对象”(如果不跟其它对象产生关联,
    就构不成prototype这个称谓)

    2. 所有object对象都有一个隐式引用
    规范中明确描述了所有对象,都有一个隐式引用,它被称之为这个对象的prototype原型。
    什么叫隐式引用:
    在这里插入图片描述
    如上图所示,我们控制台输出了一个空对象,但可以发现有 proto 属性, 这意味这这个空对象被隐式挂载了另一个对象的引用,
    置于 proto 属性中。也就是说所谓隐式:是指不是由开发者亲自创建/操作。

prototype chain 原型链

在ECMAScript2019规范里,只通过短短的一句话,就介绍完了prototype chain。
原型链概念只是在原型这个概念基础上所作的直接推论。
也就是说,prototype 对象也有自己的隐式引用,有自己的prototype对象。
如此, 构成了对象的原型的原型的原型的…链条, 直到某个对象的隐式引用为null, 整个链条终止

三. 运转机制类

Html 请求到渲染展示运转流程

在这里插入图片描述

上图运转流程简述:

  • 浏览器中输入rul ,回车(浏览器会开启一个线程来处理这个请求)
  • DNS(可以理解为:域名与ip对应的库)解析。
    解析优先顺序:
  1. 查找浏览器自身的DNS
  2. 查找本地Host文件
  3. 查找无线路由器
  4. 发起DNS的系统调用,让快带运营商帮忙找
    只要上述4个步骤中有一个能通过域名解析到IP地址, 就会跳出DNS解析这步
  • 通过IP地址定位到服务器并发起TCP连接,进行“三次握手”、
  • 建立完成TCP/IP连接, 浏览器发送HTTP请求
  • 服务器处理请求,并返回结果(HTML页面)
  • 浏览器下载HTML文件, 设置缓存,关闭TCP连接。
    此过程中可定会有css, js, 图片等静态资源也会经过上述步骤
  • 最终根据HTML 生成界面。

TCP连接与关闭原则流程

  • TCP三次握手

  • TCP四次挥手

HTML渲染流程运转机制

  1. 遇到外联JS加载,渲染会停止并处于阻塞状态(等js 加载完成后继续后续加载及渲染)。
  2. CSS 文件下载过程中, 可以通过已加载的js 打印出标签,所以CSS文件的加载阻塞了DOM渲染,但没有阻塞DOM加载。
  3. 如果为外部Javascript添加deferasync属性,其下载就不会阻塞DOM其它内容的加载。
    defer(延迟脚本): 被标注defer属性的脚本需要立即下载,但要等到整个页面解析完毕后在按先后顺序执行。
    async(异步脚本): 和 defer 类似, 只是不保证脚本按顺序执行。

四. 常用算法

快速排序

function quickSort(arr) {
   if(arr.length<1) return arr;
   var poivtIndex = Math.floor(arr.length/2)
   var poivt = arr.splice(poivtIndex, 1)[0]
   var left = []
   var right = []
   for(var i=0; i<arr.length; i++) {
      if(arr[i]<poivt) {
         left.push(arr[i])
      } else {
         right.push(arr[i])
      }
   }
  
  return quickSort(left).concat([poivt], quickSort(right))
}

var arr = [2,3,4,6,1,5]
console.log(quickSort(arr))  // [1,2,3,4,5,6]

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值