ES6新特性原理解析

153 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

typeof与instanceof

数据类型

  • 共8种数据类型:number,string,boolean,undefined,null,symbol,bigInt(es10),object。

typeof

  • typeof除了可以得到以上8种数据类型外,还能检测function类型,即使他属于object。

instanceof

  • instanceof可以得到Number,Boolean,String,Array等具体的类型,但是不能判断基础数据类型。

instanceof原理

// value instanceof type
function instanceof(value, type) {
    const prototype = type.prototype;
    let valueProto = value.__proto__;
    while(valueProto) {
        // 原理:判断目标对象与构造函数的原型对象是否一致
        if(prototype === valueProto) {
            return true;
        }
        valueProto = value.__proto__;
    }
    return false;
}

// 通过上述代码发现,instanceof是通过判断目标对象与构造函数的原型对象是否一致,来决定数据类型。
// 注意:如果修改某构造函数的原型对象,就会出现问题
function Fn() {};
Fn.prototype = new Array()
let f = new Fn();
console.log(f instanceof Array) // true

undefined与null

  • 都是基本数据类型
  • undefined代表未定义,null代表一个空对象(但不是对象)
  • typeof null为object是js在设计之初将0x000开头的定义为object,而null也是

call/apply/bind原理

相同点

  • 修改函数的this指向

不同点

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)

参数的传递

  • apply的第二个参数为数组形式

返回值不同

  • call和apply会修改函数this指向,并调用。
  • bind会返回一个修改了this指向后的函数。需要手动调用。

原理

    • 不传入第一个参数,那么默认为window
  • 改变了this指向,让新的对象可以执行该函数。那么思路可以变成:给新的对象添加一个函数,然后在执行完以后删除。

实现call

Function.prototype.myCall = function (target) {
  let target = target || window
  // 给 target 添加一个属性
  // getValue.call(a, 'pp', '24') => a.fn = getValue
  target.fn = this
  // 将 target 后面的参数取出来
  let args = [...arguments].slice(1)
  // getValue.call(a, 'pp', '24') => a.fn('pp', '24')
  let result = target.fn(...args)
  // 删除 fn
  delete target.fn
  return result
}

实现apply

Function.prototype.myApply = function(target = window, ...args) {
  // this-->func  target--> obj  args--> 传递过来的参数

  // 在target上加一个唯一值不影响target上的属性
  let key = Symbol('key')
  target[key] = this;
  let args = [...arguments].slice(1)
  let result = target[key](args); // 这里和call传参不一样
  delete target[key]; // 不删除会导致target属性越来越多
  return result;
}

实现bind

Function.prototype.myBind = function (target) {
  let _this = this
  let args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(target, args.concat(...arguments))
  }
}

new关键字的原理

内存角度分析

let p = new Person("chen");
  • 该行代码加载进执行栈
  • 在栈内存中创建变量p
  • 在堆内存中创建开辟一片空间,初始化该对象
  • 将该对象的地址赋值给p

代码层面分析

function Person(name) {
    this.name = name;
}
// 1,创建一个空对象
// 2,为该对象的原型赋值为Person
const obj = Object.create(Person.ptototype)
// 3,执行构造函数,并通过apply将this指向obj。
Person.apply(obj, "chen");

javascript的继承

注意

js中并不存在类的概念,class只是语法糖,本质还是函数。

class Person {};
Person instanceof Function; // true

组合继承

  • 缺点:
    • 子类的构造函数为Parent
function Parent(value) {
    this.val = value;
}
Parent.prototype.getValue = function() {
    console.log(this.val);
}
function Child(value) {
    // 1,调用父类构造函数添加父类属性
    Parent.call(this, value);
}
// 2,修改子类原型对象
Child.prototype = new Parent();

寄生组合继承

function Parent(value) {
    this.val = value;
}
Parent.prototype.getValue = function() {
    console.log(this.val);
}
function Child(value) {
    // 1,调用父类构造函数添加父类属性
    Parent.call(this, value);
}
// 2,修改子类原型对象
Child.prototype = Object.creat(Parent.prototype, {
    constructor: {
        value: Child,
        enumerable: false,
        writable: true,
        configurable: true
    }
})
或者:
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

es6继承

class Parent {
    constructor(value) {
        this.val = value;
    }
    getValue() {
        console.log(this.val);
    }
}
// 1,使用extends
class Child extends Parent {
    constructor(value) {
		// 2,使用super
        super(value)
        this.val = value;
    }
}
  • 如果子类和父类都有相同的属性,子类的属性会覆盖父类。并只能通过this访问该值,不能使用super访问。
  • 如果子类和父类都有相同的方法,通过this访问子类方法,通过super访问父类方法。

Iterator迭代器

介绍

  • 它是一种接口or规范,为各种不同的数据结构提供统一的访问机制。
  • 任何实现了Iterator接口的数据结构,都可以实现遍历操作,即可以使用for...of

为对象实现迭代器功能

  • 因为对象属性不存在先后的顺序,所以设计之初没有为对象实现该规范
let obj = {
    id: '123',
    name: '张三',
    age: 18,
    gender: '男',
    hobbie: '睡觉'
}
// 1,固定写法
obj[Symbol.iterator] = function () {
    let keyArr = Object.keys(obj)
    let index = 0
    return {
        // 2,固定写法
        next() {
            return index < keyArr.length ? {
                value: {
                    key: keyArr[index],
                    val: obj[keyArr[index++]]
                }
            } : {
                done: true
            }
        }
    }
}

for (let key of obj) {
  console.log(key)
}

proxy代理

因为vue3的双向绑定原理改为了proxy,所以对此进行了一个学习总结

使用

// 1,目标对象
var target = {
    name: 'poetries'
};
// 2,控制器
var logHandler = {
    get: function(target, key) {
        console.log(`${key} 被读取`);
        return target[key];
    },
    set: function(target, key, value) {
        console.log(`${key} 被设置为 ${value}`);
        target[key] = value;
    }
}
// 3,使用
var targetWithLog = new Proxy(target, logHandler);

targetWithLog.name; // 控制台输出:name 被读取
targetWithLog.name = 'others'; // 控制台输出:name 被设置为 others

console.log(target.name); // 控制台输出: others

handle对象

  • handle控制器对象本身就是es6设计的一个对象,用于自定义对象的代理操作。共有13种方法
// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.getPrototypeOf()

// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.setPrototypeOf()

// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.isExtensible()

// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.preventExtensions()

// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.getOwnPropertyDescriptor()
 
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
andler.defineProperty()
 
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()

// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()
 
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()

// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()

// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()

// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()
 
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()

数组方法总结

Array原型API

  • Array.of()
    • Array.of(1, 2) => [1, 2]
  • Array.from(obj, handle, thisObj)
    • obj:类数组对象,如String, Set, Map
    • handle:对每一项进行处理
    • handle函数的this指向
  • Array.isArray()

改变自身的方法

  • push()
  • pop()
  • shift() // 头部删除
  • unshift() // 头部添加
  • reverse()
  • sort(fn) // a-b :小到大
  • splice(start, deleteCount, ...items)
  • fill(value, startIndex, endIndex)

不改变自身的方法

  • concat(arr2)
  • join()
  • slice(startIndex, endIndex); 取指定元素生成新数组
  • toString(); 去掉括号后用逗号拼接的字符串indexOfincludes()

数组遍历的方法

  • forEach()
  • every() 数组中的每一项是否都符合某条件
  • some() 数组中是否有元素符合某条件
  • map() 处理每一项,并生成新数组
  • filter() 取到符合条件的元素,生成数组
  • reduce((pre, item, arr) => pre + item, 0)

持续更新中...