对象

213 阅读8分钟

前置知识

什么是对象?

对象是引用数据类型,可以看作一个集合,里面具备一些属性和方法。属性是静态的,方法是动态的,{}中包裹对象的成员也就是属性和方法,每个成员都以键值对方式存放,逗号分隔。使用某个对象上的属性或方法首先去该对象自身寻找,若没有则继续沿着它的原型链查找,直到找到位置,原型链的尽头是null。

JS中常见的对象

MathDateArrayString

怎么创建对象?

{}

new Object()

怎么遍历对象?

遍历对象的属性:for in

遍历对象元素:for of

对象属性怎么分类?

关键在于我们需要区分属性是否可以枚举,是否是原型上的属性,是否是symbol属性

  • 普通属性

  • 不可枚举属性Object.defineProperty

  • Symbol属性

  • 原型属性

  • 排序属性

  • 内属性--隐藏类

  • 静态属性

遍历属性的方法

这里介绍遍历属性的方法主要针对普通属性,枚举属性,symbol属性和原型属性。

包含原型

原型上所有属性

for in+proto存在时递归Reflect.ownKeys(obj)+剔除内置属性

普通属性和原型上的属性

for (let p in ins) {}

仅限自身

普通属性+可枚举属性

Object.keys() 返回对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。如果对象的键-值都不可枚举,将返回由键组成的数组。

普通属性+不可枚举属性

Object.getOwnPropertyNames(obj)该方法返回一个数组 访问除symbol以外所有的自身属性。

Symbol属性

Object.getOwnPropertySymbols(obj)

普通属性+不可枚举属性+Symbol属性

Reflect.ownKeys(obj)

所有不可枚举属性

思路:通过Reflect.ownKeys(obj)拿出所有key,筛选出不可枚举属性

key.filter()返回!Object.getOwnPropertyDescriptor(_obj,key).enumerable,判断是否可枚举,取反

key.filter()返回!Object.getOwnPropertyIsEnumerable.call(_obj,key)直接判断是否可枚举,取反(推荐)

对象的一些其他知识点

  • Object.entries把对象的键值以数组的形式遍历

  • 对象key类型本质是字符串,若是number转为string,若是object就隐式转换toPrimitive,优先调用toString。

  • delete不能删除原型上的属性

  • const val = (+{}+[])[+[]] console.log('NAN'[+[]]) {}是空对象,空对象转数字是NAN,+{}也是NAN NAN与任何运算都是NAN,[]是‘’,(+{}+[])是'NAN' []是‘’,+[]是字符串转number是0; 'NAN'[0]就是字符串的第0个是N

  • Object.create(proto)基本数据类型属于深拷贝,引用数据类型共享一个地址,相互影响。深拷贝浅拷贝

  • typeof String.prototype是{},Object.prototype.toString.call(obj).slice(8,-1)返回具体对象类型

  • let a = { n: 1 };a.x = a = { n: 2 };console.log(a.x)

js是从左到右计算表达式 . 的优先级高于 = 先执行a.x,a={n:1,x:undefined} 再执行a = { n: 2 },这时候a指向{n:2} 最后执行a.x ={n:2} a.x = a = { n: 2 } 最终a上就没有x属性,是undefined

for in for of区别

for in适合遍历对象,会遍历继承的或者原型链上的key,取到的是key

for of适合遍历数组,es6新增,只会遍历当前对象,取到的是value

若for of想遍历对象,判断该对象是否是类数组,是就转为数组,不是就添加obj[Symbol.iterator]属性再指向一个迭代器。

判断是否为数组

Array.isArray(obj)

obj instanceof Array

Object.prototype.toString.call(obj).slice(8,-1)==='Array'

Array.prototype.ifPrototypeOf(obj)

类数组转数组

[...arguments]

Array.form(obj)

Object.prototype.slice.call(obj)

Object.prototype.splice.call(obj,0)

Object.prototype.concat.call([],obj)

数组原生方法

改变原数组

  • pop()尾部删除,无参数,返回被删除的元素

  • shift()头部删除,无参数,返回被删除的元素

  • unshift()头部添加,参数是被添加的元素,返回新数组的长度

  • push()尾部添加,参数是被添加的元素,返回新数组长度

  • splice()删除或者在删除的位置添加元素,(index,count,item1,item2...),

参数个数为1在index处分割数组,返回分割线index后面的数组

参数个数为2,从index开始删除count个元素,返回被删除的元素

参数个数大于2在index处删除count个元素并添加元素,返回被删除的元素

  • reverse()翻转数组,无参数,返回翻转后的数组

  • sort()排序,可以传入函数,自定义排序规则

不改变原数组

  • slice()截取数组,返回截取后的数组

参数为空,深拷贝原数组

参数个数为1,从传入的index截取至最后一个

