JS知识点

1、预编译

1.创建AO对象

2.找形参和变量的声明,作为AO对象的属性名,值是undefined

3.实参和形参相统一

4.找函数声明,会覆盖变量的声明

2、this指向

谁调用指向谁

var name = 222
var a = {
  name: 111,
  say: function () {
    console.log('this', this)
    console.log(this.name)
  }
}
​
var fun = a.say
fun()  //222
a.say()//111
​
var b = {
  name: 333,
  say: function (fun) {
    console.log('this', this)
    fun()
  }
}
b.say(a.say)//222
b.say = a.say
b.say()//333

3、箭头函数中的this

1.箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定

2.箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this.正是因为它没有this,所以也就不能用作构造函数

var x = 11;
var obj = {
    x: 22,
    say: ()=>{
        console.log(this.x);
    }
}
obj.say()//11

4、深浅拷贝

浅拷贝:浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

function shallowCopy(obj){
    var target = {}
    for (var i in obj) {
        if(obj.hasOwnProperty(i)){
            target[i] = obj[i]
        }
    }
    return target
}

深拷贝:

方法一:

function deepClone(obj){
    var cloneObj = new obj.constructor()
    var cloneObj = {}
    if (typeof obj !== 'object' || oldObj === null) {
        return obj;
    }
    if(obj instanceof Date) return new Date(obj)
    if(obj instanceof RegExp) return new RegExp(obj)
    for (var i in obj){
        if(obj.hasOwnProperty(i)){
             cloneObj[i] = deepClone(obj[i])   
        }
    }
    return cloneObj
}

方法二:

JSON.parse(JSON.stringify(obj))

缺点:Date,正则,函数会失效

js的拷贝和原数据是否指向同一对象第一层数据为一般数据类型第一层数据不是原始数据
赋值改变会使原始数据一同改变改变会使原始数据一同改变
浅拷贝改变不会使原始数据一同改变改变会使原始数据一同改变
深拷贝改变不会使原始数据一同改变改变不会使原始数据一同改变

5、节流防抖

/**
 * @description 函数节流
 * @param {Function} fn 需要进行节流操作的事件函数
 * @param {Number} interval 间隔时间
 * @returns {Function}
 */
export function throttle(fn, interval = 500) {
  let last = 0;
​
  return function () {
    const context = this;
    const args = arguments;
​
    let now = Date.now();
    if (now - last > interval) {
      last = now;
      fn.apply(context, args);
    }
  };
}
/**
 * @description 函数防抖
 * @param {Function} fn 需要进行防抖操作的事件函数
 * @param {Number} delay 延迟时间
 * @returns {Function}
 */
