JS基础笔记

297 阅读10分钟

数据类型

基础类型:String、Number、Boolean、Undefined、Null

引用类型: Array、Object

ES6新增:Symbol

类型判断

  • typeof typeof 可以用来进行判断基础类型的数据
typeof  'abc' // string
typeof  123 // number
typeof true // boolean
typeof undefined // undefined
typeof null // object
  • instanceof instanceof 检测某个对象是否属于另一个对象的实例. mdn上的介绍是:用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
// 实现一个instanceof
function myInstanceof(left,right){
    // 获取类型的原型
    let prototype = right.prototype
    // 获取对象的原型
    left = left._proto_
    while(true){
        if(left ===null) return false;
        if(prototype === left) {
            return true
        }
        left = left._proto_
    }
}
  • Object.prototype.toString
console.log(Object.prototype.toString.call(123));    //[object Number]
console.log(Object.prototype.toString.call('123'));    //[object String]
console.log(Object.prototype.toString.call(undefined));    //[object Undefined]
console.log(Object.prototype.toString.call(true));    //[object Boolean]
console.log(Object.prototype.toString.call({}));    //[object Object]
console.log(Object.prototype.toString.call([]));    //[object Array]
console.log(Object.prototype.toString.call(function(){}));    //[object Function]
console.log(Object.prototype.toString.call(null));    //[[object Null]]

类型转换

显示转换

  • Number()转换

原始类型转换成Number类型的规则:

  • 数值:转换后还是数值
  • 字符串: 可以解析为对应的数值,则转换成对应的数值,否则,转换为NaN, 特别需要注意空字符串是转换成0
  • undefined: 转换成NaN
  • null: 转换成0
  • 布尔: true转换成1,false转换成0
  • Symbol: 报错

引用类型转换成Number类型的规则:

转换的目标是对象,先调用对象的valueOf方法,如果返回的是原始类型的值,那么按照上述规则,进行类型转换。如果返回的是复合类型,那么调用该对象的toString方法,如果toString返回的是原始值类型,则按照上述规则转换,否则报错

Number({a:1})
// valueOf() -> {a:1} -> toString() -> '[object Object]' -> Number('[object Object]')  ->  NaN

Number([1,2])
// valueOf -> [1,2] -> toString() -> '1,2' -> Number('1,2')  -> NaN

Number([1])
// valueOf -> [1] -> toString() -> '1' -> Number('1')  -> 1

Number([])
// valueOf() -> [] -> toString -> '' -> Number('') -> 0
  • String()转换

原始类型转换成String类型的规则:

  • 数值:转换成数值字符串
  • 字符串:字符串
  • null : 'null'
  • undefined: 'undefined'
  • 布尔: ‘true’或 ‘false’
  • Symbol: ‘Symbol’

引用类型转换成String类型的规则:

转换的目标是对象,首先调用对象的toString方法,如果返回的结果是个原始类型,则按照上述规则转换,如果返回的结果还是个对象类型,那么调用对象的valueOf方法,如果返回的是原始类型,按照上述规则转化,否则就报错

const a1 = {b:1}
a1.toString() // '[object Object]'

const a2 = [1,2,3]
a2.toString() // 1,2,3

const a3 = []
a3.toString() // ''

const a4 = function(){}
a4.toString() // 'function(){}'
  • Boolean()转换 除了undefined、null 、NaN、''、 0 其余的全部转成true

隐式转换

触发隐式转换的行为:四则运算、判断语句、native调用(console.log()和alert方法会内部调用string方法)

四则运算中除了加法(+),其余的运算符都会把非Number类型的转换为Number类型

+:作为算术运算符,会把其他类型通过Number方法转换为数值类型;当+两边有一边是字符串是,会被当做字符串连接符

1+2 // 3
1+'' // '1'
// 有字符串,其他类型转为字符串
1+false // 1
// 其他类型先转为number类型,再相加
1+null // 1
// 其他类型先转为number类型,再相加
1+undefined // NaN
// 其他类型先转为number类型,再相加
1+function(){} // '1function(){}'
// 引用类型先转换为值类型
1+[] // '1'
// 引用类型先转换为值类型
1+[1,2] // '11,2'
// 引用类型先转换为值类型

