JavaScript进阶(二)

343 阅读5分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

写在前面,兜兜转转,来来回回,从开始学习JS到现在已经工作这些来年,我和JS每天的生活可以用如胶似漆来形容。但自认为对他认识的还不够深刻。故从头再来,从基础开始。Go!!!

new

  • 新⽣成了⼀个对象

  • 链接到原型

  • 绑定 this

  • 返回新对象

在调⽤ new 的过程中会发⽣以上四件事情,我们也可以试着来⾃⼰实现⼀个new

function create() {

// 创建⼀个空的对象

let obj = new Object()

// 获得构造函数

let Con = [].shift.call(arguments)

// 链接到原型

obj.__proto__ = Con.prototype

// 绑定 this,执⾏构造函数

let result = Con.apply(obj, arguments)

// 确保 new 出来的是个对象

return typeof result === 'object' ? result : obj

}

instanceof

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

我们也可以试着实现⼀下 instanceof

function instanceof(left, right) {

    // 获得类型的原型

    let prototype = right.prototype

    // 获得对象的原型

    left = left.__proto__

    // 判断对象的类型是否等于类型的原型

    while (true) {

        if (left === null)

        return false

        if (prototype === left)

        return true

        left = left.__proto__

    }

}

this


function foo() {

    console.log(this.a)

}

var a = 1

foo()

var obj = {

    a: 2,

    foo: foo

}

obj.foo()


// 以上两者情况 `this` 只依赖于调⽤函数前的对象,优先级是第⼆个情况⼤于第⼀个情况

// 以下情况是优先级最⾼的,`this` 只会绑定在 `c` 上,不会被任何⽅式修改 `this` 指向

var c = new foo()

c.a = 3

console.log(c.a)

// 还有种就是利⽤ call,apply,bind 改变 this,这个优先级仅次于 new

看看箭头函数中的 this

function a() {

    return () => {

        return () => {

            console.log(this)

        }

    }

}

console.log(a()()())

箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外⾯的第⼀个不是箭头函数的函数的 this 。在这个例⼦中,因为调⽤a 符合前⾯代码中的第⼀个情况,所以this 是 window 。并且 this ⼀旦绑定了上下⽂,就不会被任何代码改变

闭包

闭包的定义很简单:函数 A 返回了⼀个函数 B,并且函数 B 中使⽤了函数 A的变量,函数 B 就被称为闭包。

function A() {

    let a = 1

    function B() {

        console.log(a)

    }

    return B

}

你是否会疑惑,为什么函数 A 已经弹出调⽤栈了,为什么函数 B 还能引⽤到函数 A 中的变量。因为函数 A 中的变量这时候是存储在堆上的。现在的JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。

经典⾯试题,循环中使⽤闭包解决 var 定义函数的问题


for ( var i=1; i<=5; i++) {

    setTimeout( function timer() {

        console.log( i );

    }, i*1000 );

}

  • ⾸先因为 setTimeout 是个异步函数,所有会先把循环全部执⾏完毕,这时候i 就是6 了,所以会输出⼀堆 6 。

解决办法三种,第⼀种使⽤闭包

for (var i = 1; i <= 5; i++) {

    (function(j) {

        setTimeout(function timer() {

            console.log(j);

        }, j * 1000);

    })(i);

}

第⼆种就是使⽤ setTimeout 的第三个参数

for ( var i=1; i<=5; i++) {

    setTimeout( function timer(j) {

        console.log( j );

    }, i*1000, i);

}

第三种就是使⽤ let 定义 i 了

for ( let i=1; i<=5; i++) {

    setTimeout( function timer() {

        console.log( i );

    }, i*1000 );

}

因为对于 let 来说,他会创建⼀个块级作⽤域,相当于

{ // 形成块级作⽤域

    let i = 0

    {

        let ii = i
        setTimeout( function timer() {

            console.log( i );

        }, i*1000 );

    }

    i++

    {

        let ii = i

    }

    i++

    {

        let ii = i

    }

    ...

}

深浅拷⻉

let a = {

    age : 1

}

let b = a

a.age = 2

console.log(b.age) // 2
  • 从上述例⼦中我们可以发现,如果给⼀个变量赋值⼀个对象,那么两者的值会是同⼀个引⽤,其中⼀⽅改变,另⼀⽅也会相应改变。

  • 通常在开发中我们不希望出现这样的问题,我们可以使⽤浅拷⻉来解决这个问题

浅拷⻉

⾸先可以通过 Object.assign 来解决这个问题

let a = {

    age: 1

}

let b = Object.assign({}, a)

a.age = 2

console.log(b.age) // 1

当然我们也可以通过展开运算符 (…)来解决

let a = {

    age: 1

}

let b = {...a}

a.age = 2

console.log(b.age) // 1

通常浅拷⻉就能解决⼤部分问题了,但是当我们遇到如下情况就需要使⽤到深拷⻉了

let a = {

    age: 1,

    jobs: {

        first: 'FE'

    }

}

let b = {...a}

a.jobs.first = 'native'

console.log(b.jobs.first) // native

浅拷⻉只解决了第⼀层的问题,如果接下去的值中还有对象的话,那么就⼜回到刚开始的话题了,两者享有相同的引⽤。要解决这个问题,我们需要引⼊深拷贝

深拷⻉

这个问题通常可以通过 JSON.parse(JSON.stringify(object))来解决

let a = {

    age: 1,

    jobs: {

        first: 'FE'

    }

}

let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'

console.log(b.jobs.first) // FE

但是该⽅法也是有局限性的:

  • 会忽略 undefined

  • 不能序列化函数

  • 不能解决循环引⽤的对象

let obj = {

    a: 1,

    b: {

        c: 2,

        d: 3,

    },

}

obj.c = obj.b

obj.e = obj.a

obj.b.c = obj.c

obj.b.d = obj.b

obj.b.e = obj.b.c

let newObj = JSON.parse(JSON.stringify(obj))

console.log(newObj)

如果你有这么⼀个循环引⽤对象,你会发现你不能通过该⽅法深拷⻉

  • 在遇到函数或者 undefined 的时候,该对象也不能正常的序列化
let a = {

    age: undefined,

    jobs: function() {},

    name: 'poetries'

}

let b = JSON.parse(JSON.stringify(a))

console.log(b) // {name: "poetries"}

你会发现在上述情况中,该⽅法会忽略掉函数和`undefined。

  • 但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决⼤部分问题,并且该函数是内置函数中处理深拷⻉性能最快的。当然如果你的数据中含有以上三种情况下,可以使⽤lodash 的深拷⻉函数。

END~~~