comTg / Blog

blog
0 stars 0 forks source link

Javascript #7

Open comTg opened 2 years ago

comTg commented 2 years ago

apply和call的区别

1. apply(): 传入两个参数,一个作为函数上下文的对象,另外一个是作为函数参数所组成的数组。

var obj = {
  name: 'test'
}
function func (firstName, lastName) {
  console.log(firstName + ' ' + this.name + ' ' + lastName)
}

func.apply(obj, ['A', 'B'])
  1. call(): 第一个参数也是作为函数上下文的对象,但是后面传入的是一个参数列表,而不是单个数组

func.call(obj, 'C', 'D')

对于什么时候该用什么方法,其实不用纠结。如果你的参数本来就存在一个数组中,那自然就用 apply,如果参数比较散乱相互之间没什么关联,就用 call。

apply和call的用法

  1. 改变this的指向

    var obj2 = {
    name: 'test'
    }
    function func2() {
    console.log(this.name)
    }
    func2.call(obj2)

    call 方法的第一个参数是作为函数上下文的对象,这里把 obj 作为参数传给了 func,此时函数里的 this 便指向了 obj 对象。

  2. 借用别的对象的方法

    var Person1 = function () {
    this.name = 'test person1'
    }
    var Person2 = function () {
    this.getname = function () {
    console.log(this.name)
    }
    Person1.call(this)
    }
    var person = new Person2()
    person.getname()

    Person2 实例化出来的对象 person 通过 getname 方法拿到了 Person1 中的 name。因为在 Person2 中,Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,那么 Person2 就有了 Person1 中的所有属性和方法了,相当于 Person2 继承了 Person1 的属性和方法

  3. 调用函数 apply, call 方法都会使函数立即执行,因此它们可以用来调用函数

call和bind的区别

在 EcmaScript5 中扩展了叫 bind 的方法,在低版本的 IE 中不兼容。它和 call 很相似,接受的参数有两部分, 第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数。 它们之间的区别有以下两点

  1. bind返回值是函数

    var obj3 = {
    name: 'test obj3'
    }
    function func3 () {
    console.log(this.name)
    }
    var func4 = func3.bind(obj3)
    func4()

    // bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window。

  2. 参数的使用

    function func5 (a, b, c) {
    console.log(a, b, c)
    }
    var func6 = func5.bind(null, 'test 6')
    func5('A', 'B', 'C')
    func6('A', 'B', 'C')
    func6('B', 'C')
    func5.call(null, 'test 5')

    call 是把第二个及以后的参数作为 func 方法的实参传进去,而 func1 方法的实参实则是在 bind 中参数的基础上再往后排。

低版本浏览器没有bind方法, 可以自己实现一个

if (!Function.prototype.myBind) {
  Function.prototype.myBind = function () {
    var self = this,                        // 保存原函数
        context = [].shift.call(arguments), // 保存需要绑定的this上下文
        args = [].slice.call(arguments);    // 剩余的参数转为数组
    console.log('args:', args, 'arguments:', arguments);
    return function () {
      self.apply(context, [].concat.call(args, [].slice.call(arguments)));
    }
  }
}
comTg commented 2 years ago

new,call,apply,bind方法的实现原理

  1. new
    • new的原理
    • 获取构造函数
    • 创建一个新对象
    • 将函数的作用域赋给新对象(实际上生产了一个新的上下文)
    • 执行函数中的代码
    • 返回值,无返回值或者返回一个非对象时,则将创建的新对象返回,否则会将返回值作为新对象返回。
function myNew () {
  let Constructor = Array.prototype.shift.call(arguments); // 1. 取出构造函数
  let obj = {}; // 2. 执行会创建一个新对象
  obj.__proto__ = Constructor.prototype;
  var result = Constructor.apply(obj, arguments);
  return typeof result === 'object' ? result : obj;
}
  1. call的实现

call方法的实现主要有以下三步,比如 fn.call(obj, a, b) :

Function.prototype.myCall = function (context) {
  context = context ? Object(context) : window;
  context.fn = this // 重置上下文
  let args = [...arguments].slice(1)
  let r = context.fn(...args) // 执行函数
  delete context.fn // 删除属性
  return r
}
  1. apply的实现

与call大同小异,唯一差别就是apply传入的参数时数组格式

Function.prototype.myApply = function (context) {
  context = context ? Object(context) : window
  context.fn = this
  let args = [...arguments][1]
  if (!args) {
    return context.fn()
  }
  let r = context.fn(...args);
  delete context.fn
  return r
}
  1. bind方法的实现

bind方法和call, apply方法的差别是,他们都改变了上下文,但是bind没有立即执行函数

Function.prototype.myBind = function () {
  let self = this,
      context = [].shift.call(arguments),
      args = [].slice.call(arguments);
  return function () {
    self.apply(context, [].concat(args, [].slice.call(arguments)));
  }
}
comTg commented 2 years ago

空值合并操作符(??)

空值合并操作符 ?? 类似于 || 运算符,当其左侧的操作数为 null 或者 undefined 时,返回右侧的操作数,否则返回左侧的操作数。

const value = value1 ?? value2;
// 等价于
const value = (value1 === undefined || value1 === null) ? value2 : value1;

const val1 = null ?? 'test' // test
const val2 = undefined ?? 'test' // test

从定义上看,空值合并操作符 ?? 类似于 || 运算符,但是是很不同的,看接下来的几个例子.

const val3 = 0 ?? 'test' // 0
const val4 = '' ?? 'test' // ''
const val5 = NaN ?? 'test' // NaN
const val6 = false ?? 'test' // false

|| 左侧的操作数会被强制转换成布尔值。因此,当使用 || 时,需要考虑左侧是否为 0, '', NaN, false 等值,如果为这几个值时,可能会出现非预期的结果。 注意,当 ?? 直接与 && 或 || 组合使用时,必须加上括号,否则会抛出 SyntaxError

null || undefined ?? "default"; // Uncaught SyntaxError: Unexpected token '??'
(null || undefined ) ?? "default"; // 返回 "default"

可选链操作符(?.)

可选链操作符(?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效 可用于属性或方法

obj?.first
obj.test?.()
comTg commented 2 years ago

闭包的作用

闭包有三步: 第一,外层函数嵌套内层函数; 第二, 内层函数使用外层函数的局部变量; 第三,把内层函数作为外层函数的返回值! 经过这样的三步就可以形成一个闭包! 闭包就可以在全局函数里面操作另一个作用域的局部变量! 当内部函数被保存到外部时,就会形成闭包; 闭包会导致原有作用域链不释放,造成内存泄露; 闭包作用:

闭包既能重复使用局部变量,又不污染全局! 实现公有变量,比如说函数累加器 做缓存 实现属性的私有化

comTg commented 2 years ago

节流与防抖

增设执行回调等待时间来降低触发事件执行回调的频率

 function debounce(callback, delay) {
   let timer = null;
   return function () {
     let args = Array.prototype.slice.call(arguments)
     let context = this
     if (timer !== null) {
       clearTimeout(timer)
     }
     timer = setTimeout(() => {
       callback.apply(context, args)
     }, delay)
   }
 }
 function throttle (func, delay) {
   let timer = null
   return function () {
     const context = this
     const args = Array.prototype.slice(arguments)
     if (!timer) {
       timer = setTimeout(function () {
         func.apply(context, args)
         timer = null;
       }, delay)
     }
   }
 }