==:

  • NaN不等于任何值
  • String与Number进行比较时,String转换为Number
  • Boolean与其他类型进行比较,Boolean转换为Number
  • null 和undefined,相等
  • 引用类型和值类型进行比较,引用类型先转换为值类型
  • 引用类型和引用类型进行比较,判断是否指向同一个引用
[] == [] // false
// 引用类型比较,不指向同一个引用 false
[] == 0 // true
// [] 转换为值类型为'',调用Number('') -> 0 ,0 ==0 ,所以为true
![] == 0 // true
// ![] 转换为false,调用Number(false) -> 0 所以为true
![] == [] // true
// ![]转换为false,调用Number(false) -> 0,[]转化为值类型‘’,调用Number('') -> 0 ,所以为true
{}==!{} // false
// !{}转换成false,调用Number(false) ->0,{}转换为值类型'[object Object]' Number('[object Object]') -> NaN,所以为false
{} == {} // false
// 引用类型比较,不指向同一个引用为false

this

this指向什么,完全取决于什么地方以什么方式调用,而不是创建时

this的绑定规则:

  • 默认绑定
function foo(){
    var a = 1
    console.log(this.a)    // 10
}
var a = 10
foo()

典型的默认绑定,foo调用的位置前面没有任何前缀,我们可以看做是window.foo(),所以非严格模式下,this指向就是window,严格模式下是undefined

  • 隐性绑定
function foo(){
    console.log(this.a)
}

var obj = {
    a:10,
    foo:foo
}

foo(); // undefined
obj.foo() // 10

答案:undefined、10

foo()直接调用,跟默认绑定是相同的,就是window.foo() ,但是全局中没有定义a,所以是undefined

下面obj.foo()这样调用,是的foo执行的时候有了上下文对象,既obj,函数内的this默认绑定为上下文对象,等价于打印obj.a,输出10.

如果是链式的关系,比如xx.yy.obj.foo(),上下文取函数的直接上级,既紧挨着的那个,或者说对象链的最后一个.

  • 显示绑定

使用 call、apply,这几个的作用都是改变函数的this指向,第一个参数都是设置this对象。

这两个函数的区别:

  1. call从第二个参数开始所有的参数都是原函数的参数
  2. apply只接受两个参数,且第二个参数必须是数组
const obj = {a:1}

function foo(){
    console.log(this.a)
}

foo.call(obj) // 1
foo.apply(obj) // 1

-------

function foo(a,b){
    console.log(a+b);
}
foo.call(null,'海洋','饼干');        // 海洋饼干  这里this指向不重要就写null了
foo.apply(null, ['海洋','饼干'] );     // 海洋饼干

除了call、apply之外,还可以使用bind来改变this指向。调用bind函数之后,后者不会立即执行,而是将一个值绑定到函数的this上,并将绑定好的函数返回

function foo(){
    console.log(this.a)
}

const obj = {a : 10}

foo = foo.bind(obj)

foo() // 10
  • new 绑定
function Fn(){
    this.name = '听风是风';
};
let echo = new Fn();

闭包

能够访问其他函数内部变量的函数,被称为闭包

对上面这句话进行一番理解,简单来说,闭包就是函数内部定义的函数,被返回了出去并在外部被调用

function foo() {
  var a = 2;

  function bar() {
    console.log( a );
  }

  return bar;
}

var baz = foo();

baz(); // 这就形成了一个闭包

作用域

作用域是指程序中定义变量的区域,该位置决定了变量的生命周期,也就是变量或函数的可访问范围

new

new操作符可以帮助我们构建出一个实例,并且绑定this

模拟实现一个new

 function MyNew(){
     const obj = {} // 声明一个对象,也是最终返回的对象
     // 取出参数中的第一个参数,就是我们要传入的构造函数,此外,shift会改变原数组,所以arguments会被去除对一个参数
     Constructor = [].shift.call(arguments)
     // 将obj的原型指向构造函数的原型对象
     obj.__proto__ = Constructor.prototype
     
    const ret = Constructor.apply(obj,arguments)
 
     return typeof ret === 'object' ? ret :obj
 }

call、apply、bind

  • call、apply

callapply都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内的this的指向。

  • call、apply的区别

