object.assign()
这个方法回使源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值
实际上对每个源对象执行的是浅复制,如果多个源对象都有相同的属性,则使用最后一个赋值的值
let obj1 = {
get a() {
return 'aaa'
}
}
let obj2 = {
set a(val) {
console.log(val)
}
}
console.log(obj1.a) //'aaa'
// 对象引用
let obj1 = {};
let obj2 = { a:{} }
Object.assign(obj1, obj2)
console.log( obj1.a===obj2.a ) //true
// 错误处理
let obj1 = {}
let obj2 = {
a: 1,
get b() {
throw new Error();
},
c: 3
}
try {
Object.assign(obj1, obj2)
} catch (e) {
}
console.log(obj1) //{a:1}
// 因此在抛出错误之前,目标对象上已经完成的修改会继续存在
Object.is()
和===很像,接收两个参数
console.log(+0 === -0) //true
console.log(+0 === 0) //true
console.log(-0 === 0) //true
// 判断NaN
console.log(NaN === NaN)//false
console.log(isNaN(NaN))//true
// 用object.is()轻松解决
console.log(Object.is(NaN, NaN))//true
console.log(Object.is(+0, -0))//false
// 要检查超过两个值,利用递归即可
function deep(x, ...rest) {
// 传入的值为 1,[1,1,4]; 1,[1,4] ; 1,[4]
return Object.is(x, rest[0]) && (rest.length < 2 || deep(...rest))
}
let a = deep(1, 1, 1, 4)
console.log(a) //false
es6增强的对象语法
//1, 属性名简写
let a = 1;
let obj = {a};
console.log(obj) //{a:1}
// 代码压缩程序会在不同的作用域间保留属性名,以防找不到引用 ------这句话还不理解
//2,可计算属性
// 有了计算属性,就可以在对象字面量中完成动态属性赋值.中括号运算符包围
//的对象属性键告诉运行时将其作为JavaScript表达式而不是字符串来求职
// 例
const a = 'name';
const b = 'sex';
const c = 'age';
let obj = { [a]: '小红', [b]: '女', [c]: 10 }
console.log(obj) //{name:'小红',sex:'女',age:10}
// 当然[]里也可以是一个复杂的函数
// 3,简写方法名
let obj = {
// 旧的写法
oldName: function (a) {
console.log(a)
},
//新的写法
newName(a) {
console.log(a)
}
}
解构赋值
// 解构在内部使用ToObject()把源数据结构转换为对象。这也意味着
//在对象结构的上下文中,原始值会被当成对象,这也意味着
// (根据ToObject()的定义),null和undefined不能被结构,否则会抛出错误。
// 例
let { length } = 'foobar';
let { constructor } = 'foobar'
// // 这里一定要切记 源对象转为对象就能看懂了
console.log(length, constructor) //6,ƒ String() { [native code] }
console.log(constructor === String) //true
//嵌套结构
//解构可以使用嵌套结构,以匹配嵌套的属性:
let obj = {
name: '小明',
age: 11,
job: {
title: 'web前端'
}
}
let { job: { title } } = obj
console.log(title) // web前端
// 参数解构---对参数进行解构不会影响arguments对象
let data = {
b: 2, c: 3
}
function aaa(a, { b, c }, d) {
console.log(arguments) // 1,[2,3],4
console.log(b, c) // 2,3
}
aaa(1, data, 2)
try catch 语句
如果try中的代码没有出错,则程序正常运行try中的内容后,不会执行catch中的内容,
如果try中的代码一但出错,程序立即跳入catch中去执行代码,那么try中出错代码后的
所有代码就不再执行了.
try {
({ name: p, work: c, job: b } = obj)
} catch (e) {
}
console.log(p, c, b)
工厂函数
这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解
决对象标识问题(即新创建的对象是什么类型)
function createPerson(name, age, sex) {
let o = new Object();
o.name = name;
o.age = age;
o.sex = sex;
o.say = function () {
console.log(`大家好,我的名字叫---${name}`)
}
return o
}
let p1 = createPerson('小明', 11, '男')
p1.say()
let p2 = createPerson('小红', 10, '女')
构造函数
定义构造函数可以确保实例被标识为特定类型
构造函数也是函数,和普通函数的唯一区别在于调用方式不同
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.job = 'web';
this.say = function () {
console.log(`大家好,我的名字叫---${name}`)
}
}
const p3 = new Person('小刚', 11, '男');
// 不传参的情况下,可以不加括号,只要有new操作符,就可以调用对应的构造函数
构造函数和工厂函数的区别
1、没有明显的创建对象
2、属性和方法直接赋值给了this
3、没有return
构造函数需要注意的事项?
首字母大写,有助于和普通函数进行区分。
要创建Person实例,需要使用new操作符,以这种方式调用构造函数会进行如下操作。
1> 在内存中创建一个新对象;
2> 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性。
3> 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
4> 执行构造函数内部的代码(给新对象添加属性)
5> 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。 ——————不太明白
原型模式
每个函数都会创建prototype属性,这个属性是一个对象,
包含应该由特定引用类型的实例共享的属性和方法。
好处是 在它上面的属性或方法可以被对象实例共享
console.log(Person.prototype)
Person.prototype.say = function () {
console.log(`我是原型上的方法${this.name}`)
}
console.log(p4)
高阶函数
符合以下一个条件即可
- 若A函数,接收到的参数是一个函数,那么A就可以称之为高阶函数。
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
常见的有Promist setTimeout 、arr.map()……
函数的柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
例 :
function aaa(a){
return function bbb(b){
return function ccc(c){
return a+b+c
}
}
}
d=aaa(1)(2)(3)
console.log(d)
纯函数
- 一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下一些约束
- 不得改写数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用Date.now()或者Math.random()等不纯的方法
nanoid(现在这放着,懒得开新的了)
- 安装 yarn add nanoid
- 使用 import {nanoid} from ‘nanoid’;
- 语法
nanoid()
即可生成随机字符
instanceof
检查实例的原型链中,是否包含指定构造函数的原型
function Person(name) {
this.name = name
}
const p1 = new Person('小红');
console.log(p1 instanceof Person) //true
console.log(p1 instanceof Object) //true
console.log(Person.prototype instanceof Object) //true
isPrototypeOf()
本质上,会在传入参数的[[prototype]]指向调用它的对象时返回true
function Person(name) {
this.name = name
}
const p1 = new Person('小红');
const p2 = new Person('小明');
console.log(Person.prototype.isPrototypeOf(p1)) //true
console.log(Person.prototype.isPrototypeOf(p2)) //true
Object.getPrototypeOf(obj)
可以方便的取得一个对象的原型。
function Person(name) {
this.name = name
}
const p1 = new Person('小红');
console.log(Object.getPrototypeOf(p1)) //{constructor:f}
console.log(Object.getPrototypeOf(p1)==Person.prototype) //true
Object.setPrototypeOf()
可以向实例的私有属性[[prototype]]写入一个新值,这样就可以重写一个对象的原型继承关系
【注意】这个方法可能会影响代码性能,会涉及所有访问了拿些修改过[[prototype]]的对象的代码
Object.setPrototypeOf(p1, p2)// p1.__proto__===p2
Object.create()
来创建一个新对象,同时为其指定原型
let biped = { numLegs: 2 };
let person = Object.create(biped)
person.name = 'Matt';
console.log(person.name); //'Matt'
console.log(person.numLegs); //2
console.log(Object.getPrototypeOf(person) === biped); //true
原型层级
- 在通过对象访问属性时,会按照这个属性的名称开始搜索,搜索开始对象实例本身。
如果在这个实例上发现了给定的名称,则返回该对象名称对应的值 - 如果没有找到属性,则搜索会沿着指针进入原型对象,找到则返回该属性对应的值,
这也就是原型用于在多个对象实例间共享属性的原理。
【注意】通过实例读取原型对象的值是不可更改的,如果在实例上添加一个与
原型对象同名的属性,则会在实例上创建这个属性,这样会遮住原型对象的属性
delect操作符
可以完全删除实例上的属性
function Person(name) {
this.name = name;
}
let p1 = new Person('小红');
Person.prototype.name = '小明';
console.log(p1.name) //'小红'
delete p1.name;
console.log(p1.name) //'小明'
hasOwnProperty()
检测是否为实例属性
function Person() {
}
let p1 = new Person();
let p2 = new Person();
Person.prototype.name = '小明';
p1.name = '小红';
console.log(p1.hasOwnProperty('name')) //true
console.log(p2.hasOwnProperty('name')) //false
Object.getOwnPropertyDescriptor()
取得原型属性的描述符(只对实例属性有效)
function Person() {
}
let p1 = new Person();
let p2 = new Person();
Person.prototype.name = '小明';
p1.name = '小红';
console.log(Object.getOwnPropertyDescriptor(p1, 'name'))
//{value: "小红", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(p2, 'name')) //undefined
in操作符
function Person(name) {
}
let p1 = new Person();
console.log('name' in p1) //false
p1.name = '小明'
console.log('name' in p1) //true
let p2 = new Person();
console.log('name' in p2) //false
Person.prototype.name = '小红'
console.log('name' in p2) //true
//结论,无论属性在实例自身还是原型上都会返回true
利用in和hasOwnProperty的特性实现检测属性是否存在于原型上
function detectionP(obj, k) {
return !obj.hasOwnProperty(k) && ([k] in obj)
}
console.log(detectionP(p2, 'name'))
for-in循环中使用in操作符时,可以通过对象访问且可以被枚举的属性都会返回,
包括实例属性和原型属性
function Person(name) {
this.name = name
}
Person.prototype.age = 10
let p1 = new Person('小红');
Object.defineProperties(p1, {
"school": {
value: 'bgs',
enumerable: true //默认false
},
"class": {
value: '1908'
}
})
console.log(p1) //Person {name: "小红", school: "bgs", class: "1908"}
for (let k in p1) {
console.log(k) // name,school,age
}
Object.keys()
接收一个对象作为参数,返回包含该对象所有可枚举属性名称的字符串数组
function Person(name) {
this.name = name
}
let p1 = new Person('小红');
Object.defineProperties(p1, {
"school": {
value: 'bgs',
enumerable: true //默认false
},
"class": {
value: '1908'
}
})
console.log(Object.keys(p1)) //['name']
Object.getOwnPropertyNames()
只要是实例属性(包含原型),无论是否可以枚举,返回对象所有属性名称的字符串数组
function Person(name) {
this.name = name
}
let k1 = Symbol('k1')
let p1 = new Person('小红');
Object.defineProperties(p1, {
"school": {
value: 'bgs',
enumerable: true //默认false
},
"class": {
value: '1908'
},
[k1]: {
value: 1
}
})
console.log(Object.getOwnPropertyNames(p1)) //["name", "school", "class"]
Object.getOwnPropertySymbols()
只针对符号,类似getOwnPropertyNames
语法Object.getOwnPropertySymbols(obj)
属性枚举的顺序
- for-in和Object.keys()的枚举顺序是不确定的,取决于javascript引擎,
可能因浏览器而异 - Object.getOwnPropertyName()、Object.getOwnPropertySymbols()
和object.assign()的枚举顺序是确定性的。 - 先以升序枚举数值键,然后以插入顺序枚举字符串和符号键,
在对象字面量中定义的键以它们逗号分隔的顺序插入。
ECMAScript2017新增两个静态方法
Object.values()
接收一个对象,返回对象值的数组(浅复制)
Object.entries()
接收一个对象,返回键值对数组(浅复制)。非字符串属性会被转换为字符串输出,
符号属性会被忽略
let k1 = Symbol('k1')
let o = {
a: 1,
b: 2,
c() { },
[k1]: 3
}
console.log(Object.values(o)) //[1,2,fn]
console.log(Object.entries(o)) //[['a',1],['b',2],['c',fn]]
原型的语法
之前每次给原型上增加属性都要通过Person.prototype.key=value
function Person() { }
//错误的写法1,会丢失constructor属性
Person.prototype = {
name: '小红',
age: 10
}
const p1 = new Person()
console.log(Person.prototype.constructor) //ƒ Object() { [native code] }
//错误的写法2,这样会得到一个[[Enumberable]]为true的constructor属性
Person.prototype = {
constructor: Person,
name: '小红',
age: 10
}
const p1 = new Person()
console.log(p1.constructor)
for (let k in p1) {
console.log(k) //name,age,constructor
}
//正确的写法
Person.prototype = {
name: '小红',
age: 10
}
Object.defineProperty(Person.prototype, 'constructor', {
value: Person,
enumerable: false
})
const p1 = new Person()
for (let k in p1) {
console.log(k) //name,age
}
实例只有指向原型的指针,没有指向构造函数的指针.
重写构造函数上的原型之后在创建的实例才会引用引得原型。而再次之前创建的实例仍然会引用最初的原型
//例1
let p1 = new Num()
function Num() {
}
Num.prototype.count = 2
console.log(p1.count) //2
//例2
let p1 = new Num()
function Num() {
}
Num.prototype = {
constructor: Num,
count: 2
}
let p2 = new Num()
console.log(p1.count) //undefined
console.log(p2.count) //2
toString()
方法可把一个 Number 对象转换为一个字符串,并返回结果。
一个参数:可以返回2~32进制的字符串
检查数据类型
Object.prototype.toString.call()
继承
原型链继承的问题
- 主要问题出现在原型中包含引用值的时候(相同引用)
- 子类型在实例化时不能给父类型的构造函数传参
盗用构造函数(也叫“经典继承”或“对象伪装”)
使用apply()和call()方法以新创建的对象为上下文执行构造函数
缺点不能访问父类原型上定义的方法
function Animal(color) {
this.color = color
}
function Dog(red, name) {
Animal.call(this, red)
this.name = name
}
const d1 = new Dog('红色', '小黄')
console.log(d1)
组合继承
就是综合了原型链和盗用构造函数,将两者的优点结合。
基本思路就是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性
调用过程中会执行两次SuperType函数
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);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function () {
console.log(this.age);
}
const instance1 = new SubType('小红', 19)
instance1.sayAge()
instance1.sayName()
console.log(instance1)
寄生式组合继承(效果最佳) (红宝石248页)
只会调用一次SuperType函数