JavaScript重难点

一、call、apply、bind(改变函数内部this指向)

使用场景:一般绑定方法的时候使用bind;在调用其他实例方法时候多用call/apply

1.call的作用

①.可以调用函数

var obj = {
    name:'alhh'
}
function fn(){
console.log(this)
}
fn.call() //window

②.改变this指向

var obj = {
    name:'alhh'
}
function fn(){
console.log(this)
}
fn.call(obj) //{name:'alhh'}

应用:主要作用用来实现继承

function Father(name,age){
  this.name = name
  this.age = age  
}
function Son(name,age,sex){
    Father.call(this,name,age,sex)
    this.sex = '男'
}

var son = new Son('al','30')
console.log(son) // {name: "al", age: "30", sex: "男"}

手写call原理:

Function.prototype.myCall = function (context){
  if(typeof this !=='function'){
    throw new TypeError('Error')
  }
  var context = context || window; //第一个参数为调用call方法的函数中的this指向
  context.fn = this; //将this赋值给context的fn属性 此处this指的是调用myCall的function
  const args = [...arguments].slice(1)
  const result = context.fn(...args); //去除参数的第一个值后执行这个添加的函数
  delete context.fn; //删除这个属性
  return result;
};

2.apply作用

①.可以调用函数

②.可以改变this的指向

var obj = {
name:'alhh'
}
function fn(){
console.log(this)
}
fn.apply(obj) //{name:'alhh'}

与call不同的是,传递的参数是数组形式或者伪数组

var obj ={
name:'alhh'
}
function fn(args){
console.log(this) //{name:'alhh'}
console.log(args) //字符串形式hello
}

fn.apply(obj,['hello'])

应用:求数组中的最大值

var arr =[1,33,444,55,666]
//如果不需要改变this指向第一个参数可以为null
var max =Math.max.apply(null,arr) 或者 //Math.max.apply(Math,arr)
console.log(max) //666

手写apply原理:和call类似主要处理参数

Function.prototype.myApply = function (context,args){
 if(typeof this !=='function'){
    throw new TypeError('error')
}    
    var context = context || window
    context.fn = this
    const result = context.fn(..args)
    delete context.fn
    return result
}

3.bind作用

与call和apply不同的是 bind不会调用函数,返回的是由指定的this值和初始化参数改造的原函数的拷贝

相同的是可以改变this的指向;第一个参数是this指向,后面的参数和call相同 不是数组

var obj = {
name:'alhh'
}
function fn(){
console.log(this)
}
var newFn =fn.bind(obj)
newFn() //{name: "alhh"}

 应用:如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向 此时使用this

 二、new的实现

首先需要知道new的时候做了什么事情

1.创建一个新的对象

2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象)

3.执行构造函数里面的代码(为这个新对象添加属性)

4.返回新对象

function myNew(){
//创建一个新的实例对象
 var obj = new Object()
//取得外部传入的构造器
 var Constructor = Array.prototype.shift.call(arguments)
//实现继承,实例可以访问构造器的属性
 obj.__proto__ = Constructor.prototype
//调用构造器,并改变this指向到实例 (参数是数组,要用apply)
 var res = Constructor.apply(obj,arguments)
//如果构造函数返回值是对象则返回这个对象,如果不是对象则返回新的实例对象
 return typeof res ==='object'?res:obj
}

还可以用Object.create(),他的属性值是放在原型下面的。

function myNew(fn,...args){
  var obj = Object.create(fn.prototype)
  var res = fn.apply(obj,...args)
  return typeof res ==='object'?res:obj //new里面可能会有返回值,返回值是对象
}

例子:

function Person(name){
    this.name = name
}
var old = new Person('用new的name')
var new1 = myNew(Person,'没用new的name')

console.log(old,new) //{name: "用new的name"} , {name: "没用new的name"}

三.Object.create(obj,propertiesObject)

obj:创建对象的原型,表示要继承的对象 必选

propertiesObject:也是一个对象,用于对新创建的对象进行初始化

底层原理

Object.create = function(o){
  var F = function(){}
  F.prototype = o
  return new F()    
}

继承的应用

var A = funtion(){}
A.prototype.say = function(){
console.log('a')
}
//B的实例继承了A的属性
var B = function(){}
B.prototype = Object.create(A.prototype)
var b = new B()
b.say() //a
//重点:相对于构造函数的继承(new Object),Object.create继承实现了将A、B的原型完美分隔,双方不会互相影响,这是Object.create亮点所在

Object.create() VS new Object()

1.创建对象的方式不同

new Object()通过构造函数来创建对象,添加的属性是在自身实例下

Object.create() es6创建对象的另一种方式,可以理解为继承一个对象,添加的属性是在原型下

//new Object()方式创建
var a = {res:'apple'}
var b = new Object(a)
console.log(b) //{res:'apple'}
b.__proto__ //{}
b.res //{res:'apple'}

//Object.create()方式创建
var a = {res:'apple'}
var b = Object.create(a)
console.log(b) //{}
console.log(b.__proto__) //{res:'apple'}
console.log(b.res) //{res:'apple'}
//Object.create()方法创建对象时,属性是在原型下面的,也可以直接访问b.res此时这个值不是b自身的,是它通过原型链proto来访问到b的值

四.js防抖和节流

防抖和节流是js性能优化很重要的一点,它们主要针对在一些短时间内被频繁触发的事件,例如:监听输入框的输入事件来验证表单,监听页面的滚动事件来实现列表的加载,窗口的resize事件等等,这些事件都有触发频率高,间隔时间短的特点,如果这个事件的回调函数涉及到很多的计算以及DOM的重绘的话,就可能会导致卡顿,影响用户的体验,所以最好用防抖和节流函数处理一下

防抖(debounce)

原理:在事件触发一定毫秒之后再执行

function debounce(func,wait){
 let timer
 return function(){
 let self = this;
 let args = arguments
if(timer){
  clearTimeout(timer)
}
timer = setTimeout(function(){
  func.apply(self,arguments)
},wait)
}
}

//使用
window.onscroll = debounce(function(){console.log('debounce')},1000)

节流(Throttle)

原理:保证回调函数在一个时间段内只执行一次,通过计算时间差,如果已经执行过了,清除定时器,重新开始计时,否则就执行回调函数(每隔一段时间触发一次,像水滴一样)

function throttle(fn,wait){
    let preTime = Date.now()
    return function(){
    let curTime = Date.now()
    if(curTime - preTime >wait){
    fn.apply(this,arguments)
    preTime  = curTime 
    }
    }
}
//应用
window.onscroll = throttle(function(){
console.log('throttle')
},1000)

 五. es6的generator

主要用于异步编程,最大的特点是可以交出函数的执行权(即暂停执行)

和普通函数的区别

1:function关键字与函数名之间有一个星号

2:就是Generator函数体内部使用yield语句,可以定义不同的内部状态(内部的状态,就是函数内部的值,它在不同时候 是不一样的)

next方法可以接收参数,传入的参数,是把上一个yield语句的返回的值给覆盖了

第一个 .next()方法其实是启动器,在它之前没有yield语句,所以给第一个.next()方法去传参是没意义的

generator函数 支持for of循环

原文地址:https://www.cnblogs.com/alhh/p/11891229.html