export function debounce(fn, delay = 1000) {
  let last = 0;
  let timer = null;
​
  return function () {
    const context = this;
    const args = arguments;
​
    let now = Date.now();
​
    if (now - last < delay) {
      clearTimeout(timer);
      time = setTimeout(() => {
        fn.apply(context, args);
        last = now;
      }, delay);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}
​

6、js的作用域

1.全局作用域

  • 全局作用域在页面打开时被创建,页面关闭时被销毁

  • 编写在SCRIPT标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到

  • 在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用

  • 全局作用域中声明的变量和函数会作为window对象的属性和方法保存

2.函数作用域

  • 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁

  • 每调用一次函数就会创建一个新的函=数作用域,他们之间是互相独立的

  • 在函数作用域中可以访问到全局作用域中的变量,在函数外无法访问到函数作用域中的变量

  • 在函数作用域中访问变量,函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域

7、内存泄漏

闭包

意外的全局变量

被遗忘的定时器

脱离DOM的引用

8、es6新特性

1.const ,let

let: 声明在代码块内有效的变量。

const: 声明一个只读的常量(一但声明,这个值不能被改变,对于引用类型,是引用的地址不能被改变,声明时必须赋值)

let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了:

2.解构赋值

按照一定模式从数组或对象中提取值,然后对变量进行赋值(先提取,再赋值)

3.模板字符串

${}中可以使用任意的javaScript表达试、运算、引用对象属性、函数调用等。结果是其返回值。

可以换行,但是所有的空格和换行会被保留。

4.函数的扩展

5.数组的扩展

  1. 扩展运算符。

  2. 用于替代数组的apply

  3. Array.from()将类数组转为数组

  4. 实例的方法

    • find()``findIndex()找出第一个符合条件的成页/下标(位置)

    • entries()``keys()``values() 用于遍历数组。(配合for...of)

    • includes() 是否存在指定无素(返回布尔值)

call apply bind的区别: 用于改变this的指向, 第一个参数为this指向的对像,后面的参数是作为函数的参数。 区加在于:call apply 会即调用,而bind是生成一个等调用的函数。call bind参数是一个个用逗号罗列,而apply 是传入一个数组。

6.箭头函数

ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;

  • 不需要 function 关键字来创建函数

  • 省略 return 关键字

  • 继承当前上下文的 this 关键字

7.for...of,for...in

for...of 用于遍历一个迭代器

let letters = ['a', 'b', 'c'];
letters.size = 3;
for (let letter of letters) {
  console.log(letter);
}
// 结果: a, b, c

for...in 用来遍历对象中的属性

 let stus = ["Sam", "22", "男"];
 for (let stu in stus) {
   console.log(stus[stu]);
  }
// 结果: Sam, 22, 男

8.class继承

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

9、手写map

function map (arr, mapCallback){
    if(!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function'){
        return []
    } else {
        let result = []
        for (let i = 0,len = arr.length;i<len;i++){
            result.push(mapCallback(arr[i],i,arr))
        }
        return result
    }
}

10.event-loop(事件循环机制)

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

三部分组成:调用栈,微任务队列,消息队列(宏任务)

Javascript单线程任务被分为同步任务异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

首先把整个script当成宏任务执行,碰到同步代码直接执行,微任务放在微队列中,宏任务放在宏队列中,执行完后在执行微队列中的任务,全部执行完后在执行宏队列中的任务,一直这样循环,直到全部执行完

11.数组的扁平化

多维数组转化为一维数组

const arr = [1,[2,[3,[4,5]]],6]
//1.数组自带的方法
console.log(arr.flat(Infinity))
//2.利用正则表达式
const res2 = JSON.stringify(arr).replace(/\[|\]/g,'')
console.log(res2.split(','))
//3.正则改良版
const res3 = JSON.parse('[' + res2 + ']')
//4.递归实现
const array = []
const fn = (arr) => {
    for (let i = 0;i<arr.length;i++){
        if(Array.isArray(arr[i])){
            fn(arr[i])
        } else {
            array.push(arr[i])
        }
    }
}
//5.reduce
const newArr = (arr) => {
    return arr.reduce((pre,cur)=> {
        return pre.concat(Array.isArray(cur) ? newArr(cur) : cur)
    },[])
}
console.log(newArr(arr))

12.数组去重

1.es6去重

function unique(arr){
    return Array.from(new Set(arr))
}

2.双重for循环

function unique(arr){
 	for(var i=0; i<arr.length;i++){
 		for(var j=i+1; j<arr.length;j++{
 			if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
  				arr.splice(j,1);
  				j--;
 			}
 		}
 	}
	return arr;
}

3.map

function unique(arr){
    let map = new Map()
    let newArr = []
    for(let i=0;i<arr.length;i++){
        if(map.has(arr[i])){
            map.set(arr[i],true)
        } else {
            map.set(arr[i],false)
            newArr.push(arr[i])
        }
    }
    return newArr
}

13.Promise

  1. Promise的状态一经改变就不能再改变。

  2. .then.catch都会返回一个新的Promise

  3. catch不管被连接到哪里,都能捕获上层的错误。

  4. Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)

  5. Promise.then 或者 .catch 可以被调用多次, 当如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。

  6. .then 或者 .catchreturn 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。

  7. .then.catch 返回的值不能是 promise 本身,否则会造成死循环。

  8. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

  9. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch.then第二个参数的简便写法。

  10. .finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。

11..all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。

12..race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

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

1.this指向不同,箭头函数this本身没有prototype,即没有this,但它会在上下文查找,一旦确定不在更改,普通函数this指向调用它的对象

2.箭头函数不能用于构造函数,不能使用new,普通函数可以用于构造函数,以此创建对象实例

3.箭头函数全部都是匿名函数

(箭头函数没有prototype(原型),所以箭头函数本身没有this 箭头函数的this在定义的时候继承自外层第一个普通函数的this。 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象) 箭头函数本身的this指向不能改变,但可以修改它要继承的对象的this。 箭头函数的this指向全局,使用arguments会报未声明的错误。 箭头函数的this指向普通函数时,它的argumens继承于该普通函数 使用new调用箭头函数会报错,因为箭头函数没有constructor 箭头函数不支持new.target 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名 箭头函数相对于普通函数语法更简洁优雅)

