likes
comments
collection
share

对象认知全提升,成为 JS 高手

作者站长头像
站长
· 阅读数 24

1.对象属性

常规属性

  • 键为字符串的属性
  • 根据创建时的顺序排序
const obj = {};

obj.p1 = "p1";
obj.p6 = "p6";
obj.p2 = "p2";

for (const p in obj) {
  console.log("property:", p);
}

执行结果:

对象认知全提升,成为 JS 高手

排序属性

  • 属性键值为数字或者数字字符串的属性
  • 按照索引值大小升序排序
const obj = {};

obj[1] = "p1";
obj[6] = "p6";
obj[2] = "p2";
//obj["1"] = "p1";
//obj["6"] = "p6";
//obj["2"] = "p2";

for (const p in obj) {
  console.log("property:", p);
}

执行结果:

对象认知全提升,成为 JS 高手

同时存在先输出排序属性

const obj = {};

obj.p1 = "str1";
obj.p6 = "str6";
obj.p2 = "str2";

obj[1] = "num1";
obj[6] = "num6";
obj[2] = "num2";

for (let p in obj) {
  console.log("property:", obj[p]);
}

执行结果:

对象认知全提升,成为 JS 高手

为什么要设计常规属性和排序属性

  • 使用两种线性结构保存(elements、properties),提升V8引擎属性的访问速度

2.属性来源

  • 静态属性,例如: Object.assign
  • 原型属性,例如: Object.prototype.toString
  • 实例属性,例如: function Person (name){ this.name = name }
// 1.函数作为构造实例
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function () {
        return name;
    };
}

Person.prototype.getAge = function () {
    return this.age;
};

const person = new Person();

// 2.class 实例对象
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  getName = () => {
    return this.name;
  };

  getAge() {
    return this.age;
  }
}

const hasOwn = Object.hasOwnProperty;
const print = console.log;

const person = new Person();
print("getName:", hasOwn.call(person, "getName")); // 实例属性
print("getAge:", hasOwn.call(person, "getAge")); // 原型属性

// 3.Object.defineProperty
const obj = {};
Object.defineProperty(obj, "name", {
  value: "云牧",
});

console.log("name:", obj.name);  // 云牧

3.属性描述符

  • Object.definePropertyObject.defineProperties 设置属性信息
  • Object.getOwnPropertyDescriptorObject.getOwnPropertyDescriptors 获取属性描述信息
    • configurable:可配置(属性能不能被删除和重新通过 defineProperty 设置,但是当设置 writablevaluetruefalse 则是允许的)
    • enumerable:是否可枚举
    • value:值
    • writable:是否可被更改
    • set:访问器函数
    • get:访问器函数
  • 数据属性: value + writable + configurable + enumerable
  • 访问器属性:get + set + configurable + enumerable

默认 defineProperty 不传第三个描述符

const obj = {};

Object.defineProperty(obj, "name", {});

console.log(obj.name); // undefined 且不能被改写

console.log(Object.getOwnPropertyDescriptor(obj, "name"));

执行结果如下:

对象认知全提升,成为 JS 高手

Object.defineProperty的缺点

  • 无法监听数组变化
  • 只能劫持对象的属性,因此我们需要对对象的每个属性进行遍历。如果属性也是对象,还得进行递归

4.对象限制

1.对象可扩展-Object.preventExtensions

  • Object.preventExtensions:对象变的不可扩展,也就是永远不能再添加新的属性
  • Object.isExtensible:判断一个对象是否是可扩展
const obj = { name: "张三" };

Object.preventExtensions(obj);

// 不可以添加新属性
obj.age = 2;

console.log("obj:", obj); // obj: { name: '张三' }
console.log(Object.isExtensible(obj)); // false

2.对象的封闭-Object.seal

  • Object.seal:阻止添加新属性+属性标记为不可配置
  • Object.isSealed:检查一个对象是否被密封
//2. Object.seal
const object1 = {
  prop1: 11,
};

Object.seal(object1);

// 不可以 添加属性
object1.prop2 = 22;
console.log(object1.prop2); // undefined

// 不可以 删除属性
delete object1.prop1;
console.log(object1.prop1); // 11

