JavaScript ES6

141 阅读7分钟

const对象的属性可以被修改吗?

因为对象是引用类型的,a保存的仅是对象的地址(指针),这就意味着,const仅保证地址(指针)不发生改变,修改对象的属性不会改变对象的地址(指针),所以是被允许的。也就是说const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。

const a = {name:'wangjianguo'}
a.name = "123"
console.log(a)

var、let、const区别

共同点:都可以声明变量。

区别一

var具有变量提升的机制,let和const不具备变量提升的机制。

区别二

var可以多次声明同一个变量。let和const 多次声明同一个变量会报错。

区别三:var和let声明变量的,const声明常量

var和let声明的变量可以再次赋值,但是const不可以再次赋值了。

区别四:var声明的变量没有自身作用域,let和const声明的变量有自身的作用域。

console.log(str) // undefined
var str = "你好"
console.log(num) // 报错
let num = 10

考题1:let和const不具备变量提升的机制。

console.log(str) // undefined
var str = "你好"
console.log(num) // 报错
let num = 10

考题2:let和const不具备变量提升的机制。

function demo (){
    var n = 2;
    if (true) {
        var n = 1
    }
    console.log(n) // 1 (n 可以再次被声明)
}
function demo (){
    let n = 2;
    if (true) {
        let n = 1 // 这里的n 的作用域仅限 if 条件大括号内
    }
    console.log(n) // 2 (此时访问的是外部的 n)
}

考题3:可以修改const常量里的属性

const obj = {a:1}
obj.a = 11111
cosole.log(obj) // {a:11111}
const arr = ['a','b','c']
arr[0] = 'aaaaa'
console.log(arr) // ['aaaaa','b','c']

将下列对象进行合并

const a = {a:1,b:4}
cosnt b = {b:2:c;3}
// 方式一 Object.assign
let obj1 = Object.assign(a,b)
console.log(obj1)
// 方式二 ... 扩展运算符
let obj2 = {...a,...b}
console.log(obj2)
// 方式三 : 自己封装方法
function extend(target,source){
    for (var key in source){
        target[key] = source[key]
    }
    return target
}
console.log(extend(a,b))

Object.freeze 和const的区别?

  1. Object.freeze()返回的是一个不可变的对象。这就意味着我们不能添加、删除或更改对象的任何属性
  2. const和Object.freeze()并不同,const是防止变量重新分配,而Object.freeze()是使对象具有不可变性。

Object.freeze() 只能冻结当前对象,如何手写实现深冻结

Object.freeze仅能冻结对象的当前层级属性,换而言之,如果对象的某个属性本身也是一个对象,那么这个内部对象并不会被Object.freeze冻结

const person = {prop1:99,prop2:'wangjianguo'}
person.prop1 = 100
console.log(person.prop1) // 100

//冻结对象
Object.freeze(person)
person.prop2 = 'hahaha'
consloe.log(person.prop2) // wangjianguo

手写深度冻结

function deepFreeze(obj){
    //取回定义在obj上的属性名
    var propNames = Object.getOwnPropertyNames(obj);
    
    //在冻结自身之前冻结属性
    propNames.forEach(fuction (name) {
        var prop = obj[name]
        // 如果prop是个对象,则进行冻结
        if (typeof prop == 'object' && prop !== null) deepFreeze(obj)
    })
    //冻结自身
    return Object.freeze(obj)
}

箭头函数

什么是箭头函数

var f = (v) => {
    return v
}
//等同于 
var f = function (v) {
    return v
}

当参数只有一个时,可以省略参数的圆括号,函数体只有一条语句时,可以省略return, 返回值是对象时,需要用圆括号包括一下,因为JavaScript不知道你是返回的是对象还是一个代码块。

// 报错
let fun = id => {id:id,name:"temp"}
// 不报错
let fun = id => ({id:id,name:"temp"})

箭头函数的this

箭头函数不会创建自己的this,所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向它在定义时已经确定了,之后不会改变。

function foo(){
    console.log(this)
    setTimeout(() =>{
        console.log("id:",this.id) // this 为 {id:42}
    },100)
}
var id = 21
foo.call({id:42})

为什么箭头函数的this指向的上一层作用域的this?

//ES6
function foo(){
    console.log(this)
    setTimeout(() =>{
        console.log("id:",this.id) // this 为 {id:42}
    },100)
}

在ES5即以下是不支持箭头函数的,箭头函数的用babel 转换下的写法

//ES5 箭头函数运行在低版本会转换为这种写法
function foo(){
    console.log(this)
    var _this = this
    setTimeout(function (){
        console.log("id:",_this.id)
    },100)
}

箭头函数的this在定义时就被确定了。换而言之,在使用时无法改变箭头函数的this。

var id = "Global"
let fun1 = () => {
    console.log(this.id)
}
fun1() // "Global"
fun1.call({id:"Obj"}) // "Global"
fun1.apply({id:"Obj"}) // "Global"
fun1.bind({id:"Obj"})() // "Global"
function foo(){
    return () =>{
        return () =>{
            return () =>{
                console.log("id:",this.id)
            }
        }
    }
}
var f = foo.call({id:1})
var t1 = f.call({id:2})()()
var t2 = f().call({id:2})()
var t3 = f()()call({id:2})

箭头函数可以让this指不可变,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面

