2021-01-22 js 相关知识点

1、内置类型

JS中分 7 种内置类型,7 种内置类型又分为 2 大类型: 基本类型对象(Object) 。
对象是 引用类型 。
基本类型有 6 种: null , undefined , boolean , number , string , symbol 。
其中 JS 的数字类型是浮点类型的,没有整型。

2、typeof

typeof 对于基本类型,除了 null 都可以显示正确的类型。

typeof 1 ;//'number'
typeof '1' ;//'string'
typeof undefined; //'undefined'
typeof true ; //'boolean'
typeof Symbol() ;//'symbol'
typeof b; //b 没有声明,但是还会显示 undefined

typeof 对于对象,除了函数都会显示 object 。
typeof [] ;//'object'
typeof {} ;//'object'
typeof console.log ;//'function'

对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久的bug。
typeof null ;//’object'

如果想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xxx)

3、类型转换

转 Boolean:
在条件判断时,除了 undefined ,null , false , NaN , '' , 0 , -0,其他所有值都转为 true,
包括所有对象。

对象转基本类型:
对象在转换基本类型时,首先会调用 valueOf 然后调用 toString 。

四则运算符:
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,
那么另一方就转为数字。并且加法运算会触发三种类型转换: 将值转换为原始值,转换为数字,转换为字符串。

4、原型

每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型。
每个对象都有 _proto_ 属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]],
但是 [[prototype]] 是内部属性,我们并不能访问到,所以使用 _proto_ 来访问。
对象可以通过 _proto_ 来寻找不属于该对象的属性, _proto_ 将对象连接起来组成了原型链。

5、new

1、新生成了一个对象
2、链接到原型
3、绑定this
4、返回新对象

function create(){
//创建一个空的对象
let obj = new Object();
//获得构造函数
let Con = [].shift.call(arguments)
//链接到原型
obj.__proto__ = Con.prototype
//绑定 this,执行构造函数
let result = Con.apply(obj,argument)
//确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}
对于创建一个对象来说,推荐使用字面量的方式创建对象。因为使用 new Object() 的方式创建对象需要通过
作用域链一层层找到object,但是使用字面量的方式就没有这个问题。

function Foo(){
//function 就是个语法糖
//内部等同于 new Function()
}
let a = {b:1};
//这个字面量内部也是使用了 new Object()

6、instanceof

instanceof 可以正确的判断对象的类型。因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype 。

function instanceof(left,right){
//获得类型的原型
let prototype = right.prototype;
//获得对象的原型
left = left.__proto__
//判断对象的类型是否等于类型的原型
while(true){
if(left === null){
return false
}
if(prototype === left){
return true
}
left = left.__proto__
}
}

7、this

function foo(){
console.log(this.a)
}
var a = 1;
foo()
var obj = {
a:2,
foo:foo
}
obj.foo()
//上面两种情况,this 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况

//以下情况是优先级最高的,this 只会绑定在 c 上,不会被任何方式修改 this 指向
var c = new foo()
c.a = 3
console.log(c.a);

//还有就是利用 call,apply,bind 改变this,这个优先级仅次于 new 。

箭头函数中的this:
function a(){
return () =>{
return () =>{
console.log(this)
}
}
}
console.log(a()()());//箭头函数其实是没有this的,这个函数中的this只取决于他外面的第一个不是箭头函数
的函数的this。在这个例子中,因为调用a符合前面代码中的第一个情况,所以this是window。并且this一旦绑定了
上下文,就不会被任何代码改变。

8、执行上下文

当执行JS代码时,会产生三种执行上下文:
全局执行上下文
函数执行上下文
eval执行上下文

每个执行上下文中都有三个重要的属性:
变量对象VO , 包含变量,函数声明和函数的形参,该属性只能在全局上下文中访问。
作用域链(js采用词法作用域,也就是说变量的作用域在定义时就决定了。)
this:

b() // call b second 在提升过程中,相同的函数会覆盖上一个函数,并且函数优先于变量提升。
function b(){
console.log('call b first')
}
function b(){
console.log('call b second')
}
var b = 'hello world'

9、闭包

定义:函数A返回了一个函数B,并且函数B中使用了函数A的变量,函数B就被称为闭包。
function A(){
let a = 1;
function B(){
console.log(a)
}
retur B
}

//经典面试题 - 循环中使用闭包解决 var 定义函数的问题
for(var i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
//首先这里 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这个时候i就是6了,所以会输出
一堆6 .
解决办法:
法一:(使用闭包)
for(var i=1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
},j*1000)
})(i);
}
法二:使用setTimeOut的第三个参数
for(var i=1;i<=5;i++){
setTimeout(function timer(j){
console.log(j)
},i*1000,i)
}
法三:使用let定义i
for(let i=0;i<=5;i++){
setTimeout(function timer(){
console.log(i)
},i*1000)
}

 10、深浅拷贝

