@tsingwong
2019-05-13T15:23:24.000000Z
字数 17526
阅读 747
面试
防抖是触发高频事件后,n 秒内函数只会执行一次,如果 n 秒内再次触发,重新计算时间。即高频事件在防抖的效果下,只会触发一次。思路:每次触发事件时都取消之前的延迟调用方法。
节流是触发高频事件后,n秒执行一次,即高频事件在节流的效果下,可能会触发多次。思路:每次触发事件时都会判断当前是否有等待执行的延迟函数。
/**
* 函数防抖
* fn 需要防抖的高频函数
* delay 延迟的秒数
* immediate 是否立即执行
*/
function debounce(fn, delay, immediate) {
let timeout = null;
return function (...args) {
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
let callNow = !timeout;
if (callNow) {
fn.apply(this, args);
}
}
timeout = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
}
/**
* 函数节流
* fn 需要节流的高频函数
* delay 延迟的秒数
*/
function throttle(fn, delay) {
let timeout = null;
return function (...args) {
if (!timeout) {
timeout = setTimeout(() => {
fn.apply(this, args);
timeout = null;
}, delay);
}
}
}
__proto__
属性指向构造函数的原型对象;this
,用构造函数内部的方法修改空对象;
function _new(fn, ...args) {
const obj = Object.create(fn.prototype);
const ret = fn.apply(obj, args);
return ret instanceof Object ? ret : obj;
}
JavaScript 主线程是一个执行栈(同步任务)和一个任务队列
JavaScript 中有宏观任务和微观任务之分。
微观任务优先级高优宏观任务。
每次 eventloop 中都会有一个 宏观任务,和多个微观任务。
执行顺序是先按照顺序执行微观任务,执行完成后执行宏观任务,依次执行下去,直到所有的任务都被执行完毕。
setTimeout 属于宏观任务。
宏观任务主要包含:
promise 和 async await 属于微观任务。
微观任务主要包含:
Promise 是同步的立即执行函数,主线程执行完毕后才会执行到 resolve 或 reject 函数。
Async 函数返回一个 Promise 对象,当函数执行时,遇到 await 先返回,等到触发异步操作完成时,再执行函数体后面的语句。可以理解为让出线程,跳出 async 函数体。
await 的含义为等待,也就是 async 函数需要等待 await 后的函数执行完成并且有了返回结果( Promise 对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。
执行完 await 后,回先让出线程,执行后面的同步代码。
async function async1() {
console.log('async1 start') // 4
await async2() // 5 执行后让出线程,先执行同步代码
console.log('async1 end') // 7 加入微观任务1 11 开始微观任务
}
async function async2() {
console.log('async2') // 6
}
console.log('script start') // 1
setTimeout(function () { // 2 加入宏观任务
console.log('settimeout') // 13 宏观任务开始
})
async1() // 3
new Promise(function (resolve) {
console.log('promise1') // 8
resolve() // 9 加入微观任务2
}).then(function () {
console.log('promise2') // 12 至此微观任务结束
})
console.log('script end') // 10 至此同步任务执行完毕
// script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2-> settimeout
回调函数(callback)
setTimeout(() => {
// callback 函数体
}, 1000)
缺点:回调地狱,不能用 try...catch
捕获错误,不能 return。
缺乏顺序性,嵌套函数过于耦合,嵌套函数过多时,很难处理错误。
Promise
链式调用,解决了 callback 回调地狱,每个 then 后面返回的都是一个新的 Promise。
缺点:无法取消 Promise,错误需要通过回调函数来捕获
new Promise(function (resolve) {
console.log('promise1') // 8
resolve() // 9 加入微观任务2
}).then(function () {
console.log('promise2') // 12 至此微观任务结束
})
Generator
可以控制函数执行。
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
Async/Await
异步的终极解决方案
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
优点:代码清晰, 不用像 Promise 对象那样写一堆 then 链,处理了回调地狱的问题。
缺点:await 将异步代码改造成了同步代码,如果多个异步操作没有依赖性,反而会因为使用了 await 导致性能降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
b
先执行,在执行到 await 10
之前变量 a
还是 0,因为 await
内部实现了 generator
,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来await
是异步操作,后来的表达式不返回 Promise
的话,就会包装成 Promise.reslove(返回值)
,然后会去执行函数外的同步代码a = 0 + 10
上述解释中提到了 await
内部实现了 generator
,其实 await
就是 generator
加上 Promise
的语法糖,且内部实现了自动执行 generator
。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。
// promise
const sleepPromise = time => {
return new Promise(resolve => setTimeout(resolve, time));
};
sleepPromise(1000).then(() => {
console.log(1);
});
// Generator
function* sleepGenerator(time) {
yield new Promise(resolve => {
setTimeout(resolve, time);
});
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)});
// async/await
async function output() {
let out = await sleepPromise(1000);
console.log(1);
return out;
}
output();
合成后,在绘制到界面上
解析HTML,生成DOM树,解析CSS,生成CSSOM树
HTTP 状态码
1**:临时回应,表示客户端请继续
2**:请求成功
3**:请求目标有变化,304 资源未发生改变, 产生前提客户端本地已有缓存版本,并在 Request 中告诉服务器端,服务器端通过时间或 tag ,发现没更新的时候,返回一个不含 body 的 304 状态
4**:客户端请求错误
5**:服务端请求错误
HTTPS 是使用加密通道来传输 HTTP 的内容,但是 HTTPS 首先会跟服务端建立一条 TLS 加密通道,TLS 构建于 TCP 协议之上,实际上是对传输内容做一次加密,所以从传输内容来看,HTTPS 跟 HTTP 是没有区别的
重绘:元素几何属性发生变化,或样式发生变化而不影响布局的。outline
、visibility
、color
、background-color
等等。
重排:当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。如:增、删DOM节点,元素位置改变或应用动画,元素尺寸变更,浏览器窗口变化,读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE) )
重排一定重绘,重绘不一定重排。
浏览器优化是在浏览器会将修改操作放在任务队列里,然后至少一个浏览器刷新时间(16.67ms)才会清空队列。需要注意的是,不要手动读取上面提到的属性,会强制渲染刷新队列。
减少重绘重排
transform
、opacity
、filters
这些动画不会引起回流重绘 。定位方案有三种:
BFC 是 Block Fromatting Contexts (块级格式上下文),属于普通流。
特性:
构建 BFC :
absolute
、fixed
)inline-block
、table-cells
、flex
、inline-flex
、flow-root
(没有副作用的方案,但需注意兼容性)、grid
、inline-grid
等BFC 作用:
浅拷贝
// ES6 之前
function extendCopy(obj) {
let temp = {};
for (let i in obj) {
temp[i] = obj[i];
}
return temp;
}
// ES6 之后两种方法 Object.assign() 和 `...`展开运算符
let target = Object.assign({}, obj);
let b = {...a}
深拷贝
JSON.parse(JSON.stringify(object))
有一定局限性,会忽略 undefined、symbol,不能序列化函数,不能解决循环引用的对象。
function deepClone(obj)
// 是不是对象
function isObject(o) {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if (!isObject(obj)) {
throw new Error('非对象')
}
// 是不是数组
let isArray = Array.isArray(obj)
let newObj = isArray ? [...obj] : { ...obj }
Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj.b.c) // 2
for...in 语句用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作),遍历的是可枚举属性。一般需要配合 hasOwnProperty()
使用
for…of 语句是 ES6 新增的语法,多用于遍历可迭代对象( Array
,Map
,Set
,String
,TypedArray
,arguments 等等)
不同组件之间通信:
Event Bus 发布订阅模式
class EventEmeitter {
constructor() {
this._events = this.events || new Map();
this._maxListeners = this._maxListeners || 10;
}
// 触发事件
emit(type, ...args) {
let handler;
handler = this._events.get(type);
handler.forEach(item => {
if (args.length > 0) {
item.apply(this, args);
} else {
item.call(this);
}
})
return true;
}
// 监听 type 事件
addListener(type, fn) {
const handler = this._events.get(type);
if (!handler) {
this._events.set(type, [fn]);
} else {
handler.push(fn);
}
}
removeListener(type, fn) {
const handler = this._events.get(type);
if (handler.indexOf(fn) > -1) {
handler.splice(handler.indexOf(fn), 1);
} else {
consol.err('没找到方法');
}
}
}
// 实例化
const emitter = new EventEmeitter();
function expel (man) {
console.log(`expel ${man}`);
}
function save (man) {
console.log(`save ${man}`);
}
// 监听一个名为arson的事件对应一个回调函数
emitter.addListener('arson', expel);
emitter.addListener('arson', save);
emitter.emit('arson', 'low-end');
emitter.removeListener('arson', save);
emitter.emit('arson', 'low-end');
hash 路由: 带 #
表示,通过监听 URL 里的 hash 变化来进行路由跳转,兼容性较好。
class Routers {
constructor() {
this.routes = {};
this.currentUrl = '';
this.refresh = this.refresh.bind(this);
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path, cb) {
this.routes[path] = cb || function() {};
}
refresh() {
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();
}
}
window.Router = new Routers();
var content = document.querySelector('body');
// change Page anything
function changeBgColor(color) {
content.style.backgroundColor = color;
}
Router.route('/', function() {
changeBgColor('yellow');
});
Router.route('/blue', function() {
changeBgColor('blue');
});
Router.route('/green', function() {
changeBgColor('green');
});
history API
history.back()
history.forward()
history.go(-3)
history.pushState(state, title, url)
用于在浏览历史中添加历史记录,但是并不触发跳转history.replaceState
方法的参数与pushState
方法一模一样,区别是它修改浏览历史中当前纪录,而非添加记录,同样不触发跳转popstate
事件,每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件
class Routers {
constructor() {
this.routes = {};
// 在初始化时监听popstate事件
this._bindPopState();
}
// 初始化路由
init(path) {
history.replaceState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 将路径和对应回调函数加入hashMap储存
route(path, callback) {
this.routes[path] = callback || function() {};
}
// 触发路由对应回调
go(path) {
history.pushState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 监听popstate事件
_bindPopState() {
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
}
变化监测
拉:状态变化,框架会暴力对比DOM节点,得出某些需要重新渲染。Angular 是脏检查,React 使用的 虚拟DOM。
推:一定程度的状态变更,会通知到框架。
监听变化的方法:Object.defineProperty()
或 Proxy
(vue 3.0)中使用。
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
}
})
}
在 getter 中收集依赖,在 setter 中触发依赖。
// Dep.js
export default class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
let index = this.subs.indeOf(sub);
if (index > -1) {
this.subs.splice(sub, 1);
}
}
// 增加依赖
depend() {
if(window.target) {
this.addSub(window.target);
}
}
// 通告,触发依赖
notify() {
// 不改变原数组
const sub = this.sub.slice();
sub.forEach(item => item.update());
}
}
调用
function defineRective(data, key, val) {
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
dep.depend();
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
dep.notify();
}
});
}
收集 依赖,依赖就是 Watcher
首先,new
的方式优先级最高,接下来是 bind
这些函数,然后是 obj.foo()
这种调用方式,最后是 foo
这种调用方式,同时,箭头函数的 this
一旦被绑定,就不会再被任何方式所改变。
注意:不管我们给函数 bind
几次,fn
中的 this
永远由第一次 bind
决定
call 、apply 都是用于改变函数运行时的上下文而存在的,换句话说就是改变函数体内的 this 指向。
// call 接受的是参数列表
func.call(this, arg1, arg2);
// apply 接受的是 数组
func.apply(this, [arg1, arg2]);
call比apply的性能要好,平常可以多用call, call传入参数的格式正是内部所需要的格式
尤其是es6 引入了 Spread operator (延展操作符) 后,即使参数是数组,可以使用 call
let params = [1,2,3,4]
xx.call(obj, ...params)
bind() 方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
实现一个 bind 方法
if (!Function.prototype.bind) {
Function.prototype.bind = function (...args1) {
var self = this, // 保存原函数
context = [].shift.call(args1), // 保存需要绑定的this上下文,第一个参数时上下文
args = [].slice.call(args1); // 剩余的参数转为数组
return function (...args2) { // 返回一个新函数
self.apply(context,[].concat.call(args1, [].slice.call(args2)));
}
}
}
可以把 Promise 看成一个状态机。初始是 pending
状态,可以通过函数 resolve
和 reject
,将状态转变为 resolved
或者 rejected
状态,状态一旦改变就不能再次变化。
then
函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending
状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then
调用就失去意义了。
对于 then
来说,本质上可以把它看成是 flatMap
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
// 用于保存 then 中的回调,只有当 promise
// 状态为 pending 时才会缓存,并且每个实例至多缓存一个
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
_this.resolve = function (value) {
if (value instanceof MyPromise) {
// 如果 value 是个 Promise,递归执行
return value.then(_this.resolve, _this.reject)
}
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = RESOLVED;
_this.value = value;
_this.resolvedCallbacks.forEach(cb => cb());
}
})
};
_this.reject = function (reason) {
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.rejectedCallbacks.forEach(cb => cb());
}
})
}
// 用于解决以下问题
// new Promise(() => throw Error('error))
try {
fn(_this.resolve, _this.reject);
} catch (e) {
_this.reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this;
// 规范 2.2.7,then 必须返回一个新的 promise
var promise2;
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传
// Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;
if (self.currentState === RESOLVED) {
return (promise2 = new MyPromise(function (resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function () {
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === REJECTED) {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
// 异步执行onRejected
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === PENDING) {
return (promise2 = new MyPromise(function (resolve, reject) {
self.resolvedCallbacks.push(function () {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.rejectedCallbacks.push(function () {
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"));
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(function (value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false;
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then;
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
y => {
if (called) return;
called = true;
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// 规范 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x);
}
}
JavaScript 有 7 种语言类型
7种语言类型中,除了 Object
其他都属于 基本类型,基本类型保存在栈内存上。
Object 类型
、Array 类型
、Date 类型
、RegExp 类型
、Function 类型
等属于引用类型,引用类型保存在堆内存上。
typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。
typeof
可以用于判定除了 null
的基本类型,null 会被识别为 object
typeof
返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 6 种:number
、boolean
、string
、object
、undefined
、function
typeof
判定引用对象时,除了 函数,其他都会被识别为 object
instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
toString 是 Object 原型对象上的方法,使用 call 来调用该方法会返回调用者的类型字符串,格式为 [object,xxx],xxx 是调用者的数据类型,包括:String、Number、Boolean、Undefined、Null、Function、Date、Array、RegExp、Error、HTMLDocument 等, 基本上,所有的数据类型都可以通过这个方法获取到。
Object.prototype.toString.call(XXX)
定义:函数 A 内部包含函数 B,函数 B 可以访问到 函数 A 的变量,那么 函数B 就是闭包。
闭包的意义,就是让我们可以间接访问函数内部的变量。
例如
for(var i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000);
}
返回结果是 11 11 11 11 11
修改上面代码使其符合预期:
// 块级作用域
// 1.1
for(let i = 0; i <= 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000)
}
// 1.2
for (var i = 0; i <= 5; i++) {
let _i = i;
setTimeout(() => {
cosole.log(_i);
}, _i * 1000)
}
// 闭包
// 2.1
for(var i = 0; i <= 5; i++) {
;(i => {
setTimeout(() => {
console.log(i);
}, i *1000);
})(i);
}
// 3 setTimeout 函数的第三的参数,会作为回调函数的第一个参数传入
// 3.1
for (var i = 0; i < 5; i++) {
setTimeout(console.log, i * 1000, i)
}
// 3.2
for (var i = 0; i <= 5; i++) {
setTimeout(console.log.bind(Object.create(null), i), i * 1000)
}
let count = (function () {
let i = 1;
return function () {
return i++;
};
})();
//Q:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
原型链继承
function Parent () {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()) // kevin
缺点:
构造函数继承
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
Parent.call(this);
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
优点:即上面的缺点。
缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。
组合继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
原型式继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
ES6 Class 继承
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
getName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super();
this.age = age;
}
getAge() {
console.log(this.age);
}
}
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]