也到了自己快找工作的时候了,所以最近在复习前端的知识,这里是自己对JavaScript的一些知识点的总结,以后会持续更新,分享自己的复习知识,有些是在不懂的时候参考大佬的讲解。有些理解不到位的地方还请指正。祝自己找工作顺利!!!

1、bind、call、apply

这三个函数都会改变this的指向,call和apply更适用于在函数运行时改变this;而bind会返回一个新的函数,新函数的this由bind传入的参数决定,所以bind更适用于返回一个新函数,这个函数在将来才会执行,比如DOM添加事件。

// call
Function.prototype.myCall = function (ctx = window, ...arg) {
  if (typeof this !== "function") return
  ctx.fn = this
  let res = ctx.fn(...arg)
  delete ctx.fn
  return res
}
// apply 
Function.prototype.myApply = function (ctx = window, arg) {
  if (typeof this !== "function") return
  ctx.fn = this
  if(!Array.isArray(arg)) {
    throw new Error('需要数组')
  }
  let res = ctx.fn(...arg)
  delete ctx.fn
  return res
}
// bind
Function.prototype.newbBind = function(target){
    target = target || window
    var self = this;
    // 这里的arguments是在调用时传入的参数
    var args = [].slice.call(arguments, 1);
    var temp = function () {}

    function f(){
        // 这里的arguments是bind返回的新函数传入的参
        var _args = [].slice.call(arguments,0)//将一个类数组转化为数组

        return self.apply(this instanceof temp? this : target, args.concat(_args))
    }
    temp.prototype = self.prototype
    f.prototype = new temp()
    return f
}
复制代码

2、函数柯里化

在Lambda演算(一套数理逻辑的形式系统,具体我也没深入研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,因为高阶函数是可以当参数传递和返回值的,所以问题就简化为:写一个只有一个参数的函数,而这个函数返回一个带参数的函数,这样就实现了能写两个参数的函数了——这就是所谓的柯里化(Currying,以逻辑学家Hsakell Curry命名),也可以理解为一种在处理函数过程中的逻辑思维方式。

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}

function multiFn(a, b, c) {
    return a * b * c;
}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);

// 参考:https://juejin.im/post/5c9c3989e51d454e3a3902b6

复制代码

3、原型、原型链

1、原型

原型是function的一个属性,该属性本质上是一个对象,它定义了构造函数构造出来的共有祖先,构造函数产生的实例对象可以继承该属性的方法和属性,当实例访问某个属性找不到就会顺着原型链访问该属性。

2、原型链

有了原型,原型还是一个对象,那么这个名为原型的对象自然还有自己的原型,这样的原型上还有原型的结构就构成了原型链。

原型链是是描述实例对象与构造函数的原型之间的关系,如果实例对象找不到某个属性或者方法就会到构造函数的prototype上查找,如果还是找不到就会访问构造函数prototype属性的__proto__属性,直到null。

4、继承

在JavaScript中没有类的概念,传统语言中类通过拷贝实现继承,JavaScript通过原型链、原型委托的方式实现继承。

组合继承:

function Father() {
    this.name = "father"
}
Father.prototype.say = function() {
    console.log('say')
}
function Child() {
    Father.call(this)
    this.age = 12
}
Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child
复制代码

圣杯模式

let inhert = (function() {
    function F(){}
    return function(father, child){
        F.prototype = father.prototype
        child.prototype = new F()
        child.prototype.constructor = child
    }
})
复制代码

5、this

参考:juejin.im/post/5c96d0…

1、什么是this?

this是JavaScript中的一个关键字,被自动定义在所有函数的作用域中。**this是在运行的时候进行绑定,并不是在编写的时候进行绑定,它的上下文取决于函数调用时的各种条件。**this的绑定和函数的声明位置无关,只取决于函数的调用方式。

当一个函数被调用的时候,会创建一个活动记录(也成为执行上下文)。这个记录会包含函数在哪里调用(调用栈)、函数调用的方法、传入的参数等信息。this就是记录中的一个属性,会在函数执行的过程中用到。

2、调用位置

调用位置指的是函数被调调用的位置而不是声明的位置。

3、绑定规则

1、默认绑定

默认绑定的时候this指向window,默认绑定是指函数不带任何修饰的函数引用进行调用。比如:

function foo() {
    console.log(this)
}
foo() // window
复制代码

但是需要注意的是在严格模式下,默认绑定并不会指向window

2、隐式绑定

隐式绑定通常以对象作为执行上下文调用。但是我们需要明白一个道理:不管是在对象中声明一个函数,还是先定义再添加函数的引用,严格来叔这个函数都不属于该对象。