var handler = {
    id:"123456",
    init:function(){
        document.addEventListener("click",event => this.doSomething(event.type),false)
        // this指向外层,指向init,init属性属于handler,所以this指向handler。
    },
    doSomething:function (type) {
        console.log('Handling' + type + 'for' + this.id)
    }
}
function foo() {
    return () => {
        return () => {
            return () => {
                console.log("id:",this.id)
            }
        }
    }
}
var f = foo.call({id:1}) // 只有foo函数绑定有效。其他都没自己的this,还需要绑定吗,所以绑定无效。
var t1 = f.call({id:2})()() // 1
var t2 = f().call({id:3}) // 1
var t3 = f()().call({id:4}) //1 

箭头函数的优点

更简洁更直观

function insert(value){
    return {
        into : fuction (array) {
            return {
                after :function (afterValue){
                    array.splice(array.indexOf(afterValue) + 1,0,value)
                    return array
                }
            }
        }
    }
}
// 箭头函数的写法
let insert = (value) => ({
    into:(array) => ({
        after:(afterValue) => {
            array.splice(array.indexOf(afterValue) + 1,0,value)
            return array
        }
    })
})

insert(2).into([1,3]).after(1) // [1,2,3]

箭头函数和普通函数区别

  1. this指向的问题。箭头函数中this只在箭头函数定义时就决定的,而且不可修改的(call,apply,bind)箭头函数的this指向定义时候,外层第一个普通函数
  2. 箭头函数不能new(不能当做构造函数)
  3. 箭头函数没有prototype
  4. 箭头函数没有arguments
let obj = {
    a:function(){
        console.log(this)
    },
    b:() =>{
         console.log(this)
    }
}
obj.a() // {a:f,b:f}
obj.b() // Window

class与构造函数的相似性、区别

构造函数

function Person (name,age){
    this.name = name
    this.age = age
}
// 原型方法
Person.prototype.sayName = function () {
    console.log('我的名字是${this.name}')
}
// 静态方法
Person.staticFunction = function () {
    console.log('我是静态方法')
}

var wangjianguo = new Person('wangjianguo',99)
wangjianguo.sayName() // 我的名字是wangjianguo
Person.staticFunction() // 我是静态方法

class

class Person {
    constructor(name,age){
       this.name = name
       this.age = age
    }
    sayName() {
        console.log('我的名字是${this.name}')
    }
    static staticFunction(){
        console.log('我是静态方法')
    }
}
var wjg = new Person('wangjianguo',99)
wjg.sayName() // 我的名字是wangjianguo
Person.staticFunction() // 我是静态方法

console.log(wjg.name) // wangjianguo
console.log(wjg.age) // 99

区别

  1. ES6中,class上的原型方法不可枚举;ES5中,构造函数上的原型方法可以枚举
function PersonES5 (name,age){
    this.name = name
    this.age = age
}
// 原型方法
PersonES5.prototype.sayName = function () {
    console.log('我的名字是${this.name}')
}
// 静态方法
PersonES5.staticFunction = function () {
    console.log('我是静态方法')
}

var personES5 = new PersonES5('es5example',25)
for (let prop in personES5){
    console.log(prop) // 这里会打印‘name’,‘age’,以及‘sayname’
}
class Person {
    constructor(name,age){
       this.name = name
       this.age = age
    }
    sayName() {
        console.log('我的名字是${this.name}')
    }
    static staticFunction(){
        console.log('我是静态方法')
    }
}
var personES6 = new PersonES6('es6example',26)
for (let prop in personES6){
    console.log(prop) // 这里仅打印‘name’,‘age’
}

  1. class必须通过new调用,普通构造函数不使用new 关键字,会当成普通函数调用,但class 没有经过new调用会抛出错误 TypeError:Class constructorPerson cannot
  2. class上的原型方法不可作为构造函数

construct

什么是内部方法[[construct]]

[[Construct]]是JavaScript引擎的一个内部方法,主要用于创建和初始化对象。

我们不能直接访问它,而是JavaScript引擎在背后用来处理通过new关键字创建新对象的机制。

不是所有函数都可以作为构造函数,只有具有[[construct]]的函数才能作为构造函数。ES6中,class 定义的构造函数里的原型方法没有[[construct]]方法。不能用new关键字调用。

function isContructor(func) {
    try {
        Reflect.construct(Object,[],func)
    } catch (e) {
        return false
    }
}
function Test1(){}

const Test2 = () => {}

console.log(isContructor(Test1)) // true
console.log(isContructor(Test2)) // false

构造函数内部定义方法和外部定义的方法区别?

class Person {
    constructor(name){
        this.name = name
        this.say1 = () =>{
            console.log("我在里面",this.name)
        }
    }
    say2(){
        console.log("我在外面",this.name)
    }
}

const A = new Person("A")
const B = new Person("B")

A.say1() // 我在里面,A
A.say2() // 我在外面,A

console.log(A.__proto__.say1) // undefined
console.log(A.__proto__.say2) // f say2() {...}

console.log(A.say1 === B.say1) // false 实例方法是单独的
console.log(A.say2 === B.say2) // true 原型上的方法是共享的

Object.keys(A) // ["name","say1"]

第一点:

在构造函数(constructor)内部定义的方法(例如say1)实际上是在每个对象实例上创建了一个新的函数

在构造函数(constructor)外部定义的方法(例如say1)是在Person的原型对象上(Person.prototype)创建的

第二点:

在构造函数(constructor)内部定义的方法(例如say1)是各个实例对象独有的。

在构造函数(constructor)外部定义的方法(例如say2),所有Person实例共享的的。

第三点:

在构造函数(constructor)内部定义的方法可以被Object.keys()遍历

在构造函数(constructor)外部定义的方法不能被Object.keys()遍历