参数个数为2(start end)包括start不包括end

  • toString()、toLocaleString()、join("")转字符串,toLocaleString()转日期字符串

  • indexOf()查找元素第一次出现的位置,找到返回元素的index,没找到返回-1

  • lastIndexOf()查找元素最后一次出现的位置,找到返回元素的index,没找到返回-1

  • includes()返回布尔值,是否包含某个元素

  • reduce()传函数,累加器求和,从左到右

  • reduceRight()从右到左

  • map()返回数组,通常用来添加属性,改变属性。可以链式调用。

基本类型不改变,引用类型会改变原数组

forEach()无返回值,通常将item用作其他地方。

原数组中是基本数据类型,用forEach会拷贝原数组的指针和值,完全独立不影响原数组。若想修改需要加入第二个参数index,arr[index修改]

原数组中是引用数据类型obj,用forEach会拷贝原数组的指针也就是在栈中的地址,直接修改整个对象不影响原数组,但是修改对象的某个属性会影响原数组

  • find()返回第一个符合条件的值
  • findIndextOf()返回第一个符合条件的值的索引
  • some()一true就true
  • every()都true才true
  • filter()传函数,返回数组包含符合条件的元素.可以链式调用

遍历数组方式

for(let i=0;i<arr.length;i++){ arr[i] }

for(let i of arr){ i是下标对应的值 }

arr.forEach((item)=>{})

newArr = arr.map((item)=>{})

作用域、作用域链、闭包

全局作用域:最外层函数及最外层函数外定义的变量 未定义直接赋值的变量,一般不推荐,造成变量污染

函数作用域:函数内部定义的变量,外部无法访问,除了闭包

块级作用域:let const定义的变量,只在代码块中使用,不能重复命名。let没有变量提升。一般用于for循环,每次循环 都是单独的块级作用域不会被覆盖

作用域链:对于函数中的自由变量,若在当前作用域找不到,会向上查找直到全局作用域也没有就是undefined,但是外层不能访问内层的。

闭包:有权访问另一个函数作用域中的变量的函数。

对象不会构成单独的作用域,箭头函数向上查找this不会找到上级对象

执行上下文

js执行分为创建阶段、执行阶段

创建阶段:词法分析、语法分析、作用域规则确定

执行阶段:创建执行上下文、执行函数确定this、垃圾回收

js引擎通过执行栈管理执行上下文,全局执行上下文、函数执行上下文

所以作用域沿着创建该函数的地方向上找、this执行的时候谁最后调用就指向谁

this

new>call apply bind>作为对象方法调用>函数调用

用new时候this指向window或者构造函数

new

创建一个空对象,将constructor.prototype指向空对象的

执行将构造函数的this指向新对象

判断返回值是引用类型就返回res,不是就返回新对象

function news(constructor, params) {
    let args = Array.from(arguments)
    let cons = args.shift()
    let obj = Object.create(cons.prototype)
    let res = cons.apply(obj, args)
    return typeof res ==='object'&&res!==null?res:obj
}

参数:call apply bind第一个参数都是this,call bind后面参数要列举出来,apply第二个参数是剩余参数的集合[],call apply会直接执行函数,bind会保留旧this,arguments,return函数进行参数拼接

call

判断调用call的是否为函数,判断传入的新this,若没有设为window

新this上添加symbol属性key,将旧this作为方法放入新this中

执行该方法携带参数拿到返回值

删除symbol属性

return 执行的结果

function call(context) {
    if (typeof this !== 'function') {
        console.error();
    }
    context = context || window
    let key = Symbol()
    context[key] = this
    let args = [...arguments].slice(1)
    let res = context[key](...args)
    delete context[key]
    return res
}

apply

和call一样,就是执行的时候,参数取...arguments[1]是个集合

bind

参数和call一样从第一个截取,保留新this 旧this和参数,return函数,判断新this instanceof Fn?this:旧this,拼接参数

function bind(context) {
    if (typeof this !== 'function') {
    }
    context = context || window
    let fn = this
    let args = [...arguments].slice(1)
    return function Fn() {
        fn.apply(this instanceof Fn?this:context,args.concat(...arguments))
    }
}

原型、原型链

每个构造函数都有一个原型prototype,这个原型是对象里面存放一些属性和方法。这些属性和方法都会被构造函数创建的实例共享,当这个实例对象在自身找不到对应的属性和方法时候会去它的原型找也就是构造函数的原型通过__proto__或者Object.getProtoTypeOf(obj),若其原型的原型没有,就会去prototype的原型上找,找到Object.prototype,Object.prototype的原型是null,寻找的过程也就是所说的原型链,instanceOf就是这个原理去查找对象的原型链上是否存在构造函数的prototype

在这里构造函数Fn比较特殊,Fn生了自己和Object。Fn.prototype.__proto__是Object.prototype最终指向null

Object.__proto是Fn.prototype