let a = {
age:1
}
let b = Object.assign({},a)
a.age = 2;
console.log(b.age) ;// 1

浅拷贝:Object.assign
或者考虑用 展开运算符(...)来解决。
let b1 = {...a}
a.age = 3;
console.log(b1,age);//1

假设是这样子的obj:
let a1 = {
age:1,
jobs:{
first:'FE'
}
}
let b = {...a1}
a.jobs.first = 'native';
console.log(b.jobs.first);//native
浅拷贝只解决第一层的问题,如果接下去的值中还有对象的话,那么又回到原点了,这个时候,需要用到深拷贝

深拷贝: JSON.parse(JSON.stringify(object))
let a2 = {
age:1,
jobs:{
first:'FE'
}
}
let b2 = JSON.parse(JSON.stringify(a2));
a2.jobs.first = 'native';
console.log(b2.jobs.first);//FE
此方法的局限性:
会忽略 undefined
会忽略 symbol
不能序列化函数
不能解决循环引用的对象。
这个时候,就可以考虑是使用 lodash 的深拷贝函数了!!!

 11、模块化

在有 Babel 的情况下,可以直接使用 ES6 的模块化
//file a.js
export default a(){

}
export default b(){

}
//file b.js
export default function(){}

import {a,b} from './a.js'
import XXX from './b.js'


CommonJS
CommonJS 是 node独有的规范,浏览器中使用就需要用到 Browserify 解析。
//a.js
module.exports = {
a:1
}
//或者
exports.a = 1

//b.js
var module = require('./a.js')
module.a //

CommonJS 和 ES6 中的模块化两者区别是:
前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。
前者是同步导入,因为用于服务端,文件都在本地。后者是异步导入,因为用于浏览器,需要下载文件。
后者会编译成 require/exports 来执行的。

AMD 是由 RequireJS 提出的
define(['./a','.b'],function(a,b){
a.do()
b.do()
})
define(function(require,exports,module){
var a = require('./a')
a.doSomething()
var b = require('./b')
b.doSomething()
})

12、防抖,节流

注意: 防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的
间隔小于wait,防抖的情况下只会调用一次,而节流的情况会每隔一定时间(参数wait)调用函数。

两者本质区别:防抖是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

防抖场景:
在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。

//这个用来获取当前时间戳
function now(){
return +new Date()
}
/*
*防抖函数,返回函数连续调用时,空闲时间必须大于或等于wait,func才会执行
*func 回调函数
*wait 表示时间窗口的间隔
*immediate 设置为true时,是否立即调用函数
*return {function} 返回客户调用函数
*/
function debounce(func,wait=,immediate=true){
let timer,context,args;

//延迟执行函数
const later = ()=>setTimeout(()=>{
//延迟函数执行完毕,清空缓存的定时器序号
timer = null
//延迟执行的情况下,函数会在延迟函数中执行
//使用到之前缓存的参数和上下文
if(!immediate){
func.apply(context,args)
context = args = null
}
},wait)

//这里返回的函数是每次实际调用的函数
return function(...params){
//如果没有创建延迟执行函数later,就创建一个
if(!timer){
timer = later()
//如果是立即执行,调用函数
//否则缓存参数和调用上下文
if(immediate){
func.apply(this,params)
}else{
context = this
args = params
}
//如果已有延迟执行函数later,调用的时候清除原来的并重新设定一个
//这样做延迟函数会重新计时
}else{
cleartTimeout(timer)
timer = later()
}
}
}


/*
*节流函数,返回函数连续调用时,func执行频率限定为 次/wait
* func 回调函数
* wait 表示时间窗口的间隔
* options 如果想忽略开始函数的调用 传入 {leading:false}
* 如果想忽略结尾函数的调用 传入 {trailing:false}
* 两者不能共存,否则函数不能执行
* return {function} 返回客户调用函数
*/
_.throttle = function(func,wiat,options){
var context,args,result;
var timeout = null;
//之前的时间戳
var previous = 0;
//如果options 没传则设为空对象
if(!options) options = {};
//定时器回调函数
var later = function(){
previous = options.leading === false ? 0: _.now()
timeout = null
result = func.apply(context,args);
if(!timeout) context = args = null;
}
return function(){
var now = _.now()
if(!previous && options.leading ===false) previous = now;
var remaining = wait - (now-previous)
context = this;
args = arguments;
if(remaining <=0 || remaining>wait){
if(timeout){
cleartTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context,args);
if(!timeout) context = args = null;
}else if(!timeout && options.trailing !==false){
timeout = setTimeout(later,remaining);
}
return result;
}
}
//ending,感觉对节流还有些不懂的样子,扎♥♥,/(ㄒoㄒ)/~~