3.对象的冻结- Object.freeze

  • Object.freeze:不加新属性+不可配置+不能修改值
  • Object.isFrozen:检查一个对象是否被冻结
const obj = {
  prop1: 11,
};

Object.freeze(obj);

// 添加
obj.prop2 = "prop2";
// 修改值
obj.prop1 = 33;
// 删除
delete obj.prop1;

Object.defineProperty(obj, "prop1", {
  value: 10,
});

console.log(obj.prop1);
console.log(obj.prop2);
console.log(Object.isFrozen(obj));

4.总结

方法新增属性修改描述符删除属性更改属性值
Object.preventExtensionsx
Object.sealxx(修改 writable 为 false 可以)x
Object.freezexx(修改 writable 为 false 可以)xx

5.访问对象原型

1.prototype

  • prototype是一个对象
  • 原型会形成原型链,原型链上查找属性比较耗时,访问不存在的属性会访问整个原型链

2._proto_

  • 构造函数的原型
  • null以外的对象均有 _proto_ 属性
  • Functionclass的实例有 prototype 以及 _proto_ 属性
  • 普通函数,祖上第三代上必为null
// 普通函数
function a() {}
console.log(a.__proto__.__proto__.__proto__); // null

// 作为构造函数
function Person() {}
const person = new Person();
console.log(person.__proto__.__proto__.__proto__); // null

// 普通对象 两代
const obj = {};
console.log(obj.__proto__.__proto__); // null

3.instanceof

  • 检测构造函数(右侧)的prototype属性是否出现在某个实例对象(左侧)的原型链上
  • Object instanceof FunctionFunction instanceof Object

手写instanceof

function instanceOf(instance, cclass) {
    let proto = instance.__proto__;
    let prototype = cclass.prototype;

    while (proto) {
        if (proto === prototype) return true;
        proto = proto.__proto__;
    }
    return false;
}

class Parent {}
class Child extends Parent {}
class CChild extends Child {}
class Luren {}
const cchild = new CChild();

console.log(instanceOf(cchild, Parent)); // true
console.log(instanceOf(cchild, Child)); // true
console.log(instanceOf(cchild, CChild)); // true
console.log(instanceOf(cchild, Object)); // true
console.log(instanceOf(cchild, Date)); // false
console.log(instanceOf(cchild, Luren)); // false

4.getPrototypeOf

  • Object.getPrototypeof()Reflect.getPrototypeOf()
    • 返回对象的原型
  • 内部先toObject转换,注意nullundefined没有原型

5.setPrototypeOf

  • Object.setPrototypeof()Reflect.setPrototypeOf()
    • 指定对象的原型
  • 原型的尽头是null
const obj = { a: 1 };
console.log(obj.toString());

Object.setPrototypeOf(obj, null); // 设置原型为null
console.log(obj.toString()); // obj.toString is not a function

6.isPrototypeOf

  • Object.isPrototypeof 、 Object.prototype.isPrototypeof 、 Reflect.isPrototypeOf 、 Function.isPrototypeOf
    • 一个对象是否存在于另一个对象的原型链上
const print = console.log;

print(Object.isPrototypeOf({})); // false
print(Object.prototype.isPrototypeOf({})); // true  期望左操作数是一个原型
print(Reflect.isPrototypeOf({})); // false
print(Function.isPrototypeOf({})); // false

7.Object.create

  • 使用现有的对象来提供新创建的对象的 __proto__
  • 使用 Object.create(null) 可以创建一个没有原型的纯净对象

6.对象属性的遍历

属性分类:

  • 普通属性
  • 原型属性
  • Symbol属性
  • 不可枚举的属性

遍历方法:

方法名普通属性不可枚举属性Symbol属性原型属性
for inxx
Obiect.keysxxx
Object.getOwnPropertyNamesxx
Object.getOwnPropertySymbolsxx
Reflect.ownKeysx

1.获取非原型属性

  • Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols
const symbolSay = Symbol.for("say1");

class Person {
  static flag = "人";
  static getFlag() {
    return Person.flag;
  }

  static [Symbol.for("symbolPro")]() {
    return "symbolPro";
  }

  constructor(name) {
    this.name = name;
    this[symbolSay] = "haha";
  }
  getName() {
    return this.name;
  }
  getAge = () => {
    return 15;
  };
}