隐式绑定规则会把函数调用中的this绑定到这个上下文对象,因为调用foo的时候this被绑定到该对象,因此this.a等同于obj.a。

对象属性引用链中只有最后一层会影响调用的位置。

let obj2 = {
  a:2,
  foo1:foo1
}
let obj1 = {
  a:1,
  obj2:obj2
}
function foo1() {
  console.log(this.a)
}
obj1.obj2.foo1() // 2
复制代码
1、隐式绑定丢失
var a = 'window'
let obj = {
  a: 'obj',
  foo() {
    console.log(this.a)
  }
}
let bar = obj.foo
bar()
复制代码

因为bar是obj.foo的一个引用,但是实际上引用的是foo函数的本身,因此bar()是一个不带任何修饰符的调用所以是默认绑定,this指向window。

var a = 'window'
let obj = {
  a: 'obj',
  foo() {
    console.log(this.a)
  }
}
function doFoo(fn) {
  fn()
}
doFoo(obj.foo)
复制代码

这里调用doFoo的时候参入了obj.foo作为实参,并将obj.foo赋值给fn,所以fn是foo函数的引用,在调用fn的时候也是不带任何修饰的调用,所以是默认调用this指向window。

以下这种情况this也是指向window。原因和上面一样。

var a = 'window'
let obj = {
  a: 'obj',
  foo() {
    console.log(this.a)
  }
}
setTimeout(obj.foo, 1000)
复制代码

所以上面我们可以看出回调函数丢失this是非常常见的。

3、显示绑定

  • call
  • apply
  • bind:bind会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。

如果把null或者undefined作为this绑定的对象传入其中,这些值会被忽略,实际上是默认绑定。

4、new绑定

5、绑定优先级

new > call、apply、bind > 隐式绑定 > 默认绑定

6、防抖节流

节流(throttle)是防止用户频繁操作,造成浏览器性能消耗过大

防抖(debounce),就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

// 节流函数(throttle)
function throttle (fn, wait=500) {
    let pre_time = 0
    return function(...arg) {
        let curr_time = Date.now()
        if(curr_time - pre_time > wait) {
            fn.apply(this, arg)
            pre_time = curr_time
        }
    }
}
// 防抖函数(debounce)
function debounce(fn, wait = 500, immediately = true) {
    let timer
    return function(...arg) {
        if(immediately) {
            fn.apply(this, arg)
            immediately = false
        }
        clearTimout(timer)
        timer = setTimout(()=> {
            fn.apply(this, arg)
        }, wait)
    }
}
复制代码

7、Promise

面试常见问题:

1、了解 Promise 吗?

2、Promise 解决的痛点是什么?

3、Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。

4、Promise 如何使用?

5、Promise 常用的方法有哪些?它们的作用是什么?如何使用?

6、Promise 在事件循环中的执行过程是怎样的?

7、Promise 的业界实现都有哪些?

8、能不能手写一个 Promise ?

function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    function resolve(value){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}
myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}

// 来源:https://github.com/forthealllight/blog/issues/4
复制代码

1、promise含义

promise是异步编程的一种解决方案,解决了回调地狱的问题。Promise是一个容器保存着某个未来才会结束的事件的结果,也可以说是一个对象从它可以获取异步操作的消息

特点:

  1. 对象状态不受外界影响,只有pending(进行中)、fulfilled(已成功)和rejected(已失败)三种状态。
  2. 一旦状态改变就不会再改变。只能从pending(进行中)到fulfilled(已成功)或pending(进行中)到reject(以失败)。

2、基本语法

const promise = new Promise(function(resolve, reject) {
    if(/*success*/) {
        resolve(val)
    } else {
        reject(val)
    }
})
复制代码

Promise接受一个函数作为参数,该函数接受两个参数,它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve的作用是在异步操作成功的时候调用,并将异步操作的结果作为参数传递出去;reject是在异步操作失败的时候调用。

Promise实例生成之后可以用then方法分别指定成功和失败的回调函数

promise.then(function() {
    /*success*/
}, function() {
    /*failure*/
})
复制代码

第一个参数是成功时调用,第二个是失败时调用,这两个函数都接受Promise对象传出的值作为参数,第一个成功时的回调函数时必须的失败时的回调函数不是必须的。

resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})


const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail
// 这里p2的状态决定p1的状态,p2后的then都是针对p1的
复制代码

这里p2的状态决定p1的状态,p2后的then都是针对p1的

Promise的具体例子:

