js三座大山
一:js三座大山之函数1 & js三座大山之函数2-作用域与动态this 即函数式编程。
二:js三座大山之对象,继承,类,原型链即面向对象编程
三:异步和单线程,目前主流是以promise为代表的异步编程
对象object
如何理解对象:js中可以从多个角度看待对象
- 对象是键值对:
有key/value:key只能是字符串和symbol
,值可以是如何类型。
例子:
typescript
const obj = {};
obj[1] = '1111'; // 转化字符串'1'赋值
obj['1'] = '22222'; // 覆盖'1'
obj[null] = '33333'; // 转化为字符串`null`
obj[undefined] = '444444'; // 转化为字符串`undefined`
obj[Symbol('1')] = '5555'; // symbol 作为key
obj[{}] = '66666'; // 转化为字符串{}.toString() = Object.prototype.toString() = [object Object]
obj.name = '77777'; // 'name'
obj[{age: '18'}] = '88888'; // {age: '18'} 转化为字符串 Object.prototype.toString() = [object Object]
obj[[]] = '99999'; // 转化为字符串[].toString() = ''
obj结果:
{
"": "99999"
1: "22222"
[object Object]: "88888"
name: "77777"
null: "33333"
undefined: "444444"
Symbol(1): "5555"
}
打印对象所有key
Reflect.ownKeys(obj).map(item=> typeof item)
// ['string', 'string', 'string', 'string', 'string', 'string', 'symbol']
key只有字符串和symbol两种类型。
属性描述符:
数据描述符value
描述属性值 writable
属性值是否可以更改。
访问器描述符getter
读取属性时会走到getter方法 obj.a || obj['a']. setter
设置属性时会走到setter方法 obj.a = 12.
数据描述符和访问器描述符两者不可同时设置
通用描述符configurable
当设置为false.属性描述符不能变更类型即不能从数据描述符变更为访问器描述符,同时属性不可删除。enumerable
属性是否可枚举。
枚举和迭代的区别。
遇到过很多同学会混淆这两个概念 这里给一个明确的区分。
- 1.可枚举(enumerable):
这是一个对象属性的特性,表示该属性是否可以通过某些操作被枚举出来。如果一个属性的enumerable
特性为true
,那么它可以被for...in
循环和Object.keys()
方法枚举出来。你可以通过Object.defineProperty()
或Object.defineProperties()
方法来设置属性的enumerable
特性。 - 2.可迭代(iterable):
这是一个对象的特性,表示该对象是否可以被迭代,即是否可以用for...of
循环遍历。一个对象是可迭代的,需要实现一个名为Symbol.iterator
的方法。这个方法返回一个迭代器对象,迭代器对象有一个next()
方法,每次调用这个方法都会返回一个包含value
和done
两个属性的对象。 例如数组是可以迭代的
所以通常我们说的遍历 都是指迭代.
ini
const a = []; // 数组实现了迭代器
a[Symbol.iterator] // 返回迭代方法 ƒ values() { [native code] }
const b = {};
b[Symbol.iterator] // undefined 所以object不可迭代
const s = ''; // 字符串实现了迭代器
s[Symbol.iterator] // 返回迭代方法 ƒ values() { [native code] }
js中与属性描述相关的方法主要是两类一种是获取的getOwnPropertyDescriptor
一种是修改的defineProperty
。两个api在Object Proxy Reflect上都存在。也有批量的操作getOwnPropertyDescriptors
和defineProperties
. 例子:
javascript
const obj = {
name: "John"
};
// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, "name");
console.log(descriptor);
// 输出:{ value: 'John', writable: true, enumerable: true, configurable: true }
// 设置属性描述符
Object.defineProperty(obj, "name", {
value: '123',
writable: false,
});
console.log(Object.getOwnPropertyDescriptor(obj, "name"));
// 输出:{value: '123', writable: false, enumerable: true, configurable: true}
console.log(obj) // {name: '123'}
obj.name = '456'; // 不可写 所以无效
console.log(obj) // {name: '123'}
- 对象是实例:
在面向对象编程中,对象通常被视为类的实例。类定义了对象的结构和行为,而对象是这个定义的具体实现。例如,你可以定义一个"汽车"类,然后创建一个"汽车"对象,这个对象就是"汽车"类的一个实例。对象有实例属性
静态属性
原型属性
。
例子:
javascript
class Car{
static config(){
console.log('static config')
};
color='';
constructor(c){
this.color = c;
};
run(){
console.log(`${this.color} car run`)
};
}
const c1 = new Car('red');
const c2 = new Car('blue');
打印c1: {color: 'red'}
打印c2: {color: 'blue'}
color 实例属性
获取原型Object.getPrototypeOf(c1) // {constructor: ƒ, run: ƒ}
run 原型属性
c1.config // undefined
Car.config // 打印函数 静态属性
类class
在JavaScript中,类(class)是一种特殊的函数,是对象的模板
。它提供了一种更简洁和更直观的方式来创建对象和处理继承.
例如上面的例子。
javascript
es5:
function Car(c){
this.color = c;
}
Car.prototype.run=function(){
console.log(`${this.color} car run`)
}
Car.config=function(){
console.log('static config')
}
es2015:
class Car{
static config = function(){
console.log('static config')
};
name='';
constructor(c){
this.color = c;
};
run(){
console.log(`${this.color} car run`)
};
}
类实际是一个语法糖,下面是一个经过编译后的代码:
javascript
/**
* 返回类型
*/
function _typeof(obj) {
return;
}
/**
* 作为方法调用拦截
*/
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
/***
* 批量给对象添加属性 定义属性描述符
*/
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
/***
* 将属性动态添加到原型上
*/
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, 'prototype', { writable: false });
return Constructor;
}
/**
* 属性添加到实例上
*/
function _defineProperty(obj, key, value) {
obj[key] = value;
return obj;
}
/**
* 将输入转化为一个合法的属性类型(string | symbol)
*/
function _toPropertyKey(arg) {
return;
}
var Car = (function () {
function Car(c) {
_classCallCheck(this, Car); // 拦截作为普通函数调用
_defineProperty(this, 'name', ''); // 添加实例属性
this.color = c; // 添加实例属性
}
_createClass(Car, [
{
key: 'run',
value: function run() {
console.log(''.concat(this.color, ' car run'));
},
},
]);
return Car;
})();
_defineProperty(Car, 'config', function () {
console.log('static config');
});
实际上就是上面的es5实现而已~
继承extend
js中实现继承的方式非常的多 根据需求场景可以采用不同的方法。
目的都是为了安全合理的获取父类的属性和方法
。
通过class方式
javascript
class Car{
static config(){
console.log('static config')
};
lights=[];
color='';
constructor(c){
this.color = c;
};
run(){
console.log(`${this.color} car run`)
};
}
class Maybach extends Car{
values(){
console.log('价格贵')
}
}
const m = new Maybach('green')
const m1 = new Maybach('green')
console.log(m.color) // 继承color属性 green
console.log(m.run()) // 继承run方法 green car run
console.log(m.values()) // 实现了新定义的方法 价格贵
m.lights.push(1)
console.log(m.lights) // [1] //继承的属性为私有的 不会影响其他实例
console.log(m1.lights) // []
构造方法继承
javascript
function Car(c){
this.color = c;
this.show=function(){}
}
Car.prototype.run=function(){
console.log(`show car`)
}
function Maybach (c, prize) {
Car.call(this, c); // 继承实例属性
}
const m = new Maybach('red', '1000w')
m.run // undefined
m.show // 打印函数
缺点:方法只能写在构造函数里面,原型上的方法无法被继承,原型的存在就是为了继承和js的设计初衷相违背了。
原型继承
javascript
function Car(){
this.lights = [1,2];
this.show=function(){}
}
Car.prototype.run=function(){
console.log(`show car`)
}
function Maybach (prize) {
this.prize = prize;
}
Maybach.prototype = new Car();
const m1 = new Maybach('1000w')
const m2 = new Maybach('900w')
m1.lights.push(3)
m1.lights // [1,2,3]
m2.lights // [1,2,3]
缺点:所有实例都访问同一个原型对象,引用类型的属性被共享了,无法做到实例隔离。
组合继承
通过将构造方法继承继承和原型继承合并在一起,避免了他们的缺点。既可以继承原型的方法 也可以做到引用类型实例隔离。
javascript
function Car(){
this.lights = [1,2];
this.show=function(){}
}
Car.prototype.run=function(){
console.log(`show car`)
}
function Maybach (prize) {
Car.call(this); // 实例属性
this.prize = prize;
}
Maybach.prototype = new Car() // 原型继承
const m1 = new Maybach('1000w')
const m2 = new Maybach('900w')
m1.lights.push(3)
m1.lights // [1,2,3]
m2.lights // [1,2]
缺点:原型对象上还是会有car的属性,但永远不会访问到。会有冗余。 可以进一步优化
javascript
Maybach.prototype = new Car() // 这里修改一下 不在直接new Car
Maybach.prototype = Car.prototype // 额... 这样更不行 父子同一个原型 子改父也会被改变
所以我们需要借助一个中间函数
var Fn = function(){}
Fn.prototype = Car.prototype;
Maybach.prototype = new Fn()
寄生组合继承
优化后的继承会更好一些。
javascript
function Car(){
this.lights = [1,2];
this.show=function(){}
}
Car.prototype.run=function(){
console.log(`show car`)
}
function Maybach (prize) {
Car.call(this); // 实例属性
this.prize = prize;
}
var Fn = function(){}
Fn.prototype = Car.prototype;
Maybach.prototype = new Fn(); // 空白的对象实例 实现原型继承
在JavaScript中,继承是通过原型链来实现的,当试图访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript会沿着原型链去查找这个属性。
原型&原型链prototype
要想画出正确的原型链 本质上就是在解释 对象 原型 构造函数三者的关系 需要明确以下几个规则。
1:任何对象都是由函数创建的
2:只有函数才拥有原型对象
3:函数是可执行的对象
4:对象有__proto__指向原型对象、有constructor属性指向构造函数
原型链有两个终点:
作为对象的终点是object.prototype然后是null;
作为函数的终点是function。