function getOwnPropertyStatics(_obj) {
  const KNOWN_STATICS = {
    name: true,
    length: true,
    prototype: true,
    caller: true,
    callee: true,
    arguments: true,
    arity: true,
  };

  let result = [];

  let keys = Object.getOwnPropertyNames(_obj);
  keys = keys.concat(Object.getOwnPropertySymbols(_obj));
  // const keys = Reflect.ownKeys(_obj)
  for (let i = 0; i < keys.length; ++i) {
    const key = keys[i];
    if (!KNOWN_STATICS[key]) {
      result.push(key);
    }
  }
  return result;
}

const staticProps = getOwnPropertyStatics(Person);
console.log("非原型属性:", staticProps); // 非原型属性: [ 'getFlag', 'flag', Symbol(symbolPro) ]

2.获取原型上所有属性

  • Reflect.ownKeys + 递归原型链
class Grand {
  gName = "Grand";
  gGetName() {
    return this.gName;
  }
}
Grand.prototype[Symbol.for("gAge")] = "G-12";

class Parent extends Grand {
  pName = "123";
  pGetName() {
    return this.pName;
  }
}
Parent.prototype[Symbol.for("pAge")] = "G-11";

class Child extends Parent {
  cName = "123";
  cGetName() {
    return this.cName;
  }
}

const child = new Child();

let result = [];
function logAllProperties(instance) {
  if (instance == null) return;
  let proto = instance.__proto__;
  while (proto) {
    result.push(...Reflect.ownKeys(proto));
    proto = proto.__proto__;
  }
}
logAllProperties(child);
console.log("result:", result);

执行结果如下:

对象认知全提升,成为 JS 高手

3.获取所有不可枚举的属性

const symbolSalary = Symbol.for("ins_symbol_attr_salary");

function Person(age, name) {
  this.ins_in_attr_age = age;
  this.ins_in_attr_name = name;
}

const person = new Person(100, "程序员");

//Symbol 属性
person[symbolSalary] = 6000;
person["ins_no_enumerable_attr_sex"] = "男";

// sex 不可枚举
Object.defineProperty(person, "ins_no_enumerable_attr_sex", {
  enumerable: false,
});

Object.defineProperty(person, symbolSalary, {
  enumerable: false,
  value: 999,
});

//
function getNoEnumerable(_obj) {
  //获取原型对象
  const keys = Reflect.ownKeys(_obj);
  // const result = keys.filter(key=> {
  //     return !Object.getOwnPropertyDescriptor(_obj, key).enumerable
  // })
  // return result;
  const result = keys.filter((key) => {
    return !Object.prototype.propertyIsEnumerable.call(_obj, key);
  });
  return result;
}

console.log(getNoEnumerable(person));

执行结果如下:

对象认知全提升,成为 JS 高手

7.对象隐式转换和注意事项

1.显示转换

  • 通过 JS 转换方法进行转换、
  • 比如 String 、 Number 、 parselnt/parseFloat 等

2.隐式转换

  • 编译器自动完成类型转换的方式就称为隐式转换,往往预期和传入不一致往往就会发生
    • 二元 + 运算符(类型不一样的相加)
    • 关系运算符 ><>=<===
    • 逻辑!if/while三目条件
    • 属性键的遍历for in
    • 模板字符串

3.对象隐式转换规则

涉及到三个方法

  • Symbol.toPrimitive

  • Object.prototype.valueOf

  • Object.prototype.toString

  • 如果[Symbol.toPrimitive](hint)方法存在,优先调用,无视valueOftoSting方法

  • 否则,如果期望是"string" ——先调用obj.toString()如果返回不是原始值,继续调用obj.valueOf()

  • 否则,如果期望是"number"或"default" 先调用 obj.valueOf() 如果返回不是原始值,继续调用obj.toString()

如果未定义[Symbol.toPrimitive](hint),期望string,此时toString()valueOf()都没有返回原始值会抛出异常

const obj = {
  value: 10,
  valueOf() {
    return this;
  },
  toString() {
    return this;
  },
};

console.log(10 + obj); // 报错

4.Symbol.toPrimitive(hint)

  • hint - "string"
  • hint - "number"
  • hint - "default"