15.new操作符的过程

new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。

16.为什么JavaScript是单线程?

JavaScript的单线程主要和它的用途是相关的,它是浏览器脚本语言,主要用途是与用户互动和操作dom。如果不是单线程的话就会带来很复杂的问题,比如js假如同时有两个线程,一个线程在某个dom节点上添加了内容,另一个删除了这个节点,那浏览器就不知道以那个线程为准了。不过在HTML5提出了Web Worker(解决页面阻塞)标准,允许JavaScript脚本创建多个线程,但是子线程是完全受主线程控制的,且不得操作dom。还是没有改变JavaScript单线程的本质。

为什么JavaScript是单线程?

JavaScript的单线程主要和它的用途是相关的,它是浏览器脚本语言,主要用途是与用户互动和操作dom。如果不是单线程的话就会带来很复杂的问题,比如js假如同时有两个线程,一个线程在某个dom节点上添加了内容,另一个删除了这个节点,那浏览器就不知道以那个线程为准了。不过在HTML5提出了Web Worker(解决页面阻塞)标准,允许JavaScript脚本创建多个线程,但是子线程是完全受主线程控制的,且不得操作dom。还是没有改变JavaScript单线程的本质。

深浅拷贝

  • 浅拷贝:只拷贝一层,更深层次对象级别的只拷贝引用地址

    1. for in 循环

    2. for(let key of Object.keys(obj)) Object.keys(obj): [a,b]

    3. for(let [key,value] of Object.entries(obj)) Object.entries(obj): [["a",1],["b",2]]

    4. getOwnPropertyNames + getOwnPropertyDescriptor + defineProperty

    5. Object.assign(target,source).

    6. [...]扩展运算符

//浅拷贝
let obj = {
    a:1,
    b:2
}
//一般写法 es3
function simpleClone1(obj){
    let cloneObj = {}
    for(let i in obj){
        cloneObj[i] = obj[i]
    }
    return cloneObj
}
let cloneObj = simpleClone1(obj)
console.log(obj1)
//es6 写法
function simpleClone2(obj){
    let cloneObj = {}
    //可以这样
    // for(let key of Object.keys(obj)){ // Object.keys(obj): [a,b]
    //     cloneObj[key] = obj[key]
    // }
    
    //或者
    for(let [key,value] of Object.entries(obj)){ //Object.entries(obj): [["a",1],["b",2]]
        cloneObj[key] = value
    }
    return cloneObj
}
//es5的写法 getOwnPropertyNames + getOwnPropertyDescriptor + defineProperty
function simpleClone2(obj){
    let cloneObj = {}
	Object.getOwnPropertyNames(obj).forEach((key)=>{
        //getOwnPropertyNames: ["a","b"]
    	// cloneObj[key] = obj[key]
    	let des = Object.getOwnPropertyDescriptor(obj,key)
        //{value: 1, writable: true, enumerable: true, configurable: true}
        //{value: 2, writable: true, enumerable: true, configurable: true}
        
    	Object.defineProperty(cloneObj,key,des)
	})
    return cloneObj
}

//Object.assign(target,source)
//第一级是深拷贝:
let a = {James: {age: 18}}
let b = Object.assign({}, a)
b.James = 20
console.log(b) // { James: 20 } 
console.log(a) // { James: { age: 18 } }