13、继承

function Super(){}
Super.prototype.getNumber = function(){
return 1
}

function Sub(){}
let s = new Sub()
Sub.prototype = Object.create(Super.prototype,{
constructor:{
value:Sub,
enumerable:false,
writable:true,
configurable:true
}
})
//上面这个是将子类的原型设置为父类的原型

//ES6中可以通过class的语法解决
class MyDate extends Date{
test(){
return this.getTime()
}
}
let myDate = new MyDate()
myDate.test()
注意:ES6不是所有浏览器都兼容,需要Babel来编译

14、 call , apply , bind 区别?

call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。
除了第一个参数外,call 可以接收一个参数列表,apply 只接收一个参数数组。

bind 和这两个方法作用一致,只是它会返回一个函数。并且可以通过bind实现柯里化。

let a = {
val:1
}
function getVal(name,age){
console.log(name)
console.log(age)
console.log(this.value)
}
getVal.call(a,'hello','18')
getVal.apply(a,['world','18'])

模拟实现call 和 apply
Function.prototype.myCall = function(context){
var context = context || window
context.fn = this
var args = [...arguments].slice(1)
var result = context.fn(...args)
delete context.fn
return result
}

Function.prototype.myApply = function(context){
var context = context || window
context.fn = this
var result
if(arguments[1]){
result = context.fn(...arguments[1])
}else{
result = context.fn()
}
delete context.fn
return reuslt
}

Function.prototype.myBind = function(context){
if(typeof this !=='function'){
throw new TypeError('error')
}
var _this = this;
var args = [...arguments].slice(1)
return function F(){
if(this.instanceof F){
return new _this(...args,...arguments)
}
return _this.apply(context,args.concat(...arguments))
}
}

15、promise 实现

promise 的出现是解决了回调地狱的问题。

16、generator 实现

// 使用 * 表示这是一个 generator 函数
// 内部可以通过 yield 暂停代码
// 通过 next 恢复执行

function* test(){
let a = 1+2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next());//{value:2,done:false}
console.log(b.next());//{value:3,done:false}
console.log(b.next());//{value:undefined;done:true}

//加上*的函数执行后拥有了next函数,也就是说函数执行后返回了一个对象。
每次调用next函数可以继续执行被暂停的代码。

17、Map , FlatMap 和 Reduce

Map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后append到新的数组中。

[1,2,3].map((v)=>v+1) // [2,3,4]
Map 有三个参数,分别是当前索引元素,索引,原数组
['1','2','3'].map(parseInt);
// parseInt('1',0) =>1
// parseInt('2',1) =>NaN
// parseInt('3',2) =>NaN

FlatMap 和 map 的作用几乎相同,但是对于多维数组来说,可以将原数组降维。

Reduce 作用是数组中的值组合起来,最终得到一个值
function a(){
console.log(1)
}
function b(){
console.log(2)
}
[a,b].reduce((a,b) =>a(b())) // 2 1

17、async  和 await

一个函数如果加上 async ,那么该函数就会返回一个 promise

async function test(){
return '1';
}
console.log(test());// Promise {<resolved>:'1'}
可以把async看成将函数返回值使用 promise.resolve() 包裹了下。
await 只能在 async 函数中使用。

function sleep(){
return new Promise(resolve =>{
setTimeout(()=>{
console.log('finish')
resolve('sleep')
},2000)
})
}
async function test(){
let val = await sleep();
console.log('object')
}
text();//会先打印 finish ,然后再打印 object。因为 await会等待 sleep 函数 resolve,所以即使后面
是同步代码,也不会先去执行同步代码再来执行异步代码。

18、proxy

Proxy,可以用来自定义对象中的操作。

19、0.1 + 0.2 !=0.3

parseFloat((0.1+0.2).toFixed(10))

参考链接:https://yuchengkai.cn/docs/frontend/

如果快乐太难,那祝你平安。
原文地址:https://www.cnblogs.com/sunnyeve/p/14309054.html