hint - "string"

  • window.alert(obj)
  • 模板字符串`${obj}
  • test[obj]=123
const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "hello";
    }
    return true;
  },
};
// alert, 浏览器
// window.alert(obj);
// ${}
console.log(`${obj}`);
// 属性键
obj[obj] = 123;
console.log(Object.keys(obj));

执行结果:

对象认知全提升,成为 JS 高手

hint - "number"

  • 一元+,位移
  • -*/ 等关系运算符
  • Math.powStringprototype.slice 等很多内部方法
const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "hello";
    }
    return true;
  },
};

// 一元+
console.log("一元+:", +obj);

// 位移运算符
console.log("位移运算符", obj >> 0);

// 除减算法, 没有 + 法,之前已经单独说过转换规则
console.log("减法:", 5 - obj);
console.log("乘法:", 5 * obj);
console.log("除法:", 5 / obj);

// 逻辑 大于,小于,大于等于, 没有等于, 有自己的一套规则
console.log("大于:", 5 > obj);
console.log("大于等于:", 5 >= obj);

// 其他期望是整数的方法
console.log("Math.pow:", Math.pow(2, obj));

执行结果如下:

对象认知全提升,成为 JS 高手

hint - "default"

  • 二元+
  • == 、!=
const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "hello";
    }
    return true;
  },
};

console.log("相加:", 5 + obj); // 相加: 6
console.log("等等与:", 5 == obj); // 等等与: false
console.log("不等于:", 5 != obj); // 不等于: true

5.ValueOf 和 toString

来几个小练习大家自己想想

const user = {
  name: "John",
  age: 10,
  toString() {
    return this.name;
  },
  valueOf() {
    return this.age;
  },
};

console.log("user:", +user); // user: 10
console.log("user:", `${user}`); // user: John
const user = {
  name: "John",
  age: 10,
  toString() {
    return this.name;
  },
  valueOf() {
    return this;
  },
};

console.log("user:", +user); // NaN
// 相当于
console.log(+"John"); // NaN
const user = {
  name: "John",
  age: 10,
  toString() {
    return this;
  },
  valueOf() {
    return this.age;
  },
};

Object.prototype.toString = undefined;

console.log("user:", `${user}`); // user: 10
const obj = {
  value: 10,
  toString: function () {
    return this.value + 10;
  },
  valueOf: function () {
    return this.value;
  },
};

obj[obj] = obj.value;

console.log("keys:", Object.keys(obj)); // keys: [ '20', 'value', 'toString', 'valueOf' ]
console.log("${obj}:", `${obj}`); // ${obj}: 20
console.log("obj + 1:", obj + 1); // obj + 1: 11
console.log('obj + "":', obj + ""); // obj + "": 10

特殊Date

  • hint是default ,是优先调用toString,然后调用valueOf
const date = new Date();

console.log("date toString:", date.toString());
console.log("date valueOf:", date.valueOf());

console.log(`date str:`, `${date}`);
console.log(`date number:`, +date);

console.log(`date +:`, date + 1);

执行结果如下:

对象认知全提升,成为 JS 高手

8.JSON和toJSON

  • 严格意义上JSON对象是不合理,JSON是文本协议
  • 全局作用域下JSON,名为JSON,是Object对象
  • JSON是一种轻量级的、基于文本的、与语言无关的语法,用于定义数据交换格式
  • 它来源于ECMAScript编程语言,但是独立于编程语言

JSON特征

  • JSON就是一串字符串,使用特定的符号标注

  • {}双括号表示对象

  • []中括号表示数组

  • ""双引号内是属性键或值

属性键

  • 只能是字符串
  • 必须双引号包裹

JSON值

  • object
  • array
  • number(只能十进制)
  • string
  • true
  • false
  • null

合格JSON

`["你", "我", "她"]`

`{ "name": "云牧", "age": 18 }`

`{ "IDS": ["123", "456"] }`

`{ "name": null }`

`{}`   

`[]`

不合格JSON

`
{
    "name":"云牧",
    [Symbol.for("sex")]: 1 
}`

`
{ 
  name: "云牧", 
  'age': 32  
} `

`
{
    "name": "云牧",
    "age": undefined 
}`

`[-10, 0xDDFF]` 
   
`
{ 
  "name": "云牧",
  "created": new Date(),  
  "price": 18
  "getPrice": function() { 
      return this.price;
  }
}`

`
{
   "name":"云牧",
   "age": 32, 
}
`

JSON.stringify()

  • 语法:JSON.stringify(value[, replacer [, space]])
  • 第二个参数replacer:过滤属性或者处理值
    • 如果该参数是一个数组:则只有包含在这个数组中的属性名才会被序列化到最终的JSON字符串中
    • 如果该参数是一个函数︰则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理
    • 如果该参数为null或者未提供:,则对象所有的属性都会被序列化
  • 第三个参数space:美化输出格式
const person1 = {
  name: "云牧",
  age: 18,
  birth: "2002-01-01",
};

//replacer 数组
console.log(JSON.stringify(person1, ["name", "age"])); // {"name":"云牧","age":18}

const person2 = {
  name: "云牧",
  age: 18,
  birth: "2002-01-01",
};

//replacer 方法
const jsonString = JSON.stringify(person2, function (key, value) {
  if (typeof value === "string") {
    return undefined;
  }
  return value;
});

console.log(jsonString); // {"age":18}

// space 美化格式
const person3 = {
  name: "云牧",
  age: 18,
  birth: "2002-01-01",
};
const a = JSON.stringify(person3);
console.log(a); // {"name":"云牧","age":18,"birth":"2002-01-01"}

const person4 = {
  name: "云牧",
  age: 18,
  birth: "2002-01-01",
};
const c = JSON.stringify(person4, null, "\t");
console.log(c);
// {
// 	"name": "云牧",
// 	"age": 18,
// 	"birth": "2002-01-01"
// }

序列化undefined、任意的函数、symbol

  • 作为对象属性值,自动忽略
  • 作为数组,序列化返回null
  • 单独序列化时,返回undefined

其他规则

  • Date返回 ISO 字符串
  • 循环引用报错
  • NaNInfinitynull都会作为null
  • Biglnt报错
  • MapSetWeakMap等对象,仅序列化可枚举属性
// 自动忽略
const data1 = {
  a: "test1",
  b: undefined,
  c: Symbol("test2"),
  fn: function () {
    return true;
  },
};
console.log(JSON.stringify(data1)); // {"a":"test1"}

//数组返回null
const data2 = [
  "test1",
  undefined,
  function aa() {
    return true;
  },
  Symbol("test2"),
];
console.log(JSON.stringify(data2)); // ["test1",null,null,null]

//返回undefined
const a1 = JSON.stringify(function a() {
  console.log("test1");
});
console.log("a1:", a1); // a1: undefined
const a2 = JSON.stringify(undefined);
console.log("a2:", a2); // a2: undefined
const a3 = JSON.stringify(Symbol("test2"));
console.log("a3:", a3); // a3: undefined

// Date
console.log(JSON.stringify({ now: new Date() })); // {"now":"2022-09-12T00:17:54.812Z"}

// NaN 和 Infinity 以及null
console.log(JSON.stringify(NaN)); // null
console.log(JSON.stringify(Infinity)); // null
console.log(JSON.stringify(null)); // null

//转换为对应的原始值。
console.log(JSON.stringify([new Number(2), new String("test"), new Boolean(false)])); // [2,"test",false]

//仅序列化可枚举属性
const a = JSON.stringify(
  Object.create(null, {
    test1: { value: "test1", enumerable: false },
    test2: { value: "test2", enumerable: true },
  })
);
console.log(a); // {"test2":"test2"}

// BigInt 报错
// const c = {
//   test: 1n,
// };
// console.log(JSON.stringify(c));

JSON.parse()

  • 注意:第二个参数函数reviver ( k, v )
    • k代表属性键,v代表属性值,如果返回undefined则会从当前的属性删除
const jsonStr = `
	{ 
  	"name": "帅哥", 
  	"age":  18, 
    "isFans": true,
    "IDCard": "xxxxxxxxxxxxxxxxxx"
   }
