参考:
https://github.com/mqyqingfeng/Blog深入系列
https://www.bilibili.com/video/BV14s411E7qf
1.每个函数都有2条属性,显式原型属性prototype和隐式原型属性—proto—
2.函数的实例对象f1,f2,01,02与函数的原型对象Foo.prototype,Function.prototype(new出来的object对象)都只有隐式原型属性—proto—,它们的—proto—指向new它们的函数的原型对象。即:对象都有—proto—
3.值得一提的是Object.prototype不是new出来的,它是最初始的,object.prototype里面保存的是String等方法。object.prototype.-proto-指向的是null,object.prototype是原型链的尽头.无法再往上查找了。
//instanceof表示前者能不能顺着__proto__找到后者的prototype
console.log(Foo.prototype instanceof Object) //true
console.log(Fuction.prototype instanceof Object) //true
console.log(Object.prototype instanceof Object) //false
4.所有函数:Object(),Function(),function Foo(),的**-proto-指向的都是new它们的Function函数的原型对象**,Function是由自己产生自己的,所以Function.prototype===Function.—proto—
5.constructor:构造器
位置:存在于构造函数的prototype上,系统为我们自己添加的
作用:让实例对象能够通过—proto—.consructor找到它的构造函数
1.如果使用Person.prototype.getName=function或者Person.prototype.a=3这样的方式,系统会帮我们创建Person的原型对象,然后执行Person.prototype.constructor=Person
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('aa')
console.log(p.constructor===Person) //true
// p没有constructor,会去p.__proto__上找constructor
2.如果使用Person.prototype={}的方式创建,会先执行{}=new Object(), Obeject.prototype.constructor=Object,然后把{}的地址赋给Person.prototype
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype = {
sayename: function() {}
}
var p = new Person('aa')
//p没有constructor
console.log(p.hasOwnProperty('constructor')) //false
//通过p.__proto__找到{sayname:f},也没有constructor
console.log(p.__proto__.hasOwnProperty('constructor')) //false
//通过p.__proto__.__proto__找到constructor。它指向Object
console.log(p.__proto__.__proto__.hasOwnProperty('constructor'))//true
console.log(p.constructor===Object) //true
//如果想p.constructor指向Person
//p.constructor=Person
//console.log(p.constructor===Person) //true
我们一般将构造函数的方法保存在原型链,属性保存在实例对象。因为方法是通用的,每个对象会有不同的属性。
对于下列代码期待输出的是实例对象 fn1{test1:f} 结果是:Fn{test1:f}
function Fn(name){
this.test1=function(){
console.log('test1')
}
console.log(this)
}
var fn1 = new Fn() //Fn{test1:f}
原因:控制台中使用Fn{test1:f}来表示实例对象,使用函数来表示构造函数
function A(){
}
A.prototype.n=1
var b=new A()
A.prototype={ //这里改变了地址,指向了另一个对象
n:2,
m:3
}
var c=new A()
console.log(b.n,b.m,c.n,c.m)//1 undifine 2 3
function F(){
}
Object.prototype.a=function(){
console.log('a()')
}
Function.prototype.b=function(){
console.log('b()')
}
var f=new F()
F.a() //a()
F.b() //b()
f.a() //a()
f.b() //f.b is not a function
//F是Function new出来的,Function.prototype是Object new出来的
//f是F new出来的,F.prototype是Object new出来的
1.原型对象:原型对象的—proto—都指向Object.prototype
2.实例对象:谁new它就指向谁的prototype
1.函数的—proto—:都指向Function.prototype,包括Function本身
2**.函数的prototype**:都是通过Object new出来的原型对象。
全局上下文中,变量对象就是window
函数上下文中,用活动对象(activation object, AO)来表示变量对象。活动对象是在进入函数上下文时刻才被创建的,所以成为活动对象。
变量对象会包括:
1.函数的所有形参 (如果是函数上下文)
2.函数声明
3.变量声明
例如:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
在进入执行上下文后,这时候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
还是上面的例子,当代码执行完后,这时候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
1.在执行全局代码前会创建全局执行上下文,压入栈,
2.然后先将变量提升赋为undefine
3.将函数添加到window上
4.将this绑定为window
5.逐步执行全局代码
1.全局代码在执行到函数时,会添加一个函数执行上下文,将上下文压入栈
2.将参数赋值给实参,添加为执行上下文的属性
3.将参数添加给argument,添加argument为执行上下文的属性
4.变量提升,添加为执行上下文的属性
5.将函数里的函数添加为执行上下文里的方法
6.this绑定为调用函数的对象,
7.逐步执行函数体代码
1.因为函数执行上下文是调用函数就进栈,执行完就释放该区域的栈空间,所以函数里的变量是不能保存的
console.log('globle begin '+i)
var i=1
foo(1);
function foo(i){
if(i==4){
return
}
console.log('foo()begin'+i)
foo(i+1)
console.log('foo() end'+i)
}
console.log('globle end'+i)
//输出结果:
//globle begin undefined
//foo()begin1
//foo()begin2
//foo()begin3
//foo() end3
//foo() end2
//foo() end1
//globle end1
function a(){
}
var a;
console.log(typeof a)//function
//挂载函数->变量提升(重名不干扰),重名的话->绑定this->运行代码
if(!(b in window)){
var b=1
}
console.log(b)//undefined
//没有调用函数就没有新的执行上下文,
//var会在最开始就变量提升
var c=1
function c(c){
console.log(c)
var c=3
}
c(2)
//c is not a function
//执行顺序 var c=undefined,c=function,c=1,c(2)
https://github.com/mqyqingfeng/Blog/issues/8
函数的作用域在函数定义的时候就决定了
函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
举个例子:
function foo() {
function bar() {
...
}
}
函数创建时,各自的[[scope]]为:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
执行过程如下:
1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
3.checkscope 函数并不立刻执行,开始做准备工作
第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
}
4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5.第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
//作用域链在一开始就确定了,找不到某个元素只会去它的上一级找
var x=10
function fn(){
console.log(x)
}
function show(){
var x=20
fn()
}
show() //10
//这里只有一个全局作用域和一个函数作用域,函数作用域没找到就去全局作用域找
var fn=function(){
console.log(fn)
}
fn() //f(){ console.log(fn) }
//这里只有一个全局作用域和一个函数作用域,函数作用域没找到就去全局作用域找
var obj={
fn2:function(){
console.log(fn2)
}
}
obj.fn2() //fn2 is not define
1.通过变量名查找:会顺着作用域链查找到window,如果window也没有,就顺着原型链去查找window.prototype和Object.prototype
2.通过 . 或者[]查找: 顺着原型链查找
MDN 对闭包的定义为:
闭包是指那些能够访问自由变量的函数。
自由变量是指在函数中使用的,但既不是函数参数也不是函数局部变量的变量。
接下来,看这道刷题必刷,面试必考的闭包题:
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
答案是都是 3,让我们分析一下原因:
当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
globalContext = {
VO: {
data: [...],
i: 3
}
}
当执行 data[0] 函数的时候,data[0] 函数的作用域链为:
data[0]Context = {
Scope: [AO, globalContext.VO]
}
data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。data[1] 和 data[2] 是一样的道理。
所以让我们改成闭包看看:
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
data[0]();
data[1]();
data[2]();
当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
globalContext = {
VO: {
data: [...],
i: 3
}
}
跟没改之前一模一样。
当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:
data[0]Context = {
Scope: [AO, 匿名函数Context.AO globalContext.VO]
}
匿名函数执行上下文的AO为:
匿名函数Context = {
AO: {
arguments: {
0: 0,
length: 1
},
i: 0
}
}
data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。
参数传递复制的是值,如果是变量复制的就是变量本身,如果是对象复制的是对象的引用
//obj=0x123
var obj = {
value: 1
};
function foo(o) { //0=0x123
o.value = 2;
o.a=3
console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
console.log(obj.a) //3
//obj=0x123
var obj = {
value: 1
};
function foo(o) { //0=0x123
o = 2; // 0=2
console.log(o); //2
}
foo(obj);
//o复制的是obj的地址,o=0x123,obj=0x123,o=2不会印象obj
console.log(obj.value) // 1
this指向调用它的对象,如果没有则指向window
function Sub(){
this.subprop='Sub prop'
}
Sub.prototype.showSub=function(){
console.log(this.subprop)
}
var sub =new Sub()
sub.showSub() //'Sub prop' 对象调用showSub,所以this是sub
特殊情况(可以不了解):
详细请看:https://github.com/mqyqingfeng/Blog/issues/7
//"use strict"
//如果开启严格模式那么最下面的三段输出都会报错,非严格模式下this为undefined的时候会被转化为全局对象,所以我们可以认为this指向调用它的对象,如果没有则指向window
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
console.log(foo.bar()); //2
console.log((foo.bar)());//2
console.log((foo.bar = foo.bar)());//1
console.log((false || foo.bar)());//1
console.log((foo.bar, foo.bar)());//1
**函数 **使用指定的this和指定的参数 来调用
如:bar.call(foo) 简单说就是 在foo里面调用bar函数
call接收的是连续的参数,参数可以是任意的类型,包括数组
apply接收的是数组
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn
Function.prototype.call2 = function(context) {
//console.log(this) // f bar()
// 首先要获取调用call的函数,用this获取(谁调用,this就指向谁)
context.fn = this;
context.fn();
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
解决call函数的参数,如bar.call(foo,‘kevin’,18)
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push(`${arguments[i]}`);
}
//console.log(args)
context.fn(...args)//...取出所有可以遍历的属性,拷贝到参数上
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'kevin', 18);
// kevin
// 18
// 1
解决函数的返回值与传入参数为null的问题
1.原版bar.call(null) 会在window执行bar
2.原版bar.call(foo) bar如果不是函数,会报错
3.原版bar.call(foo) foo如果是数字或字符串,会转化为对象
4.原版如果bar函数有返回值,那么bar.call(foo)也会有返回值
// 第三版
Function.prototype.call2 = function (context) {
// 判断调用对象
if (context === null || context === undefined) {
context = window;
} else {
context = Object(context) || context;//传入的是数字或者别的
}
context.fn = this;
var args = [...arguments].slice(1)
let result = context.fn(...args);
delete context.fn
return result;
}
测试:
var value = 2;
var obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.call2(null); // 2
console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }
apply只是参数和call不同
Function.prototype.apply2 = function(context) {
// 判断调用对象
if (context === null || context === undefined) {
context = window;
} else {
context = Object(context) || context;//传入的是数字或者别的
}
context.fn = this;
let result = null;
// 调用方法
if (arguments[1]){
result=context.fn(...arguments[1])
} else {
result = context.fn();
}
delete context.fn;
return result;
};
1.传入参数:bind的时候可以传参,对bind返回的函数也可以传参,
如下面例子:函数需要传 name 和 age 两个参数,可以在 bind 的时候,只传一个 name,在执行返回的函数的时候,再传另一个参数 age!
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
可以使用上面的例子验证!
Function.prototype.myBind = function(context) {
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
//args是mybind里面的参数,bindArgs是往bind返回的函数里传入的参数
let bindArgs=[...arguments].slice(0)
//使用apply在context上执行函数bar,返回函数的返回值
return fn.apply(context,args.concat(bindArgs))
};
};
原本使用bind返回的函数可以当成构造函数!这种情况下bind传入的this会失效,但是传入的参数依然生效。
举个例子:
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,因为new的时候 this 指向了 obj,所以this.habit生效,this.value返回undefined
// 第二版
Function.prototype.bind2 = function (context) {
var fn = this;
var args = [...arguments].slice(1);
var fBound = function () {
let bindArgs=[...arguments].slice(0)
// 1.当作为构造函数时,this 指向实例,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值,如上面例子中的habit
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return fn.apply(
this instanceof fBound ? this:context ,
args.concat (bindArgs));
}
// 修改返回函数的 prototype ,实例就可以继承绑定函数的原型中的值
fBound.prototype = this.prototype;
return fBound;
}
就是这句:fBound.prototype = this.prototype
Function.prototype.bind2 = function (context) {
var fn = this
var args = [...arguments].slice(1)
var fBound = function () {
var bindArgs = [...arguments].slice(0)
return fn.apply(
this instanceof fBound ? this:context ,
args.concat (bindArgs))
}
fBound.prototype = Object.create(this.prototype)
return fBound
}
参考:https://github.com/mqyqingfeng/Blog/issues/13
new是关键字,我们无法创造出跟它一样的写法:
使用 new : var person = new Otaku(……);
使用 objectFactory:var person = objectFactory(Otaku, ……)
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
//arguments不是数组,要想使用数组的shift方法就得用call,
//这时候Constructor等于arguments第一个参数也就是传入的构造函数,
//shift影响原数组,arguments已经少了第一个参数了
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments); //改变构造函数的this到obj
//apply后面接收的是数组,call接收的是一连串参数,所以这里用apply
return obj;
};
//不足:第一版并没有考虑构造函数有返回值的情况
//如果构造函数返回的是对象,实例 person 中只能访问返回的对象中的属性
//如果返回的是基本类型,如:return 'haha',相当于没有返回值
function Otaku (name, age) {
this.strength = 60;
this.age = age;
return {
name: name,
habit: 'Games'
}
}
var person = new Otaku('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
//3.第二版代码(最终版)
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);//如果构造函数有返回值,用ret接收
return typeof ret === 'object' ? ret : obj;//ret如果是引用数据类型,返回ret,否则返回obj
};
参考:https://github.com/axuebin/articles/issues/20
深浅拷贝都是对于引用数据类型而言的,浅拷贝就只是复制对象的引用,深拷贝才是真正地对对象的拷贝
const originArray = [1,2,3,4,5];
const originObj = {a:'a',c:[1,2,3]};
const cloneArray = originArray; // [1,2,3,4,5]
const cloneObj = originObj;// {a:'a',c:Array[3]}
cloneArray.push(6);
cloneObj.a = {aa:'aa'};
console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]
console.log(cloneObj); // {a:{aa:'aa'},c:Array[3]}
console.log(originObj); // {a:{aa:'aa'},c:Array[3]}
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa'; cloneObj.c = [1,1,1]; cloneObj.d.dd = 'doubled';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
//使用这种方法进行深拷贝 undefined、function、symbol 会在转换过程中被忽略
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}
//核心就是对每一层的数据都实现一次 创建对象->对象赋值 的操作
//我们必须先创建新的{}或[],然后给他们用 = 给他们进行浅拷贝赋值
//递归使得每一层的拷贝都是深拷贝
function deepClone(source){
// 判断复制的目标是数组还是对象
const targetObj = source.constructor === Array ? [] : {};
for(let keys in source){ //for in获取的是键名,且是字符串形式
if(source.hasOwnProperty(keys)){
// source[keys]表示不为null,后面表示:值是如果是对象,就递归一下
if(source[keys] && typeof source[keys] === 'object'){
targetObj[keys] = deepClone(source[keys]);
}
else{
// 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
测试:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false
console.log(cloneObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
//4.1 concat(num1,num2) 返回拼接后的数组,不影响原数组
//concat进行的是首层深拷贝,第一层与原数组互不影响,后面的层会影响
const originArray = [1,[1,2,3],{a:1}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray.push(3)
//这里之所以用Json进行深拷贝,是因为如果console.log(cloneArray),console.log记录的是引用,控制台输出的时候他输出的是代码全部执行完后的cloneArray
console.log(JSON.parse(JSON.stringify(cloneArray))) //[1,[1,2,3],{a:1},3]
console.log(JSON.parse(JSON.stringify(originArray))) //[1,[1,2,3],{a:1}]
cloneArray[1].push(4);
cloneArray[2].a = 2;
console.log(originArray); // [1,[1,2,3,4],{a:2}]
//4.2 slice 用于读取下标start(包含)到end(不包含)的数组,不改变原数组
//slice与concat一样,进行首层深拷贝
//4.3 扩展运算符...
//扩展运算符进行的是首层深拷贝
const originArray = [1,2,3,4,5,[6,7,8]];
const originObj = {a:1,b:{bb:1}};
const cloneArray = [...originArray];
cloneArray[0] = 0;
cloneArray[5].push(9);
console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]
const cloneObj = {...originObj};
cloneObj.a = 2;
cloneObj.b.bb = 2;
console.log(originObj); // {a:1,b:{bb:2}}
//4.4 Object.assign
//Object.assign() 是首层深拷贝
function shallowClone(source) {
const targetObj = source.constructor === Array ? [] : {};
for (let keys in source) { // 遍历目标
if (source.hasOwnProperty(keys)) {
targetObj[keys] = source[keys];
}
}
return targetObj;
}
JSON.stringify
实现的是深拷贝,但是对目标对象有要求;在变量不确定时使用
var obj=new Object()
obj.name='zhangsan'
如果创建多个对象会有大量重复的代码
obj={
name:'zhangsan'
}
工厂模式就是通过函数返回对象
缺点:无法区别使用createPerson创建出来的对象与createDog创建出来的对象有什么区别,都是object,跟createPerson函数没有关系.它只是简单的封装了复用代码,而没有建立起对象和类型间的关系
function createPerson(name,age){
obj={//对象解构赋值
name,
age
}
return obj
}
var p1=createPerson('zhangsan',11)
var p2=createPerson('lisi',11)
console.log(p1,p2)
使用new创建
构造函数模式相对于工厂模式的优点是:所创建的对象和构造函数建立起了联系,因此可以通过原型来识别对象的类型。但这种创建方式为不同的对象跟别创建了相同的方法,如:p1和p2都添加了fun1方法,浪费了内存空间
function Person(name,age){
this.name=name
this.age=age
this.fun1=function(){
console.log(3)
}
}
var p1=new Person('zhangsan',11)
var p2=new Person('lisi',12)
这种方式能解决4里的浪费内存的问题,但同时也接收不了参数了
这是最常见的方法,但是因为使用了两种不同的模式,所以对于代码的封装性不够好
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.fun1=function(){
console.log(3)
}
var p1=new Person('zhangsan',11)
var p2=new Person('lisi',12)
console.log(p1)
console.log(p2)
将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。
属性存在原型上,修改引用类型的数据时容易混乱。而且子类型实例化的时候不能往父类型里面传参。
function Father(){
this.a='Father a'
}
Father.prototype.showFather=function(){
console.log(this.a)
}
function Sun(){
this.b='Sun b'
}
//关键:Sun的prototype指向Father的实例,不写这句会默认指向object的实例
Sun.prototype=new Father()
//让protorype里面的构造器指向Sun,不然会沿用之前的最终会指向Father
Sun.prototype.constructor=Sun
Sun.prototype.showSun=function(){
console.log(this.b)
}
var s=new Sun()
s.showFather()//Father a
s.showSun()//Sun b
缺点:会重复创建函数,相同的函数没有得到复用
//实际就是用call方法简化了代码量,实际还是那么多代码
function Person(name,age){
this.name=name
this.age=age
}
function Student(name,age,sex){
//Person构造构造函数也是函数!用这里的this调用Person函数
Person.call(this,name,age)
this.sex=sex
}
使用原型继承和构造函数继承相结合。
理解:变量是不同的,需要单独指定,但可以用call减少代码量。方法可以是相同的,使用原型链
缺点:由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
function Father(age,name){
this.age=age
this.name=name
}
Father.prototype.showFather=function(){ }
function Sun(age,name,sex){
Father.call(this,age,name)
this.sex=sex
}
Sun.prototype=new Father()//用Father生成实例对象作为Sun的原型对象
Sun.prototype.constructor=Sun//让protorype里面的构造器指向Sun
Sun.prototype.showSun=function(){}
原型式继承的主要思路是可以基于已有的对象创建新的对象。
Object.create() 方法就是下面代码的规范化。
function object(o){
function F(){};
F.prototype = o;
return new F();
}
与原型链方式相同,缺点是修改引用类型的数据时容易混乱。而且子类型实例化的时候不能往父类型里面传参。
寄生式继承的思路是,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象,最后返回这个对象.
缺点是没有办法实现函数的复用。
function createAnother(original) {
let clone = Object.create(original);
clone.sayHi = function () {
console.log("hi");
};
return clone;
}
综合4、5对3进行改造,得到的6是最理想的继承方式
寄生式组合继承与组合继承不同的地方主要是:在继承原型时,我们继承的不是超类的实例对象,而是原型对象是超类原型对象的一个实例对象,这样就解决了基类的原型对象中增添了不必要的超类的实例对象中的所有属性的问题。
简单说就是:因为new的时候会运行父亲里的代码,我们不让Sun.prototype=new Father(),我们让Sun.prototype=Object.create(Father.prototype)
function inheritPrototype(subType, superType) {
// 创建对象 创建原型对象是超类原型对象的 一个实例对象
let prototype = Object.create(superType.prototype);
// 修改prototype的constructor为subType
prototype.constructor = subType;
// 赋值
subType.prototype = prototype;
}
function Father(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Father.prototype.sayName = function () {
console.log(this.name);
};
function Son(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(Son, Father);
SubType.prototype.sayAge = function () {
console.log(this.age);
};
详解:https://juejin.cn/post/6844903512845860872
主程序执行完后,就一直在等待任务队列的任务,settimeout等会直接拿出来执行,onclick等会一直在队列中,等你触发相应的事件后就会执行
settime理解:遇到settime声明,就将函数放到event table,过了指定的时间就将函数放入event queue
,所以等待时间大于主程序运行时间它几乎是准确的,而如果它放入了event queue中后主程序还没运行完,那么要等待主程序运行完才能运行event queue里的内容。
setinterval理解:每隔一段时间将要执行的函数加入到event queue
settimeout 和setintelout属于进入宏任务的event queue
new promise 立即执行,多层new也是立即执行
promise.then和process.nextTick进入微任务的event queue
每执行完一个宏任务会去执行所有的微任务,最开始window上的代码执行完也算执行完了一段宏任务,执行完setTimeout里面的回调也算是执行完了一段宏任务.
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
//1,7,6,8,2,4,3,5,9,11,10,12
使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱
使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
async 函数 的方式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。