「这是我参与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)