1.如何创建一个对象,并将他的属性age设置为不可删除,age的默认值为18?
let a = {name: "haha"};
Object.defineProperty(a, "age", {
configurable: false,
value: 18,
})
console.log(a) // {name: "haha", age: 18}
delete a.name
delete a.age
console.log(a) //{age: 18}
一般属性分为数据属性和访问器属性,在数据属性上有4种属性:
-
[[Configurable]] 是否能通过delete删除重新定义
- 一个属性被定义为不可配置以后,不能再变回可配置了。
-
[[Enumerable]] 是否能通过for - in 循环返回
-
[[Writeable]] 表示属性是否能被修改
-
[[Value]] 包含属性的值
2.如何为该对象添加上相关的方法,使得当你修改year属性的时候,当该值大于2017时,修改_year,并且修改edition = edition - 2017。
let book = {
year_: 2018,
edition: 1
}
// TODO
book.year = 2019
console.log('book', book); // book { year_: 2019, edition: 2 }
Object.defineProperty(book, "year" ,{
get() {
return this.year_;
},
set(newValue) {
if(newValue > 2017) {
this.year_ = newValue;
this.edition = newValue - 2017
}
}
})
访问器不包含数据值,相反包含一个获取getter,和设置setter的函数.
- [[Configurable]] 是否能通过delete删除重新定义
- [[Enumerable]] 是否能通过for - in 循环返回
- [[Get]] 获取函数,读属性时调用
- [[Value]] 设置函数,写入函数时调用
访问器必须使用Object.defineProperty(),不能直接定义
3. 如何读取一个对象的属性描述符?
let a = {};
Object.defineProperty(a, "age", {
configurable: false,
value: 18,
})
// TODO 获取age的属性描述符
let description = Object.getOwnPropertyDescriptor(a, "age");
console.log("description", description)
Object.getOwnPropertyDescriptor()可以获取指定属性的属性描述符. ES7中新增了Object.getOwnPropertyDescriptors()的静态方法,获取一个对象的所有自身属性的描述符。
4. 关于合并对象,写出以下代码的结果。
let src , dest, result ;
dest = {id: 'dest'}
src = Object.assign(dest, {id: 'src1,' ,a: 'foo'}, {id: 'src2', b: 'bar', c: {}})
console.log('src', src);
console.log('flag', src.c === dest.c)
src {id: "src2", a: "foo", b: "bar", c: {}}
flag true
- Object.assign,目标对象和多个源对象的参数,将每一个源对象可枚举(Object.propertyIsEnumerable() 为true)的 属性和自有属性(Object.hasOwnProperty 为 true)复制到目标对象上。
- 如果有多个源对象都有相同属性,只复制最后一个。
- 浅复制,只会复制对象的引用
- 复制期间出错,只会完成部分的复制。
5. 如何正确的判断一个对象是否相同,如何判断+0,-0,NaN。
function compare(a, b) {
}
compare(NaN,NaN) // true
compare(+0, -0) // false
compare(+0, 0) // true
compare({}, {}) // false
compare(true, 1) // false
function compare(a, b) {
return Object.is(a, b)
}
6. 如何简化以下代码
let name = "haha";
let person = {
name: name,
sayName: function(name) {
console.log('my name is ' + name);
}
}
let name = "haha";
let person = {
name,
sayName(name) {
console.log('my name is ' + name);
}
}
增强对象的语法
- 属性简写
- 可计算属性
const nameKey = 'age';
let person = {
[nameKey]: 18
}
- 简写方法名
7. 写出以下代码的结果
let person = {
name: "haha",
age: 18
};
const {age,name,job,other="otherthings"} = person;
console.log("age", age);
console.log("name", name);
console.log("job", job);
console.log("other", other);
let {length} = 'fooo';
console.log('length', length);
let {constructor: c} = 4;
console.log(c === Number)
let { s } = null;
let { s2 } = undefined;
age 18
name haha
job undefined
other otherthings
length 4
true
VM274:18 Uncaught TypeError: Cannot destructure property 's' of 'null' as it is null.
at <anonymous>:18:7
ES6新增了对象解构语法,可以在一条语句中使用其那套数据实现一个或多个赋值操作。
- 赋值不一定与对象属性匹配,如果引用的属性不存在就为undefined
- 也可以在解构赋值的同时定义默认值
解构的内部使用了函数ToObject()(不能在函数运行时环境访问)把源数据解构转化为对象。意味着在对象解构的上下文中,原始值会被当成对象,意味着(根据ToObject()的定义),null和undefined不能被解构,否则会抛出错误。
8.写出以下代码的输出结果
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name)
}
}
let person1 = new Person("p1", 18, "teacher")
let person2 = new Person("p2", 27, "doctor")
person1.sayName();
person2.sayName();
console.log(person1.sayName == person2.sayName)
console.log(person1.constructor == Person)
console.log(person2.constructor == Person)
console.log(person1 instanceof Object)
console.log(person2 instanceof Object)
console.log(person1 instanceof Person)
console.log(person2 instanceof Person)
// 结果
p1
p2
false
true
true
true
true
true
true
通过构造函数去创建一个对象的时候,其实是:
- 没有显示地创建了对象
- 属性和方法直接赋值给了this
- 没有return
如何实现new
- 在内存中创建一个新对象
- 整个新对象地内部地[[prototype]]特性被赋值为构造函数地prototype属性
- 构造函数内部地this被赋值给这个新对象(即this指向新对象)
- 执行构造函数内部地代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的对象。
constructor 和 实例
- constructor是用来标识对象的类型的。
- instanceof是用来确定对象类型更可靠的方式
- 每一个对象都是Object的实例,同时也是person的实例。
几个问题
- 实例对象上都是有一个constructor指向构造函数吗?
- 实例是不是就是指向谁创造而来的呢?
- 那么person1.constructor指向的能否是对象呢?Object
构造函数与普通函数
- 唯一区别就是调用方式的不同,任何函数只要使用new 操作符号调用就是构造函数
构造函数的问题
- 构造函数的定义的方法会在每一个实例上都再创建一遍。
- 函数是对象,每一次定义函数的时候,都会初始化一个对象。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name)
}
// 其实等同于以下的
this.sayName = new Function("console.log(this.name)")
}
- 因此person1.sayName 和person2.sayName 虽然同名但不是同一个了。
9.写出以下代码的输出结果
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype.sayName = function() {
console.log('name', this.name)
}
let person1 = new Person("p1", 18, "teacher")
let person2 = new Person("p2", 27, "doctor")
person1.sayName();
person2.sayName();
console.log(person1.sayName == person2.sayName)
name p1
name p2
true
原型模式
- 每一个函数都会创建一个prototype的属性,这个属性是一个对象,包含由特定引用类型的实例共享的属性和方法。
- 实际上,这个对象 就是通过调用构造函数创建对象的原型。
- 无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)
- 默认情况下,所有原型对象自动会获取一个名为constructor的属性,指回与之关联的构造函数
- 在自定义构造函数是,原型对象默认只回获取constructor属性,其他的所有方法都继承自Object
- 每一次调用构造函数创建一个新实例,这个实例的内部[[prototype]]指针就回被赋值为构造函数的原型对象。
问题
- 实例和构造函数原型之间有直接联系,但实例和构造函数之间没有。
10. 写出以下结果
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
let person2 = new Person();
person1.name = "Greg";
console.log(person1.name);
console.log(person2.name);
delete person1.name;
console.log(person1.name);
console.log(person1.name); // "Greg" - from instance
console.log(person2.name); // "Nicholas" - from prototype
delete person1.name;
console.log(person1.name); // "Nicholas" - from the prototype
- 通过对象访问属性时,会先搜索对象实例本身,如果找到了,就返回,如果没有找到就沿着指针进入原型对象,返回该值。
- 给对象实例添加一个属性,这个属性就会遮蔽原型对象上的同名属性。
- 即使将属性设置为null,也不会恢复联系,但可以通过delete 完全删除属性,让标识符解析继续搜索对象。
11. Object.create
let biped = {
numLegs: 2
}
let person = Object.create(biped);
person.name = "Matt"
console.log(person.name)
console.log(person.numLegs)
console.log(Object.getPrototypeOf(person) === biped)
Matt
2
true
Object.create 会创建一个新对象,并为其指定原型。
12. 写出以下结果
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
console.log(person1.hasOwnProperty("name"));
console.log("name" in person1);
let keys = Object.keys(Person.prototype);
console.log(keys);
p1.name = "Rob";
p1.age = 31;
let p1keys = Object.keys(p1);
console.log(p1keys);
console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true
console.log(keys); // "name,age,job,sayName"
console.log(p1keys); // "name,age"
两种使in的操作符的方式
- 单独使用 和 在for - in 中使用
- in 会返回该属性是由在对象上,原型属性和实例属性都会返回true
hasOwnProperty
- 只有属性存在实例上才会返回true
对象属性
- for in 遍历时,可以通过对象范文并可以被枚举的属性都会返回,包括实例属性和原型属性
- 遮蔽原型中[[Enumerable]]特性被设置为false的属性也会在for-in循环中被返回
- 获取对象上所有可枚举的实例属性,用Object.keys
- 列出对象上所有实例属性,无论是否可枚举用 Object.getOwnPropertyNames()
- ES7还增加了两个方法Object.values() 以及Object.entries()
constructor的改变不会影响instanceof
13. 写出以下代码
function Person() {}
let friend = new Person();
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
friend.sayName();
VM73:15 Uncaught TypeError: friend.sayName is not a function
at <anonymous>:15:8
原型的动态性
- 原型的搜索值时动态的,在实例修改前原型已经存在,在任何时候对原型对象的修改也会马上反映出来
- 重写原型和添加方法是两回事,实例的[[Prototype]]指针是在调用构造函数时自动赋值的,这个指针即使把对象修改为不同的对象也不会改变。重写整个原型会切断最初原型和构造函数的关系。
原生对象的原型
- 所有原型引用类型的构造函数(包括Object,Array,String)都在原型上定义了实例方法。
- 通过原生对象的原型可以取得所有默认方法的引用,也可以给原生类型的实例定义新的方法。可以修改自定义对象原型一样修改原生对象原型,因此可以随时修改添加方法。
- 像字符串,在读取属性时,后台会自动加上String的包装实例,从而找到String.prototype上的方法调用。
什么时包装实例?
14.原型链的问题
function SuperType(name){
this.property = true
}
SuperType.prototype.getSuperValue = function() {
return this.property
};
function SubType(){
this.subProperty = false
}
// inherit methods
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subProperty
};
let instance1 = new SubType();
console.log(instance.getSuperValue())
继承的思想
- 通过原型继承多个引用类型的属性和方法
- 每一个构造函数都有以一个属性对象,原型有一个属性指向回构造函数,而实例内部有一个指针指向原型
- 如果一个原型是另一个类型的实例?那就意味着,整个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
默认原型
-
默认情况下,所有引用类型都继承Object。
-
任何函数地默认原型都是一个Object的实例。也就是说这个实例有一个内部指针指向Object.prototype
15.如何实现一个寄生式组合继承
function obejct(o) {
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype); // create object
prototype.constructor = subType; // augment object
subType.prototype = prototype; // assign object
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name); // second call to SuperType()
this.age = age;
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function() {
console.log(this.age);
};
-
继承父类的实例方法 super.call
- 在子类的构造函数中调用父类的构造函数。因为函数其实就是在特定上下文中执行代码的简单对象
-
原型式继承:有一个对象,想要在已有的基础上再创建一个对象。
- ES5 新增的 Object.create()
-
为什么不用super.prototype = new SuperType()?
- 这样会导致父类的构函数始终被两用两次,一次是再原型创建子类原型时调用,另一次时再子类构造函数时调用。
-
而inheritPrototype 方法时,通过父类的一个原型副本来实现。
16. 类
定义提升
- 函数声明可以提升,但类定义不行
构成
- 构造函数
- 实例方法
- 获取函数 get
- 设置函数 set
- 静态方法 static
类构造函数 和 函数构造函数
- 调用类构造函数必须使用new ,普通构造函数如果不用new 就是以全局的this作为普通对象
- 调用类如果没有new 会抛出异常
- 类中定义的constructor不会被当作构造函数,对它使用instanceof会返回false。但是如果创建实例直接将类构造函数当成普通构造函数来使用,那么instanceof就会不同了。
17. 类的语法
实例成员
每次通过new调用类 的时候,都会执行类的构造函数。函数内部会创建新实例this,添加自有属性。
每一个实例都对应唯一一个成员对象。
原型方法与访问器
在类块中定义的所有内容都会被定义在类的原型上
不能在类块中给原型添加原始值或者对象作为成员数据
静态方法
每一个类上只有一个静态成员。
静态成员中this指向引用类本省
非函数原型和类成员
可以在类的外部定义
迭代器和生成器方法
类定义语法支持在原型和类本身定义生成器方法
18. 继承
实现基础
通过extends来实现
构造函数和HomeObject 和super()
- 静态方法中调用super可以继承类上定义的方法
super的注意点
- 只能在派生类构造函数和静态方法中使用
- 不能单独引用super关键字,要么用它调用构造函数,要么引用它的静态方法
- 调用super()会调用父类构造的函数,并将返回的实例赋值给this
- 在类构造函数中,不能再super()之前引用this
- 如果在派生类中显示定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回了一个对象。
抽象基类
- 它可供其他类继承,但本身不会被实例化
- 通过new.target
继承内置类型
类混入
- extends 关键字后面是一个Javascript表达式。任何可以解析为一个类或者一个构造函数的表达式都是有效。