Skip to content

Latest commit

 

History

History
840 lines (593 loc) · 23.5 KB

File metadata and controls

840 lines (593 loc) · 23.5 KB

javascript基础知识点

智力题

技巧

数据初始化null if 提升到外层

运算规则

从右到左

in运算符

如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。

prop in object
// 数组
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees        // 返回true
3 in trees        // 返回true
6 in trees        // 返回false
"bay" in trees    // 返回false (必须使用索引号,而不是数组元素的值)
"length" in trees // 返回true (length是一个数组属性)
Symbol.iterator in trees // 返回true (数组可迭代,只在ES2015+上有效)

// 内置对象
"PI" in Math          // 返回true

// 自定义对象
var mycar = {make: "Honda", model: "Accord", year: 1998};
"make" in mycar  // 返回true
"model" in mycar // 返回true

深度合并对象

const deepMerge = (function() {
  function isIteration(obj){
    let objType = Object.prototype.toString.call(obj);
    return objType=='[object Object]'||objType=='[object Array]'
  }
  return function _deepMerge(source, atrget) { 
    if (!isIteration(atrget)) {
      throw new Error('error arguments');
    }
    for (let key in atrget) {
      //只对对象自有属性进行拷贝
      if (atrget.hasOwnProperty(key)) {
        if (isIteration(atrget[key])) {
          _deepMerge(source[key], atrget[key]);
        } else {
          source[key] = atrget[key];
        }
      }
    }  
  } 
})()

Error

throw new Error();

安全随机

const array = new Uint32Array(2);
window.crypto.getRandomValues(array);
return array.join("");

生成随机数的方法:https://www.hangge.com/blog/cache/detail_1872.html

内置对象

全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。

标准内置对象的分类