//[...]扩展运算符
var a = { name: '周杰伦', age: 18, c: { d: 3 } }
var b = { ...a }
a.age = 20
a.c.d = 10
console.log(b)
  • 深拷贝:多层拷贝,每层的数据都会拷贝

    1. 递归

    2. JSON.stringify(不能拷贝 时间对象、正则表达式、Error对象、函数、undefined、NaN)拷贝构造函数生成的对象的话,会丢失对象的constructor

    3. lodash库 (var objects = [{ 'a': 1 }, { 'b': 2 }]; var deep = _.cloneDeep(objects);)··

//深拷贝
let obj = {
    a:1,
    b:{
        c:2,
        d:{
            e:3,
            f:[1,2,3,4,5]
        }
    }
}
function deepClone(){
    let cloneObj = {}
    //这样还只是浅拷贝 当更改obj.b.c的值时 拷贝的对象也会改变
    for(let i in obj){
        cloneObj[i] = obj[i]
    }
    return cloneObj
}
let obj1 = deepClone(obj)
obj.b.c =10
console.log(obj1)

//手动拷贝三层
function deepClone1(obj) {
  let cloneObj = {}
  for (let i in obj) {
    if (typeof obj[i] === 'object' && obj[i] !== null) {
      cloneObj[i] = {}
      for (let j in obj[i]) {
        if (typeof obj[i][j] === 'object' && obj[i][j] !== null) {
          cloneObj[i][j] = {}
          for (let k in obj[i][j]) {
            cloneObj[i][j][k] = obj[i][j][k]
          }
        } else {
          cloneObj[i][j] = obj[i][j]
        }
      }
    } else {
      cloneObj[i] = obj[i]
    }
  }
  return cloneObj
}

//递归实现 缺陷只能克隆数组和json对象
function deepClone2(obj, cloneObj) {
  var cloneObj = cloneObj || {}
  for (let i in obj) {
    if (typeof obj[i] === 'object' && obj[i] !== null) {
        //判断是数组 不是就赋值为数组
      // cloneObj[i] = Array.isArray(obj[i]) ? [] : {}
      // cloneObj[i] = obj[i] instanceof Array ? [] : {}
      // cloneObj[i] =
      //   Object.prototype.toString.call(obj[i]) === '[object Array]' ? [] : {} //这个方法能判断所有的
      cloneObj[i] = obj[i].constructor === Array ? [] : {}

      cloneObj[i] = deepClone2(obj[i], cloneObj[i])
    } else {
      cloneObj[i] = obj[i]
    }
  }
  return cloneObj
}

//JSON.stringify
function deepClone3(obj) {
  return JSON.parse(JSON.stringify(obj))
}

//测试
let cloneObj = deepClone3(obj)
obj.b.d.f.push(55)
console.log(obj.b.d.f)
console.log(cloneObj.b.d.f)
//最完善的深拷贝
//判断对象的类型
function jugeType(obj) {
  const toString = Object.prototype.toString
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object',
  }
  // if (obj instanceof Element) {
  //   return 'element'
  // }
  return map[toString.call(obj)]
}

function deepCopy3(obj) {
  const type = jugeType(obj)
  let copyObj = null
  if (type === 'array') {
    copyObj = []
    for (let i = 0; i < obj.length; i++) {
      copyObj.push(deepCopy3(obj[i]))
    }
  } else if (type === 'object') {
    copyObj = {}
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        copyObj[key] = deepCopy3(obj[key])
      }
    }
  } else {
    return obj
  }
  return copyObj
}

let obj = {
  a: 1,
  dates: [new Date(1536627600000), new Date(1540047600000)],
  b: {
    c: 2,
    d: {
      e: 3,
      F: function () {},
      reg: new RegExp('\\w+'),
    },
  },
}
let deepObj = deepCopy3(obj)
obj.b.c = 5
obj.b.d.e = 10
console.log('deepObj', deepObj)

17.判断数组的四种方法