对于callapply而言,作用完全一样,只是接受参数的方式有区别,例如:

const func = function(a,b){}

// 使用两者调用
func.call(this,a,b)
func.apply(this,[a,b])

以上this是想要指定的上下文,call需要把参数按顺序传递进去,apply则是把参数放在数组里

  • bind bind同样能改变函数体内的this,跟callapply的区别在于,bind是返回绑定好this的对应的函数

模拟实现call apply apply

Function.prototype.call2 = function(context) {
    context = context || window
    
    context.fn = this
    
    const args = [].slice.call(arguments,1)
    
    const result = context.fn(...args)
    
    delete context.fn
    
    return result

}

Function.prototype.apply2 = function(context,args){
     context = context || window
    
    context.fn = this
    const result = context.fn(...args)
    
    delete context.fn
    
    return result
} 

Function.prototype.bind2 = function (context) {

    var self = this;
    // 获取bind2函数从第二个参数到最后一个参数
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
        // 这个时候的arguments是指bind返回的函数传入的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(bindArgs));
    }

}

原型

javascript中,函数可以有属性。每个函数都有一个特殊的属性叫做原型(prototype)

原型链

javascript对象通过__proto__指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,既原型链

继承

最优的一种继承:寄生式组合继承

function Parent() {
    this.name = 'parent'
}

function Child() {
    this.age = 22
    Parent.call(this)
}

Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

Promise

  • promise的作用

解决异步请求中的嵌套问题,可以更好的组织多层嵌套的异步回调,优雅的写法

模拟实现一个基础的promise

class MyPromise {
    constructor(executor) {
        // 初始化值
        this.initData();
        // 初始化this指向
        this.initBind();
        // promise有throw的话,就相对于执行reject
        try {
            executor(this.resolve, this.reject);
        } catch (error) {
            this.reject(error);
        }
    }

    initData() {
        // 终值
        this.PromiseResult = null;
        // 状态
        this.PromiseState = 'pending';
        // 保存成功回调
        this.onFulfilledCallbacks = [];
        // 保存失败回调
        this.onRejectedCallbacks = [];
    }

    initBind() {
        this.resolve = this.resolve.bind(this);
        this.reject = this.reject.bind(this);
    }

    resolve(value) {
        // 状态是不可变的,已经是reject了,就不允许执行下面的操作
        if (this.PromiseState !== 'pending') return false;
        this.PromiseResult = value;
        this.PromiseState = 'fulfilled';

        while (this.onFulfilledCallbacks.length) {
            // 从成功的回调结果中获取第一个回调函数,执行它
            this.onFulfilledCallbacks.shift()(this.PromiseResult);
        }
    }

    reject(reason) {
        // 状态是不可变的,已经是fulfilled了,就不允许执行下面的操作
        if (this.PromiseState !== 'pending') return false;
        this.PromiseResult = reason;
        this.PromiseState = 'rejected';

        while (this.onRejectedCallbacks.length) {
            // 从成功的回调结果中获取第一个回调函数,执行它
            this.onRejectedCallbacks.shift()(this.PromiseResult);
        }
    }

    then(onFulfilled, onRejected) {
        const thenPromise = new MyPromise((resolve, reject) => {
            const resolvePromise = cb => {
                setTimeout(() => {
                    try {
                        const x = cb(this.PromiseResult);

                        if (x === thenPromise) {
                            throw new Error('不能返回自身');
                        }
                        if (x instanceof MyPromise) {
                            x.then(resolve, reject);
                        } else {
                            resolve(x);
                        }
                    } catch (error) {
                        reject(error);
                    }
                });
            };

            if (this.PromiseState === 'fulfilled') {
                // 成功状态,执行第一个回调
                resolvePromise(onFulfilled);
            } else if (this.PromiseState === 'rejected') {
                // 失败状态,执行第二个回调
                resolvePromise(onRejected);
            } else if (this.PromiseState === 'pending') {
                // 如果是待定状态,暂时保存两个回调
                this.onFulfilledCallbacks.push(onFulfilled.bind(this));
                this.onRejectedCallbacks.push(onRejected.bind(this));
            }
        });
        // then方法需要返回包装过的promise

        return thenPromise;
    }