function timeout(ms) {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, ms, 'done');
  })
}
let p = timeout(100).then((val) => {
  console.log(val)
})
复制代码

Promise创建之后会立即执行

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// promise
// Hi!
// resolved.
复制代码

实现Ajax

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});
复制代码

3、promise.prototype.then

then方法是定义在原型对象Promise.prototype上的,它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法也可以返回一个新的Promise实例,因此可以采用链式调用:

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function funcA(comments) {
  console.log("resolved: ", comments);
}, function funcB(err){
  console.log("rejected: ", err);
});
复制代码

4、promise.prototype.catch

用于错误的捕获

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });
复制代码

上面代码中,第二种写法要好于第一种写法,理由是**第二种(catch)写法可以捕获前面then方法执行中的错误,**也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。

5、promise.prototype.finally

finally方法用于执行不管最后状态如何,都会执行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码

6、promise.all()

该方法用于将多个Promise实例包装成一个新的Promise实例。

const p = Promise.all([p1, p2, p3])
复制代码

Promise.all()接受一个数组,数组的值都是Promise对象,如果不是则会调用Promise.resolve()方法。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

7、promise.race()

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3])
复制代码

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。和Promise一样,数组的值必须是promise对象,如果不是则会调用Promise.resolve()方法。

8、Promise.resolve()

Promise.resolve()方法可以将现有的对象转换为Promise对象。

Promise.resolve('foo')
// 等同于
new Promise(function(resolve) {
    resolve('foo')
})
复制代码

9、Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

let p = Promise.reject('foo')
// 等同于
new Promise((resolve, reject) => reject('foo'))
复制代码

8、深拷贝

因为数组和对象都是引用值,所以当我们直接使用=赋值,会是两个对象的指针指向同一个空间,当我们改变其中一个值的时候,另一个对象也会受到影响。当我们使用深拷贝重新开辟了一个内存空间,将该对象的指针指向新开辟的空间。

针对数组我们可以使用[...arr]

针对对象我们可以使用Object.assign({}, obj)、{...obj}

以上两种都是浅拷贝

也可以使用JSON.parse(JOSN.stringify(obj))

function deepClone(obj) {
    let res
    if(typeof obj === "object") {
        res = obj.constructor = Array?[]:{}
        for(let i in obj) {
            res[i] = typeof obj[i] === "object"?deepClone(obj[i]):obj[i]
        }
    } else {
        res = obj
    }
    return obj
}
复制代码

9、JavaScript事件循环

juejin.im/post/5bac87…

JavaScript将任务分为同步任务和异步任务,在第一次执行的时候会将整个script代码看作宏任务同步任务进入主线程,异步任务进入Event Table注册,当满足条件异步任务的回调函数加入到Event Queue队列中,当主线程空闲的时候,会从Event Queue取出对应的函数。宏任务(script、setTimeout)和微任务(Promise、process.nextTick)分别进入不同的Event Table,它们的执行顺序不一样,当主线程空闲的时候首先会清空微任务队列,然后再拿出一个宏任务队列的函数,然后再检查微任务队列,如此循环。

10、作用域、作用域链、执行上下文、预编译

1、作用域

作用域是在运行时代码中的特定变量的有效范围。作用域决定了代码区块中变量和其他资源的可见性。作用域内层可以看见作用域外层,作用域外层不能看见作用域外层,所以作用域在不同作用域中声明的变量不会造成污染和命名冲突。

  • 全局作用域

定义在最外层的函数和变量,未经声明就赋值的变量,window的属性。这里需要注意的是var声明的全局变量以及未经声明就赋值的变量会挂载到window属性上,但是var声明的变量不能删除,未经声明的变量可以删除。

  • 函数作用域

当函数执行的时候就会在内部创建一个函数作用域,当函数执行完成就会销毁该作用域。

  • 块级作用域

在ES6之前是没有块级作用域的,ES6引入了let、const关键字就可以创建块级作用域。

2、作用域链

当在一个函数内部搜索一个变量的时候,如果该函数没有声明该变量,那么就会顺着代码执行环境创建的作用域逐层向外搜索,一直搜索到全局作用域。

3、执行上下文

解释阶段:

  • 词法分析
  • 语法分析
  • 作用域规则确定()

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript在解释阶段便会确定作用域规则,但是执行上下文是在函数执行的前一刻。

执行上下文最明显的就是this指向是在执行的时候确定的。

区别:执行上下文在运行时确定,随时可以改变;作用域在定义时就确定,并且不会改变。同一作用域下,不同的调用会产生不同的执行上下文,从而产生不同的结果。