假设 let a = []

  1. Array.isArray()

    Array.isArray(a) //true

  2. a instanceof Array 只能判断Object Function Array

    //Object Function Array
    a instanceof Array //true

  3. Object.prototype.toString.call(a) === '[object Array]' 可以判断任何类型(Number String Object Array Function Null Undefined )

    //Number String Object Array Function Null Undefined
    Object.prototype.toString.call(a) === '[object Array]' //true
    
    Object.prototype.toString.call(a) === '[object Number]' //true

  4. a.constructor === Array 除了null和undefined 其它的都能判断

    //Number String Object Array Function
    a.constructor === Array //true
    
    a.constructor === String //true

防抖和节流

防抖和节流:限制函数的执行次数

  1. 防抖:通过setTimeout的方式,在一定时间内,将多次触发变成一次触发

  2. 节流:减少一段时间的触发频率

防抖以及防抖的应用场景

  • 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求

  • 调整窗口大小时

  • scroll事件,当停止滚动后获取滚动条距离顶部的距离

  • 搜索输入框查询,只需用户最后一次输入完,再发送请求防抖

//防抖 最简单的写法
function debounce1(fn, wait) {
  let timer = null
  return function () {
    if (timer !== null) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      // 箭头函数中是没有arguments对象的
      fn.apply(this, arguments)
    }, wait)
  }
}
//默认第一次点击触发的防抖
function debounce2(fn, wait) {
  let timer = null
  return function () {
    let firstClick = !timer
    console.log('firstClick', firstClick)
    if (timer) {
      clearTimeout(timer)
    }
    if (firstClick) {
      fn.call(this, arguments)
    }
    timer = setTimeout(() => {
      timer = null
    }, wait)
  }
}
//用户用来设置是否需要第一次就触发的防抖
function debounce3(fn, wait, triggleNow) {
  let timer = null
  return function () {
    if (timer) {
      clearTimeout(timer)
    }
    if (triggleNow) {
      let firstClick = !timer
      if (firstClick) {
        fn.apply(this, arguments)
      }
      timer = setTimeout(() => {
        // 箭头函数中是没有arguments对象的
        timer = null
      }, wait)
    } else {
      timer = setTimeout(() => {
        // 箭头函数中是没有arguments对象的
        fn.apply(this, arguments)
      }, wait)
    }
  }
}

节流以及节流的应用场景

  • scroll事件,滚动监听事件,每隔一秒计算一次位置信息

  • 浏览器播放事件,每隔一秒计算一次进度信息

  • input框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求

function throttle(fn, delay) {
  let begin = 0
  return function () {
    let cur = new Date().getTime()
    if (cur - begin > delay) {
      fn.apply(this, arguments)
      begin = cur
    }
  }
}

18.栈与堆的区别

栈(stack):

由操作系统自动分配释放,存放函数的参数值,局部变量的值等,调用完毕立即释放

一种先进后厨的数据结构

堆(heap):

一般由程序员分配释放,若程序员不释放,程序员结束时可能由OS回收,分配方式倒是类似于链表

堆可以看成是一个树

js中基本数据类型存放在栈中:Number String Boolean Null undefined Symbol

引用数据类型存放在堆中:Function Object Array

区别:

  1. 栈使用的是一级缓存,被调用时处于存储空间中,调用完毕立即释放

  2. 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是没用了就释放了)

19.js数组常用操作

  • push(a,b,c...) 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度

  • unshift (a,b,c...)往数组的最前面添加元素,并返回该数组的新长度

  • pop() 删除并返回数组的最后一个元素

  • shift() 删除并返回数组的第一个元素

  • splice (0,3,'213',12) 从数组中指定的位置删除指定个数(3)的元素 或者 替换删除的参数 后面的参数为所需要替换的参数,splice会改变原数组

  • slice(2,4) 从数组中索引 0-3之间的元素数组(包含2不包含4),不会修改原数组,返回新数组

  • join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符隔离

  • reverse() 颠倒数组中元素的顺序

  • concat (arr1,arr2....) 不会修改原数组,连接数组

//splice
let arr = [1,2,3,4,5]
arr.splice(0,3,1,2,3,4,5)
console.log(arr); //[1,2,3,4,5,4,5]