`;
// 保密身份证
const obj = JSON.parse(jsonStr, function (key, value) {
  if (key == "IDCard") {
    return undefined;
  } else {
    return value;
  }
});

console.log(obj); // { name: '帅哥', age: 18, isFans: true }

注意:遍历顺序

const jsonStr = `{
    "name": "牙膏",
    "count": 10, 
    "orderDetail": {
        "createTime": 1632996519781,
        "orderId": 8632996519781,
        "more": {
            "desc": "描述"
        }
    }
}`;

JSON.parse(jsonStr, function (k, v) {
  console.log("key:", k);
  return v;
});

执行结果如下:

对象认知全提升,成为 JS 高手

注意:this

const jsonStr = `{
    "name": "云牧",
    "orderDetail": {
        "createTime": 1632996519781
    }
}`;

JSON.parse(jsonStr, function (k, v) {
  console.log("key:", k, ",this:", this);
  return v;
});

执行结果如下:

对象认知全提升,成为 JS 高手

toJSON

  • 对象拥有toJSON方法,toJSON会覆盖对象默认的序列化行为
const product = {
  name: "牙膏",
  orderDetail: {
    createTime: 1632996519781,
  },
  toJSON() {
    return {
      name: "云牧",
    };
  },
};

console.log(JSON.stringify(product)); // '{"name":"云牧"}'

使用场景

  • 请求接口发送数据,接收数据
  • 本地存储
  • 深克隆对象

9.学习自检

题目一

const obj = {},
  objA = { propertyA: "A" },
  objB = { propertyB: "B" };

obj[objA] = "objectA";
obj[objB] = "ObjectB";

for (let [p, v] of Object.entries(obj)) {
  console.log("p:", p, ", v:", v);
}

执行结果:

对象认知全提升,成为 JS 高手

  • Object.entires :迭代器,能获取键值对
  • 对象键的特性∶本质上是字符串,如果是数字,转换字符串
  • 隐式转换︰对象的隐式转换,Symbol.toPrimitive,valueOf,toString()
const obj = {},
  objA = {
    propertyA: "A",
    toString() {
      return "objA";
    },
  },
  objB = {
    propertyB: "B",
    valueOf() {
      return "objB";
    },
  };

obj[objA] = "objectA";
obj[objB] = "ObjectB";

for (let [p, v] of Object.entries(obj)) {
  console.log("p:", p, ", v:", v);
   // 优先调用toString
   // p: objA , v: objectA
   // p: [object Object] , v: ObjectB
}

题目二

const person = {
  name: "二哈",
};
const person2 = Object.create(person);
delete person2.name;

console.log(person2.name);

执行结果:

对象认知全提升,成为 JS 高手

题目三

const val = (+{} + [])[+[]];
console.log(val);

/*

(+{} + [])[+[]]
// +{}  => NaN
(NaN + [])[+[]]
// [] 隐式转换 ''
(NaN + '')[+[]]
// NaN + '' => 'NaN'
('NaN')[+[]]
// +[] => 0
('NaN')[0]
// 'N'

*/

题目四

const proto = {
  name: "原型",
  arr: [1, 2],
};
const person = Object.create(proto);
person.name = "实例";
person.arr.push(3);

console.log(person.name);
console.log(proto.name);

console.log(person.arr);
console.log(proto.arr);

执行结果:

对象认知全提升,成为 JS 高手

题目五

const toString = Object.prototype.toString;
function getObjectType(obj) {
  return toString.call(obj).slice(8, -1);
}
const obj = String.prototype;
console.log(typeof obj);
console.log(getObjectType(obj));

执行结果:

对象认知全提升,成为 JS 高手

题目六

let a = { n: 1 };
a.x = a = { n: 2 };

// 求a.x
console.log(a.x);

执行结果:

对象认知全提升,成为 JS 高手

题目七

const proto = {
  name: "p_parent",
  type: "p_object",
  [Symbol.for("p_address")]: "地球",
};

const ins = Object.create(proto);
Object.defineProperty(ins, "age", {
  value: 18,
});
ins.sex = 1;
ins[Symbol.for("say")] = function () {
  console.log("say");
};

const inKeys = [];
for (let p in ins) {
  inKeys.push(p);
}

console.log(inKeys);
console.log(Reflect.ownKeys(ins));

执行结果:

对象认知全提升,成为 JS 高手