    all(promises) {
        const result = [];

        let count = 0;

        return new MyPromise((resolve, reject) => {
            const addData = (index, value) => {
                result[index] = value;
                count++;
                if (count === promises.length) resolve(result);
            };

            promises.forEach((promise, index) => {
                if (promise instanceof MyPromise) {
                    promise.then(res => {
                        addData(index, res);
                    }, err => reject(err));
                } else {
                    addData(index, promise);
                }
            });
        });
    }

    race(promises) {
        return new MyPromise((resolve, reject) => {
            promises.forEach(promise => {
                if (promise instanceof MyPromise) {
                    promise.then(res => {
                        resolve(res);
                    }, err => reject(err));
                } else {
                    resolve(promise);
                }
            });
        });
    }
}

事件循环

js可以分为宏任务和微任务

宏任务:

  • script代码
  • setTimeout
  • setInterval
  • setImmediate
  • UI render

微任务:

  • promise
  • process.nextick
  • async/await
  • mutationObserver

事件循环通俗的解释就是,进入主线程,执行宏任务,然后是执行该宏任务产生的微任务,如果微任务执行的过程中又产生了微任务,则继续执行微任务,微任务执行完之后,再回到宏任务中进行下一轮循环。

举个例子:

console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)

new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})

console.log('script end')

// 输出顺序  script start 、async2 end 、promise、script end、async1 end、promise1、promise2、setTimeout

节流防抖

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

带有立即执行选项的防抖函数


/**
 * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
 *
 * @param  {function} func        回调函数
 * @param  {number}   wait        表示时间窗口的间隔
 * @param  {boolean}  immediate   设置为ture时,是否立即调用函数
 * @return {function}             返回客户调用函数
 */
 
 function debounce(func,wait=50,immediate=true){
     let timer,context,args;
     
     // 延迟执行函数
     const later = ()=>{
         setTimeout(()=>{
             // 延迟执行函数执行完毕,清除缓存的定时器
             timer = null
             // 延迟执行的情况下,函数会在延迟函数中执行
             // 使用之前缓存的参数和上下文
             
             if(!immediate){
                 func.apply(context,args)
                 context = args = null
             }
         },wait)
     }
     
     return function (...params) {
         // 如果没有创建延迟执行函数,则创建一个
         if(!timer) {
             timer = later()
             // 如果是立即执行,调用函数,否则缓存参数和上下文
             if(immediate) {
                 func.apply(this,params)
             } else {
                 context = this;
                 args = params
             }
         } else {
             clearTimeout(timert)
             timer = later()
         }
     }
 }
 
 
 function throttle(fn, wait) {
	let prev = new Date();
	return function() { 
	    const args = arguments;
            const now = new Date();
            if (now - prev > wait) {
                    fn.apply(this, args);
                    prev = new Date();
            }
	}

柯里化

柯里化就是将多个参数的函数转化成接收一个参数的函数

function curry(func){
    return function curried(...args){
        if(args.length>=func.length){
            return func.apply(this,args)
        }else {
            return function(...args2){
                return curried.apply(this,args.concat(args2))
            }
        }
        
    }

}

深拷贝

 function deepClone(target, map = new Map()) {
      if (typeof target !== 'object' || target === null) {
        return target
      }
      const res = Array.isArray(target) ? [] : {}

      if (map.get(target)) {
        return map.get(target)
      }
      map.set(target, res)
      for (const key in target) {
        res[key] = deepClone(target[key])
      }

      return res
    }

数组扁平化

function flat(data) {
      data.reduce((prev, cur) => {
        return Array.isArray(cur) ? [...prev, ...flat(cur)] : [...prev, cur]
      }, [])
    }

Promise应用

// 实现一个红绿灯交换的案例
    function red() {
      console.log('red')
    }

    function green() {
      console.log('green')
    }

    function yellow() {
      console.log('yellow')
    }

    const light = function (timer, cb) {
      return new Promise((resolve) => {
        setTimeout(() => {
          cb()
          resolve()
        }, timer)
      })
    }

    const step = function () {
      Promise.resolve().then(() => {
        return light(3000, red)
      }).then(() => {
        return light(2000, red)
      }).then(() => {
        return light(1000, yellow)
      }).then(() => {
        return step()
      })
    }