//slice
let arr = [1,2,3,4,5]
let arr1 = arr.slice(0,2)
console.log('arr1',arr1);//[1,2]
console.log('arr',arr);

20.js数组常用函数

  • map 对数组的每一项都运行给定的函数,返回每次函数调用的结果组成一个新数组,原数组不改变

  • find 传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。

  • filter((item,index,arr)=>{}) 过滤掉不符合的元素,返回剩下的元素

  • forEach((item,index,arr)=>{}) 遍历数组

//map
let arr = [1,2,3,4,5]
let aaaa = arr.map((item)=>{
  return item *2
})
console.log(arr);//[1,2,3,4,5]
console.log(aaaa);//[2,4,6,8,10]


//find
const arr = [1, “2”, 3, 3, “2”]
console.log(arr.find(n => typeof n === “number”)) // 1

//filter
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter( (x)=> {
    return x % 2 !== 0;
});
// [1, 5, 9, 15]

21.去除数组的空值

  • filter

  • forEach、map + push循环判断

//filter
arr.filter(d=>d)

//forEach
arr.forEach(item => {
if (item) {
    newArr.push(item)
}
})

//map
arr.map(function(val, index) {
        //过滤规则为,不为空串、不为null、不为undefined,也可自行修改
        if (val !== "" && val != undefined) {
            newArr.push(val);
        }
    });

22.字符串的常用操作

  • split(separator,howmany) 方法用于把一个字符串分割成字符串数组。separator 指定的分割的参数,返回数组的最大长度

  • slice(satrt,end) 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。包括start,不包括end 可接受负数参数

  • substring(start,stop) 和slice一样但是substring不接受负的参数

23.js实现栈操作

class stack{
  constructor(){
    this.data = []
  }
  push(item){
    this.data.push(item)
  }
  pop(){
    if(this.data.length < 1){
      return false
    }
    console.log(this.data.pop())
  }
  isEmpty(){
    return this.data.length > 0 ? false : true
  }
}
let newStack = new stack()
newStack.push(2)
console.log(newStack.isEmpty());
console.log(newStack);

24.js实现队列操作

class queue{
  constructor(){
    this.arr = []
  }
  push(item){
    this.arr.push(item)
  }
  pop(){
    if(this.arr.length < 1) return false
    this.arr.shift()
  }
  getFront(){
    return this.arr[0]
  }
  getRear(){
    return this.arr[this.arr.length -1]
  }
  clear(){
    arr = []
  }
  size(){
    return this.arr.length
  }
}

let newQueue = new queue()
newQueue.push(1)
newQueue.push(2)
newQueue.pop()
console.log('newQueue',newQueue);

25.用栈实现队列

class myQueue{
  constructor(){
    this.stack1 = []
    this.stack2 = []
  }
  push(item){
    this.stack1.push(item)
  }
  pop(){
    if(this.stack2.length === 0){
      while(this.stack1.length > 0){
        this.stack2.push(this.stack1.pop())
      }
      if (this.stack1.length === 0 && this.stack2.length === 0) {
        return -1
    	}
    }
    return this.stack2.pop()
  }
  peek(){
    if(this.stack2.length > 0){
      return this.stack2[this.stack2.length -1]
    }else{
      return this.stack1[0]
    }
  }
  empty(){
    return this.stack1.length === 0 && this.stack2.length === 0
  }
}

let queue = new myQueue()
queue.push(1)
queue.push(2)
console.log(queue.pop());
console.log(queue.empty());
console.log('queue',queue);

26.用两个队列实现栈