(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。

例如 Infinity、NaN、undefined、null 字面量

(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。

例如 eval()、parseFloat()、parseInt() 等

(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。

例如 Object、Function、Boolean、Symbol、Error 等

(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。

例如 Number、Math、Date

(5)字符串,用来表示和操作字符串的对象。

例如 String、RegExp

(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array

(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。

例如 Map、Set、WeakMap、WeakSet

(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。

例如 SIMD 等

(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。

例如 JSON 等

(10)控制抽象对象

例如 Promise、Generator 等

(11)反射

例如 Reflect、Proxy

(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。

例如 Intl、Intl.Collator 等

(13)WebAssembly

(14)其他

例如 arguments

ToString

范的 9.8 节中定义了抽象操作 ToString ,它负责处理非字符串到字符串的强制类型转换。

(1)Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined",

(2)Boolean 类型,true 转换为 "true",false 转换为 "false"。

(3)Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。

(4)Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。

(3)对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

ToNumber

有时我们需要将非数字值当作数字来使用,比如数学运算。为此 ES5 规范在 9.3 节定义了抽象操作 ToNumber。

(1)Undefined 类型的值转换为 NaN。

(2)Null 类型的值转换为 0。

(3)Boolean 类型的值,true 转换为 1,false 转换为 0。

(4)String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。

(5)Symbol 类型的值不能转换为数字,会报错。

(6)对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。

为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。

如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

ToBoolean

ES5 规范 9.2 节中定义了抽象操作 ToBoolean,列举了布尔强制类型转换所有可能出现的结果。

以下这些是假值: • undefined • null • false • +0、-0 和 NaN • ""

假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。

DOM 和 BOM

DOM 指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了处理网页内容的方法和接口。

BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

严格模式

https://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html

use strict 是一种 ECMAscript5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行。

严格模式的目的:

消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;

消除代码运行的一些不安全之处,保证代码运行的安全;

提高编译器效率,增加运行速度;

为未来新版本的 Javascript 做好铺垫。

escape,encodeURI,encodeURIComponent

escape

简单来说,escape是对字符串(string)进行编码(而另外两种是对URL),作用是让它们在所有电脑上可读。

编码之后的效果是%XX或者%uXXXX这种形式。其中 ASCII字母 数字 @*/+ 这几个字符不会被编码,其余的都会。

最关键的是,当你需要对URL编码时,请忘记这个方法,这个方法是针对字符串使用的,不适用于URL。

encodeURI和encodeURIComponent

对URL编码是常见的事,所以这两个方法应该是实际中要特别注意的。

它们都是编码URL,唯一区别就是编码的字符范围,

其中encodeURI方法不会对下列字符编码 ASCII字母 数字 ~!@#$&*()=:/,;?+'

encodeURIComponent方法不会对下列字符编码 ASCII字母 数字 ~!*()'

所以encodeURIComponent比encodeURI编码的范围更大。

实际例子来说,encodeURIComponent会把 http:// 编码成 http%3A%2F%2F 而encodeURI却不会。

场景

1、如果只是编码字符串,不和URL有半毛钱关系,那么用escape。

2、如果你需要编码整个URL,然后需要使用这个URL,那么用encodeURI。

比如encodeURI("http://www.cnblogs.com/season-huang/some other thing");

编码后会变为"http://www.cnblogs.com/season-huang/some%20other%20thing";

其中,空格被编码成了%20。

但是如果你用了encodeURIComponent,那么结果变为"http%3A%2F%2Fwww.cnblogs.com%2Fseason-huang%2Fsome%20other%20thing"。看到了区别吗,连 "/" 都被编码了,整个URL已经没法用了。

3、当你需要编码URL中的参数的时候,那么encodeURIComponent是最好方法。

var param = "http://www.cnblogs.com/season-huang/"; //param为参数
param = encodeURIComponent(param);
var url = "<http://www.cnblogs.com?next>=" + param;
console.log(url) //"http://www.cnblogs.com?next=http%3A%2F%2Fwww.cnblogs.com%2Fseason-huang%2F"

js动画与css动画

js动画适合复杂

css动画适合简单的

事件流管理 EventEmitter

https://zhuanlan.zhihu.com/p/77876876

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    let callbacks = this.events[event] || [];
    callbacks.push(callback);
    this.events[event] = callbacks;

    return this;
  }

  off(event, callback) {
    let callbacks = this.events[event];
    this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);

    return this;
  }

  emit(event, ...args) {
    let callbacks = this.events[event];
    callbacks.forEach(fn => {
      fn(...args);
    });

    return this;
  }

  once(event, callback) {
    let wrapFun = function(...args) {
      callback(...args);

      this.off(event, wrapFun);
    };
    this.on(event, wrapFun);

    return this;
  }
}

js的命名规则

(1)第一个字符必须是字母、下划线(_)或美元符号($) (2)余下的字符可以是下划线、美元符号或任何字母或数字字符

一般我们推荐使用驼峰法来对变量名进行命名,因为这样可以与 ECMAScript 内置的函数和对象命名格式保持一致。

定时器偏差

https://juejin.cn/post/6844903685458231303

在前端实现中我们一般通过 setTimeout 和 setInterval 方法来实现一个倒计时效果。但是使用这些方法会存在时间偏差的问题,这是由于 js 的程序执行机制造成的,setTimeout 和 setInterval 的作用是隔一段时间将回调事件加入到事件队列中,因此事件并不是立即执行的,它会等到当前执行栈为空的时候再取出事件执行,因此事件等待执行的时间就是造成误差的原因。

一般解决倒计时中的误差的有这样两种办法:

(1)第一种是通过前端定时向服务器发送请求获取最新的时间差,以此来校准倒计时时间。

(2)第二种方法是前端根据偏差时间来自动调整间隔时间的方式来实现的。这一种方式首先是以 setTimeout 递归的方式来实现倒计时,然后通过一个变量来记录已经倒计时的秒数。每一次函数调用的时候,首先将变量加一,然后根据这个变量和每次的间隔时间,我们就可以计算出此时无偏差时应该显示的时间。然后将当前的真实时间与这个时间相减,这样我们就可以得到时间的偏差大小,因此我们在设置下一个定时器的间隔大小的时候,我们就从间隔时间中减去这个偏差大小,以此来实现由于程序执行所造成的时间误差的纠正。

= + 符号意义

如下,把字符串b转化为数字

let b = '22';
let a = +b;

随机id

const RandomId = len => Math.random().toString(36).substr(3, len);
const id = RandomId(10);
// id => "jg7zpgiqva"

生成随机HEX色值

// 好像有问题 随机函数不包括边界值
const RandomColor = () => {
  return "#" + 
    Math.floor( Math.random() * 0xffffff ) // 得到十六进制0x000000 - 0xffffff的数字
    .toString(16) // 把上述数字转为十六进制表示的字符串
    .padEnd(6, "0"); // 末尾补0
}
const color = RandomColor();
// color => "#f03665"

精确位数小数

const RoundNum = (num, decimal) => Math.round(num * 10 ** decimal) / 10 ** decimal;
const num = RoundNum(1.69, 1);
// num => 1.7

判断奇偶

const OddEven = num => !!(num & 1) ? "odd" : "even";
const num = OddEven(2);
// num => "even"

生成范围随机数

const RandomNum = (min, max) => 
  Math.floor(
    Math.random() * (max - min + 1)
  ) 
  + min;
const num = RandomNum(1, 10);

对象不为空时执行

const obj = { a: 0, b: 1, c: 2 };
Object.keys(obj).length && Func();

过滤空值

const arr = [undefined, null, "", 0, false, NaN, 1, 2].filter(Boolean);
// arr => [1, 2]

Boolean 也是一个function

显示全部DOM边框

[].forEach.call($$("*"), dom => {
    dom.style.outline = "1px solid #" + (~~(Math.random() * (1 << 24))).toString(16);
});

switch语法

如果你忘记添加 break,那么代码将会从值所匹配的 case 语句开始运行,然后持续执行下一个 case 语句而不论值是否匹配。直到遇到break,或者return

const expr = 'Papayas';
switch (expr) {
  case 'Oranges':{
    let a = 2;
    console.log('Oranges are $0.59 a pound.');
    break;
  }
  case 'Mangoes':
  case 'Papayas':
    console.log('Mangoes and papayas are $2.79 a pound.');
    // expected output: "Mangoes and papayas are $2.79 a pound."
    break;
  default:
    console.log(`Sorry, we are out of ${expr}.`);
}

实现一个异步事件管理

class eventManage {
  constructor(){
    this.eventQuene = []; 
  }
  push(func){
    this.eventQuene.push(func)
  }
  execute(){
    while(){ // queue非空

    }

    this.eventQuene.unshift().then(()=>{
      
    })
  }  
}

functiona(){

}

functionb(){

}

['1', '2', '3'].map(parseInt)

返回 [1, NaN, NaN]

请根据面对对象编程的思想,设计一个类型 Cash 用于表达人民币,使得

class Cash {
}
const cash1 = new Cash(105);
const cash2 = new Cash(66);
const cash3 = cash1.add(cash2);
const cash4 = Cash.add(cash1, cash2);
const cash5 = new Cash(cash1 + cash2);
console.log(`${cash3}`, `${cash4}`, `${cash5}`);
class Cash {
  constructor(money) {
   this.money = money;
  }
  static add(){ // Cash类直接可调用的方法
    let c = new Cash();
    [...arguments].forEach( // arguments转义为可数组Array
      function(item){
        this.money=(this.money||0)+(item.money||0);
      }.bind(c) // bind后返回改变this的函数
    );
    return c;
  }
  add(){ // 实例方法
    return Cash.add(this,...arguments);
  }
  valueOf() { // 实现实例可以运算,并取值
   return this.money;
  }
  toString() {
   return this.money.toString().replace(/(.)(..)$/,"$1元$2").replace(/(.)(.)$/,"$1角$2")+"分";
  }
}

假设你有8个球,其中一个略微重一些,但是找出这个球的惟一方法是将两个球放在天平上对比。最少要称多少次才能找出这个较重的球

最少两次可以称出。

首先将 8 个球分为 3 组,其中两组为 3 个球,一组为 2 个球。

第一次将两组三个的球进行比较,如果两边相等,则说明重的球在最后一组里。第二次将最后一组的球进行比较即可。如 果两边不等,则说明重的球在较重的一边,第二次只需从这一组中随机取两球出来比较即可判断。

用3升,5升杯子怎么量出4升水?

(1)将 5 升杯子装满水,然后倒入 3 升杯子中,之后 5 升杯子还剩 2 升水。

(2)将 3 升杯子的水倒出,然后将 5 升杯子中的 2 升水倒入 3 升杯子中。

(3)将 5 升杯子装满水,然后向 3 升杯子中倒水,直到 3 升杯子装满为止,此时 5 升杯子中就还剩 4 升水。

数组扁平化、去重、排序

mqyqingfeng/Blog#36

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
// 扁平化
let flatArr = arr.flat(Infinity)
// 去重
let disArr = Array.from(new Set(flatArr))
// 排序
let result = disArr.sort(function(a, b) {
    return a-b
})
console.log(result)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

数组交集

sisterAn/JavaScript-Algorithms#6

filter 过滤 ;

Set 去重;

const intersection = function(nums1, nums2) {
    return [...new Set(nums1.filter((item)=>nums2.includes(item)))]
};

在数组中,找和等于target的两个数

给定一个整数数组 nums 和一个目标值 target ,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

方案:

存储值, 每次迭代在存储列表查找 target与当前值的差值

const twoSum = function(nums, target) {
    let map = new Map()
    for(let i = 0; i< nums.length; i++) {
        let k = target-nums[i]
        if(map.has(k)) {
            return [map.get(k), i]
        }
        map.set(nums[i], i)
    }
    return [];
};

合并两个有序数组

sisterAn/JavaScript-Algorithms#3

const merge = function(nums1, m, nums2, n) {
    let len1 = m - 1,
        len2 = n - 1,
        len = m + n - 1
    while(len2 >= 0) {
        if(len1 < 0) {
            nums1[len--] = nums2[len2--]
            continue
        }
        nums1[len--] = nums1[len1] >= nums2[len2] ? nums1[len1--]: nums2[len2--]
    }
};

LRU 缓存机制

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和写入数据 put 。

获取数据 get(key) - 如果密钥 ( key ) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1 。

写入数据 put(key, value) - 如果密钥不存在,则写入数据。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据,从而为新数据留出空间。

利用 Map 既能保存键值对,并且能够记住键的原始插入顺序

var LRUCache = function(capacity) {
    this.cache = new Map()
    this.capacity = capacity
}

LRUCache.prototype.get = function(key) {
    if (this.cache.has(key)) {
        // 存在即更新
        let temp = this.cache.get(key)
        this.cache.delete(key)
        this.cache.set(key, temp)
        return temp
    }
    return -1
}

LRUCache.prototype.put = function(key, value) {
    if (this.cache.has(key)) {
        // 存在即更新(删除后加入)
        this.cache.delete(key)
    } else if (this.cache.size >= this.capacity) {
        // 不存在即加入
        // 缓存超过最大值,则移除最近没有使用的
        this.cache.delete(this.cache.keys().next().value)
    }
    this.cache.set(key, value)
}

多个数组的交集

const intersection = function(...args) {
    if (args.length === 0) {
    return []
  }
  if (args.length === 1) {
    return args[0]
  }
  return [...new Set(args.reduce((result, arg) => {
    return result.filter(item => arg.includes(item))
  }))]
};

翻转字符串里的单词

https://leetcode-cn.com/problems/reverse-words-in-a-string/solution/tu-jie-leetcodefan-zhuan-zi-fu-chuan-li-de-dan-ci-/

无空格字符构成一个单词。

输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

输入: "a good example"

输出: "example good a"

var reverseWords = function(s) {
    return s.trim().replace(/\s+/g, ' ').split(' ').reverse().join(' ')
};
var reverseWords = function(s) {
    let left = 0
    let right = s.length - 1
    let queue = []
    let word = ''
    while (s.charAt(left) === ' ') left ++
    while (s.charAt(right) === ' ') right --
    while (left <= right) {
        let char = s.charAt(left)
        if (char === ' ' && word) {
            queue.unshift(word)
            word = ''
        } else if (char !== ' '){
            word += char
        }
        left++
    }
    queue.unshift(word)
    return queue.join(' ')
};

最长公共前缀

sisterAn/JavaScript-Algorithms#19

解法一:逐个比较

解法二:先找最长,最短字符串,再找这两个字符串的最长公共前缀

解法三:分治策略,归并思想

var longestCommonPrefix = function(strs) {
    if (strs === null || strs.length === 0) return "";
    return lCPrefixRec(strs)
};

// 若分裂后的两个数组长度不为 1,则继续分裂
// 直到分裂后的数组长度都为 1,
// 然后比较获取最长公共前缀
function lCPrefixRec(arr) {
  let length = arr.length
  if(length === 1) {
    return arr[0]
  }
  let mid = Math.floor(length / 2),
      left = arr.slice(0, mid),
      right = arr.slice(mid, length)
  return lCPrefixTwo(lCPrefixRec(left), lCPrefixRec(right))
}

// 求 str1 与 str2 的最长公共前缀
function lCPrefixTwo(str1, str2) {
    let j = 0
    for(; j < str1.length && j < str2.length; j++) {
        if(str1.charAt(j) !== str2.charAt(j)) {
            break
        }
    }
    return str1.substring(0, j)
}

解法四:Trie 树(字典树)

字符串相加

sisterAn/JavaScript-Algorithms#32

给定两个字符串形式的非负整数 num1 和 num2 ,计算它们的和。

例如:

"111" + ”2222“ = ”2333“

注意:

num1 和 num2 的长度都小于 5100
num1 和 num2 都只包含数字 0-9
num1 和 num2 都不包含任何前导零
你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式

实现思路:


从低位开始相加:

从 num1 ,num2 的尾部开始计算,模拟人工加法,保存到 tmp 中;

计算 tmp 的个位数,并添加到 result 的头部,这里的 result 是 string 类型,不是 number 类型;

计算进位,改成 tmp,进行下次循环

索引溢出处理:循环结束,根据 tmp 判断是否有进位,并在 result 头部添加进位 1

返回 result
var addStrings = function(num1, num2) {
  let a = num1.length, b = num2.length, result = '', tmp = 0
  while(a || b) {
    a ? tmp += +num1[--a] : ''
    b ? tmp +=  +num2[--b] : ''
    
    result = tmp % 10 + result
    if(tmp > 9) tmp = 1
    else tmp = 0
  }
  if (tmp) result = 1 + result
  return result
};

无重复字符的最长子串

sisterAn/JavaScript-Algorithms#21

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

用map实现,时间复杂度O(n)

var lengthOfLongestSubstring = function(s) {
    let map = new Map(), max = 0
    for(let i = 0, j = 0; j < s.length; j++) {
        if(map.has(s[j])) {
            i = Math.max(map.get(s[j]) + 1, i)
        }
        max = Math.max(max, j - i + 1)
        map.set(s[j], j)
    }
    return max
};

(a === 1 && a === 2 && a === 3) 返回true

== 用Object.valueOf

=== 用defineProperty(window,a,{ get })

https://juejin.cn/post/6844903725442531341

如何实现一个 new

function _new(fn, ...arg) {
    const obj = Object.create(fn.prototype);
    const ret = fn.apply(obj, arg);
    return ret instanceof Object ? ret : obj;
}