4、预编译

  1. 创建AO对象
  2. 寻找形参和变量声明
  3. 形参实参相统一
  4. 找函数声明,函数名作为属性名,函数体作为属性值

5、闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域外执行。简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。

创建闭包:

  1. 在函数内部引用外部函数
let a = 1
function foo() {
  console.log(a)
}
function bar() {
  let a = 2
  foo()
}
bar() // 1
复制代码
  1. 在函数内部返回函数
let a = 'window'
function foo() {
    let a = 'foo'
    return function() {
        console.log(a)
    }
}
let bar = foo()
bar() // foo
复制代码

闭包的应用和缺陷

  1. 设计私有的方法和变量。
  2. 容易造成内存泄露。

11、DOM事件

1、DOM事件级别

  1. DOM0:dom.onclick = function(){}
  2. DOM2:dom.addEventListenner('click',function(){})
  3. DOM3:dom.addEventListenner('keyup',fuction(){}),增加了事件类型

2、DOM事件模型

dom.addEventListenner('keyup',fuction(){}, false|true),第三个参数为false表示在冒泡阶段触发,第三个参数为true表示在捕获阶段触发。先捕获后冒泡。

  1. 捕获:父元素到子元素,dom.addEventListenner('keyup',fuction(){}, true)
  2. 冒泡:子元素到父元素,dom.addEventListenner('keyup',fuction(){}, false)

事件委托就是基于事件冒泡的,当子元素触发点击事件会冒泡到父元素,然后通过e.target来判断子元素。

3、DOM事件流

通过冒泡或者捕获怎么到达目标对象的阶段

事件首先通过捕获到达目标元素,再通过目标元素冒泡到window对象,即先捕获后冒泡

4、Event常见对象

  • 阻止默认行为:e.preventDefault()
  • 阻止冒泡:e.stopPropagation()
  • 阻止其他绑定的事件的执行(事件响应优先级):e.stopImmediatePropagation()
  • e.target
  • e.currentTarget

5、自定义事件

参考:www.jianshu.com/p/71bb3cf19…

// 1.第一种
// 定义
let eve = new Event('coustome')
// 绑定
dom.addEventListenner('coustome', function(){})
// 触发
dom.dispatch(eve)

// 2.第二种,可以添加数据
let eve1 = new CustomoeEvent('coustome', {data})
复制代码

12、new

  1. 创建一个新对象
  2. 将该对象的__proto__属性指向函数的prototype属性
  3. 将this指向该对象
  4. 如果该函数没有显示的返回对象则返回创建的对象
function New(fn, ...arg) {
    let res = {}
    if(fn.prototype !== null) {
        res = Object.create(fn.prototype)
    }
    let ret = fn.apply(res, arg)
    if(ret === "object" || ret === "function" && ret !== null) {
        return ret
    }
    retrun res
}
复制代码

13、JavaScript数据类型

  • object:包括Function、Date、Array等
  • number:数值,NaN和自身不相等,但是可以通过Object.is()来判断
  • string:字符串
  • boolean:布尔
  • null:原型链的终点
  • undefined:表示变量声明还没有被赋值
  • symbol:ES6新增,表示独一无二的值

1、隐式转换

2、显示转换

3、包装类

语法:let str = new String('hello world')

当我们声明一个字符串变量的时候let str1 = 'hello',这是字面量的形式,并且是一个不可变的值。我们访问str1.length属性、或其他属性的时候,就会把该变量转换成为一个String对象(这里通常叫做包装类),因为声明的字符串没有该属性,只有转换为包装类才有。在JavaScript中会把字符串字面量转化成String对象

4、null、undefined比较

null在数值转换时被转换为0,undefined会被转换为NaN

  • nudefined

undefined只有一个值,即undefined。以下情况会出现undefined:

  1. 定义变量,但是没有初始化;
  2. 调用某个函数时,实参个数小于形参个数时,未实参化的形参在函数调用过程中的值是undefined;
  3. 访问对象没有的属性
  4. 函数默认的返回值
  5. 为初始化的变量执行typeof
  6. 未声明的变量执行typeof
  • null

null也只有一个值,但是当我们执行typeof null的时候,会返回object。我们可以理解为null是一个空指针对象,还没有保存对象。以下几种情况会使用出现null:

  1. 手动设置为null,比如在释放变量的时候
  2. 未获取到DOM节点
  3. 原型链顶端
  4. 在正则捕获的时候,如果没有捕获到结果,默认也是null

5、判断数据类型

  • typeof

不能区别null、对象、数组、正则表达式等

  • instanceof