class myStack{
  constructor(){
    this.queue1 = []
    this.queue2 = []
  }
  push(item){
    // 向已有元素的队列中添加元素
    if(this.queue1.length !== 0){
      this.queue1.push(item)
    }else{
      this.queue2.push(item)
    }
  }
  pop(){
    if(this.queue1.length !== 0){
      // queue1不为空,则把queue1中除最后一个元素的其他元素移入queue2
      while(this.queue1.length != 1){
        this.queue2.push(this.queue1.shift())
      }
      return this.queue1.shift()
    }else if(this.queue2.length !== 0){
      // queue2不为空,则把queue2中除最后一个元素的其他元素移入queue1
      while(this.queue2.length != 1){
        this.queue1.push(this.queue2.shift())
      }
      return this.queue2.shift()
    }
    return false
  }
  peek(){
    if(this.queue1.length !== 0){
      // 与pop操作一样
      while(this.queue1.length != 1){
        this.queue2.push(this.queue1.shift())
      }
      let res = this.queue1.shift()
      this.queue2.push(res)
      return res
    }else if(this.queue2.length !== 0){
      while(this.queue2.length != 1){
        this.queue1.push(this.queue2.shift())
      }
      let res = this.queue2.shift()
      this.queue1.push(res)
      return res
    }
    return false
  }
  empty(){
    if(this.queue1.length !== 0 || this.queue2.length !== 0) return false
    return true
  }
}

let stack = new myStack()
stack.push(1)
stack.push(2)
console.log(stack.pop());
stack.push(3)
// console.log(stack.pop());
console.log(stack.peek());
console.log('stack',stack);

27.线程和进程的区别

  • 进程:进程是有一定独立功能的程序,他是系统进行资源分配调度的一个独立单元

  • 线程:线程是进程的一个实体,是CPU调度分派的基本单位,线程直接基本上不拥有系统资源

一个程序至少有一个进程,一个进程至少有一个线程,资源分配给进程,同一个进程下所有线程共享该进程的资源

28.闭包

  • 概念:引用了外部自由变量的函数,就叫闭包

  • LHS:name='sdas' 当变量出现在赋值操作左侧是就是LHS操作,意味着给变量赋值写入内存

  • LHR:var myName = name 当变量出现在赋值操作右侧就是LHR操作,意味着查找变量,从内存中读取

面试题:

//1
function test (){
    var num = []
    var i

    for (i = 0; i < 10; i++) {
        num[i] = function () {
            console.log(i)
        }
    }

    return num[9]
}

test()()//10

//2
var test = (function() {
    var num = 0
    return () => {
        return num++
    }
}())

for (var i = 0; i < 10; i++) {
    test()
} //num = 9

console.log(test())//10

//3
function foo(a,b){
  console.log(b);
  return {
    foo:function(c){
      return foo(c,a);
    }
  }
}
 
var func1=foo(0);//unde
func1.foo(1);//0
func1.foo(2);//0
func1.foo(3);//0
var func2=foo(0).foo(1).foo(2).foo(3);// undefined 0 1 2
var func3=foo(0).foo(1);//undefined 0
func3.foo(2);//1
func3.foo(3);//1

应用:

  • 模拟私有变量

  • 偏函数和柯里化

  • 保存外部函数变量

  1. 模拟私有变量

    // 利用闭包生成IIFE,返回 User 类
    const User = (function() {
        // 定义私有变量_password
        let _password
    
        class User {
            constructor (username, password) {
                // 初始化私有变量_password
                _password = password
                this.username = username
            }
    
           login() {
               // 这里我们增加一行 console,为了验证 login 里仍可以顺利拿到密码
               console.log(this.username, _password)
               // 使用 fetch 进行登录请求,同上,此处省略
           }
        }
    
        return User
    })()
    
    let user = new User('xiuyan', 'xiuyan123')
    
    console.log(user.username)
    console.log(user.password)
    console.log(user._password)
    user.login()
  2. 偏函数和与柯里化

    • 偏函数 偏函数就是固定函数的一个或者几个入参,返回一个新的函数,偏函数强调的是把函数的入参拆解为两部分

      //原函数
      function generateName(prefix, type, itemName) {
          return prefix + type + itemName
      }
      
      // 调用时一口气传入3个入参
      var itemFullName = generateName('大卖网', '母婴', '奶瓶')
      
      //偏函数改造
      function generateName(prefix) {
          return function(type, itemName) {
              return prefix + type + itemName
          }
      }
      
      // 把3个参数分两部分传入
      var itemFullName = generateName('大卖网')('母婴', '奶瓶')
    • 柯里化 柯里化是把接受n个参数的1个函数改造为只接受一个参数的n个互相嵌套的函数的过程

      //原函数
      function generateName(prefix, type, itemName) {
          return prefix + type + itemName
      }
      // itemName 是原有商品名
      generateName('大卖网', type, itemName)
      
      
      //改造之后
      function generateName(prefix) {  
          return function(type) {
              return function (itemName) {
                  return prefix + type + itemName
              }    
          }
      }
      
      // 生成大卖网商品名专属函数
      var salesName = generateName('大卖网')
      // “记住”prefix,生成大卖网母婴商品名专属函数
      var salesBabyName = salesName('母婴')
      // "记住“prefix和type,生成洗菜网生鲜商品名专属函数
      var vegFreshName = generateName('洗菜网')('生鲜')
      
      // 输出 '大卖网母婴奶瓶'
      salesBabyName('奶瓶')
      // 输出 '洗菜网生鲜菠菜'
      vegFreshName('菠菜')
      
      // 啥也不记,直接生成一个商品名
      var itemFullName = generateName('洗菜网')('生鲜')('菠菜')
  3. 保存外部函数的变量

缺点:

  • 内存泄漏

  • 闭包会在父函数外部,改变父函数内部变量的值,如果把父函数当做对象使用,把闭包当做公用方法,内部变量当做私有属性,这时候就不要随便改变父函数内部变量的值

28.原型与原型链

原型分为两种:显式原型和隐式原型,每个构造函数都有一个显式原型,每个实例都有一个隐式原型,如果用构造函数去创建实例时,实例的隐式原型就会指向构造函数的显式原型

自由属性+原型继承属性

function A() {
    this.name = 'a'
    this.color = ['green', 'yellow']
 }
 function B() {
   
 }
 B.prototype = new A()
 var b1 = new B()
 var b2 = new B()
 
 b1.name = 'change'//写操作
 b1.color.push('black')//读操作

console.log(b2.name) // 'a'
console.log(b2.color) // ["green", "yellow", "black"]

读操作和写操作的区别:

b1.name = 'change' 是一个赋值动作赋值动作不会沿着原型链往上找,只有读的操作会沿着原型链找 像b1.color.push('black')它走的是 原型链 查询 + 修改 的流程,不是创建新属性的流程

原型链继承的几种方式

  1. 原型链继承

  2. 构造继承

  3. 组合式继承

父类:

function People(name){
    this.name = name || 'Annie'
    this.sleep = function(){
        console.log(this.name + '正在睡觉')
    }
}
//原型方法
People.prototype.eat = function(food){
    console.log(this.name + '正在吃:' + food);
}

原型链继承;

function Woman(){}
Woman.prototype= new People();
Woman.prototype.name = 'haixia';
let womanObj = new Woman();

29.BOM

(1)location对象

location.href-- 返回或设置当前文档的URL location.search -- 返回URL中的查询字符串部分。例如 http://www.dreamdu.com/dreamdu.php?id=5&name=dreamdu 返回包括(?)后面的内容?id=5&name=dreamdu location.hash -- 返回URL#后面的内容,如果没有#,返回空 location.host -- 返回URL中的域名部分,例如www.dreamdu.com location.hostname -- 返回URL中的主域名部分,例如dreamdu.com location.pathname -- 返回URL的域名后的部分。例如 http://www.dreamdu.com/xhtml/ 返回/xhtml/ location.port -- 返回URL中的端口部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回8080 location.protocol -- 返回URL中的协议部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回(//)前面的内容http: location.assign -- 设置当前文档的URL location.replace() -- 设置当前文档的URL,并且在history对象的地址列表中移除这个URL location.replace(url); location.reload() -- 重载当前页面

(2)history对象

history.go() -- 前进或后退指定的页面数 history.go(num); history.back() -- 后退一页 history.forward() -- 前进一页

(3)Navigator对象

navigator.userAgent -- 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串) navigator.cookieEnabled -- 返回浏览器是否支持(启用)cookie

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值