是基于原型链操作的:A instanceof B,判断A的原型链上有没有B的原型

  • Object.prototype.toString.call()

比较好的方法,但是IE6/7/8中 Object.prototype.toString.apply(null)返回“[object Object]”。

  • constructor

14、对象

1、语法

对象声明可以使用字面量形式和构造函数形式

let obj = {}
let obj1 = new Object()
复制代码

这两种方法生成的对象是一样的,区别在于字面量形式可以添加多个键值对、构造函数形式只能逐个添加。

2、内置对象

JavaScript还有一些对象子类型,通常被称为内置对象。

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

3、内容

对象中的值通常不会存储在对象内部,通常情况下,存储在对象容器内部的是这些属性的名称,它们就像指针一样,指向这些值的真正存储位置

1、访问对象值的方法

let obj = {
    a:1
}
obj.a
obj['a']
复制代码

obj.a,的语法被称为属性访问,obj['a']的方法被称为键访问,它们在大都数情况下是可以互换的,区别在于.a要符合命名的规范性,['a']可以接受任意的UTF-8/Unicode字符作为属性名。比如"super-Fun!",这时候就不可以使用属性访问了。

注意:在对象中属性名永远都是字符串,如果不是者会被转换为字符串。

2、可计算的属性名

let a = "foo"
let obj = {
  [a + '1']: 'hello',
  [a + '2']: 'hello2'
}
复制代码

4、对象常见的方法

1、属性描述符

1、查看属性描述符:Object.getOwnPropertyDescriptor(obj, props)

语法:

let myObj = {
  a: 1
}
console.log(Object.getOwnPropertyDescriptor(myObj, 'a'))
复制代码
  • value,属性值
  • writable,是否可修改
  • configurable,是否可配置,如果为true则可以通过Object.defineProperty(obj, props)方法,来修改这些属性,所以需要注意的是把configurable修改为false是一个单向操作无法撤销。除了无法修改,configurable还会禁止删除该属性。
  • enumerable,是否可枚举

2、设置属性描述符:Object.defineProperty(obj, props)

语法:

Object.defineProperty(myObj, 'b', {
  value:2,
  writable: false,
  configurable: true,
  enumerable: true
})
复制代码

所以我们通过设置writable,configurable为false来设置一个对象常量。

2、不变性

1、通过设置writable,configurable为false来设置一个对象常量。

2、禁止拓展:Object.preventzectensions(obj)

语法:

let myObj1 = {
  a: 1
}
Object.preventExtensions(myObj1)
myObj1.b = 2
myObj1.b // undefined
复制代码

3、密封:Object.seal(obj)

实际上这个方法会调用Object.preventzectensions(obj)方法,并将现有属性的configurable设为false,所以密封之后既不能添加新的属性,也不能删除和配置现有属性

4、冻结:Object.freeze(obj)

这个方法会调用Object.seal()方法,并将现有属性的writable设为false,故既不能添加新的属性,也不能删除、配置、修改现有属性

3、get、set

get、set会劫持你对对象数据的操作。

let data = {}
Object.defineProperty(data, 'key', {
  // value: 1,
  enumerable: true,
  configurable: false, // 不能再定义
  get: function () {
    // Dep.target && dep.addDep(Dep.target)
    return this.value
  },
  set: function (newVal) {
    if (newVal === this.value) {
      return
    }
    console.log(`发生了变化${this.value}=>${newVal}`)
    this.value = newVal
    // dep.notify() // 通知所有订阅者
  }
})
复制代码

4、存在性

1、in:检查对象及原型链

2、hasOwnProperty()

5、其他常见方法

  • Object.keys(),返回所有可枚举属性
  • Object.values(),返回所有可枚举属性的值
  • Object.entries(),返回所有可枚举属性的键和值
  • Object.getOwnPropertyNames,返回所有属性,不管是否可枚举

15、数组

1、类数组

具有length属性,可以通过数字下标访问元素,如arguments、获取的DOM节点。Array.from(arguments)可以将一个类数组转化为数组

2、数组常见方法

  • push\pop:在数组尾部添加删除元素
  • unshift\shift:在数组头部添加删除元素
  • concat:合并数组
  • join:
  • slice:切片数组,返回一个新的数组
  • splice:删除、修改、增加数组元素
  • sort:排序,sort((a,b)=>{return a - b})

3、去重

// es6最简单的方式
[...new Set(arr)]

function unique(arr) {
    let list = [...arr]
    let res = []
    list.forEach(item => {
        if(!res.include(item)) {
            res.push(item)
        }
    })
    return res
}
复制代码