编译原理 词法作用域 this 对象拷贝 不变性 存在性 遍历for of 类(混合对象) 混入 原型 反射 对象关联 行为委托 对与对象
1、作用域形成的原因:变量存储在哪?如何找到?
2、编译原理:编译:
分词-解析:AST抽象语法树-代码生成:将AST转换为可执行代码。
js的编译大部分发生在执行前几微秒,优化有JIT,延迟编译甚至实施重编译。
1.2.RHS 获取变量的源值 LHS 找到变量的容器(=号和赋值),从而可以对其赋值
1.3 作用域嵌套
作用域:是根据名称查找变量的一套规则。
如果找不到RHS会报错ReferenceError。而LHS查询时,如果在全局作用域找不到,则会创建一个,在非严格模式下。
RHS另外的异常:对非函数进行函数调用;对引用null、undefined的属性,报TypeError异常。
欺骗词法:eval
with:可以将一个没有或者有多个属性的对象处理为一个完全隔离的词法作用域。
2.3函数作用域和块作用域,规避冲突。
基于模块的函数并不是一个能被稳定识别的模式,只有在运行时才会被考虑进来。ES6模块API更加稳定,不会在运行时改变。
闭包:当函数可以技术并访问所在的词法作用域,即使函数时在当前的词法作用域之外执行,这时就产生了闭包。
什么是词法作用域?是一套关于引擎如何寻找变量以及会在何处找到变量的规则。
动态作用域是让作用域作为一个在运行时被动态确定的形式。作用域链基于调用栈。
到底this是什么?
绑定规则:
默认绑定、隐士绑定(最后一层影响调用位置,会在传参和引用时发生隐士丢失)、显示绑定、new绑定
function foo(){
console.log(this.a);
}
var obj={ a:2 };
var bar=function(){ foo.call(obj); }
bar();
setTimeout(bar,100);
bar.call(window);
硬绑定场景,创建一个包裹函数:
function foo(something){
console.log(this.a,something);
return this.a + something;
}
var obj={ a:2 };
var bar=function(){
return foo.apply(obj,arguments);
};
var b=bar(3);
console.log(b);
创建一个i可以重复使用的辅助函数:
function foo(something){
console.log(this.a,something);
return this.a+something;
}
function bind(fn,obj){
return function(){
return fn.apply(obj,arguments);
};
}
var obj={ a:2 };
var bar=bind(foo,obj);
var b=bar(3);
console.log(b);
apply实现bind
function foo(s){
console.log(this,s);
return this.a+s;
}
var obj={a:2};
Function.prototype.bind=function(obj){
var self=this;
console.log("bind",this,arguments);
var args=Array.from(arguments);
return function(){
console.log("return",this,args,arguments);
return self.apply(obj,args.slice(1));
}
}
var bar=foo.bind(obj);
bar(3);
测试测试绑定和显示的优先级?
function foo(){
console.log(this.a)
}
var obj1={a:2,foo:foo};
var obj2={a:3,foo:foo};
obj1.foo();
obj2.foo();
obj1.foo.call(obj2);
obj2.foo.call(obj1);
new和隐式绑定?
function foo(something){
this.a=something;
}
var obj1={};
var bar=foo.bind(obj1);
bar(2);
console.log(obj1.a);
var baz=new bar(3);
console.log(baz.a);
console.log(obj1.a);
MDN的bind实现:
if(!Function.prototype.bind){
Function.prototype.bind=function(oThis){
if(typeof this !== "function"){
throw new TypeError(
"Function.prototype.bind - what is trying " +
"to be bound is not callable"
);
}
var aArgs=Array.prototype.slice.call(arguments,1),
fToBind=this,
fNOP=function(){}
fBound=function(){
return fToBind.apply(
(
this instanceof fNOP &&
oThis ? this : oThis
),
aArgs.concat(
Array.prototype.slice.call(arguments)
);
)
}
fNOP.prototype=this.prototype;
fBound.prototype=new fNOP();
return fBound;
}
}
bind的功能之一可以把除了第一个参数之外的其他参数都传给下层函数。
function foo(p1,p2){ this.val=p1+p2; }
var bar=foo.bind(null,"p1");
var baz=new bar("p2");
baz.val;
更安全的this
function foo(a,b){
console.log("a:"+a+",b:"+b);
}
var Ø=Object.create(null);
foo.apply(Ø,[2,3]);
//使用bind进行柯里化
var bar=foo.bind(Ø,2);
bar(3);
间接引用
function foo(){
console.log(this.a);
}
var a=2;
var o={a:3,foo:foo};
var p={a:4};
o.foo();
(p.foo=o.foo)();
软绑定:
if(!Function.prototype.softBind){
Function.prototype.softBind=function(obj){
var fn=this;
//捕获所有curried参数
var curried=[].slice.call(arguments,1);
var bound=function(){
return fn.apply(
(!this || this === (window || global))?
obj:this,
curried.concat.apply(curried,arguments)
);
};
bound.prototype=Object.create(fn.prototype);
return bound;
};
}
该方法保留了隐式绑定和显示绑定修改this的能力。
function foo(){
console.log("name: "+this.name);
}
var obj={name:"obj"},
obj2={name:"obj2"},
obj3={name:"obj3"};
var fooOBJ=foo.softBind(obj);
fooOBJ();//obj
obj2.foo=foo.softBind(obj);
obj2.foo();//obj2
ooOBJ.call(obj3);//obj3
setTimeout(obj2.foo,10);//obj
this词法:
function foo(){
return (a)=>{ console.log(this.a); }
}
var obj1={a:2};
var obj2={a:3};
var bar=foo.call(obj1);
bar.call(obj2);
箭头函数的绑定无法被修改。
function foo(){
setTimeout(()=>{ console.log(this.a) },100);
}
var obj={a:2};
foo.call(obj);
内置对象 String Number Boolean Object Function Array Date RegExp Error
function anotherFunction(){}
var anotherObject={c:true};
var anotherArray=[];
var myObject={
a:2,
b:anotherObject,
c:anotherArray,
d:anotherFunction
}
anotherArray.push(anotherObject,myObject);
JSON安全的复制方法:
var newObj=JSON.parse(JSON.stringify(someObj));
浅复制ES6,Object.assign()
var newObj=Object.assign({},myObject);
newObj.a;
newObj.b===anotherObject;
newObj.c===anotherArray;
newObj.d===anotherFunction;
属性描述符
var myObject={a:2};
Object.getOwnPropertyDescriptor(myObject,"a");
{value: 2, writable: true, enumerable: true, configurable: true}
var myObject={};
Object.defineProperty(myObject,"a",{
value:2,
writable:true,
configurable:true,
enumerable:true
});
muObject.a;
Enumerable设置成false 不会出现在枚举中for…in
不变性
//对象常量
var myObject={};
Object.defineProperty(myObject,"FAVORITE_NUMBER",{
value:42,
writable:false,
configurable:false
})
//禁止扩展
var myObject={a:2};
Object.preventExtensions(myObject);
myObject.b=3;
muObject.b;//undefined
//密封
Object.seal(Obj)
//冻结
Object。freeze()//最高级别的不可变性 并可以深度冻结
[[Put]]/[[Get]]
var myObject={
get a(){ return 2; }
};
Object.defineProperty(
myObject,
"b",
{
get:function(){ return this.a*2 },
enumerable:true
}
);
console.log(myObject.a,
myObject.b);
存在性
var myObject={a:2};
console.log(
("a" in myObject),//in会检查是否在myObject对象中
("b" in myObject),
myObject.hasOwnProperty("a"),
myObject.hasOwnProperty("b"));
//true false true false
如果对象没有链接到Object.prototype上,可以使用Object.prototype.hasOwnProperty.call(obj,“a”);
枚举性
var myObject={};
Object.defineProperty(
myObject,
"a",
{enumerable:true,value:2}
);
Object.defineProperty(
myObject,
"b",
{enumerable:false,value:3}
);
console.log(myObject.b,("b" in myObject),myObject.hasOwnProperty("b"));
for(var k in myObject){
console.log(k,myObject[k]);
}
VM961:12 3 true true
VM961:14 a 2
另一种方式可枚举
var myObject={};
Object.defineProperty(
myObject,
"a",
{ enumerable:true, value:2 }
);
Object.defineProperty(
myObject,
"b",
{enumerable:false,value:3}
);
console.log(
myObject.propertyIsEnumerable("a"),
myObject.propertyIsEnumerable("b"),
Object.keys(myObject),
Object.getOwnPropertyNames(myObject));
VM3093:12 true false ["a"] (2) ["a", "b"]
目前没有方法获取in操作符使用的属性列表,可以递归遍历对象的整条Prototype保存每一层使用Object.keys
遍历 for of
var myArray=[1,2,3];
var it=myArray[Symbol.iterator]();
console.log(
it.next(),it.next(),it.next(),it.next());
可以给对象定义@@iterator
var myObject={a:2,b:3};
Object.defineProperty(myObject,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function(){
var o=this;
var idx=0;
var ks=Object.keys(o);
return {
next:function(){
return {
value:o[ks[idx++]],
done:(idx > ks.length)
};
}
};
}
});
var it = myObject[Symbol.iterator]();
console.log(
it.next(),
it.next(),
it.next());
for(var v of myObject){
console.log(v);
}
// {value: 2, done: false} {value: 3, done: false} {value: undefined, done: true}
// 2
// 3
也可以直接在定义对象事声明var myObject={a:2,[Symbol.iterator]:function(){}}
无限迭代器
var randoms={
[Symbol.iterator]:function(){
return {
next:function(){
return { value:Math.random() };
}
};
}
};
var randoms_pool=[];
for(var n of randoms){
randoms_pool.push(n);
if(randoms_pool.length===100)break;
}
类/继承是什么?类/继承描述了一种代码的组织结构形式,一种咋软件中对真实世界中问题领域的建模方法。
function mixin(sourceObj,targetObj){
for(var key in sourceObj){
if(!(key in targetObj)){
targetObj[key]=sourceObj[key];
}
}
return targetObj;
}
var Vehicle={
engines:1,
ignition:function(){
console.log("Turning on my engine.");
},
drive:function(){
this.ignition();
console.log("Steering and moving forward!");
}
};
var Car=mixin(Vehicle,{
wheels:4,
drive:function(){
Vehicle.drive.call(this);
console.log("Rolling on all" + this.wheels + " wheels!");
}
})
///
Car
{wheels: 4, engines: 1, drive: ƒ, ignition: ƒ}
drive: ƒ ()
engines: 1
ignition: ƒ ()
wheels: 4
__proto__: Object
混合复制
function mixin(sourceObj,targetObj){
for(var key in sourceObj){
targetObj[key]=sourceObj[key];
}
return targetObj;
}
var Vehicle={};
var Car=mixin(Vehicle,{});
mixin({
wheels:4,
drive:function(){}
},Car);
寄生继承
function Vehicle(){
this.engines=1;
}//传统js类
Vehicle.prototype.ignition=function(){
console.log("Turning on my engine.");
}
Vehicle.prototype.drive=function(){
this.ignition();
console.log("Steering and moving forward!");
};
//寄生类
function Car(){
//car是一个Vehicle类
var car=new Vehicle();
//接着对car进行定制
car.wheels=4;
//保存到Vehicle::drive()的特殊引用
var vehDrive=car.drive;
//重写Vehicle::drive()
car.drive=function(){
vehDrive.call(this);
console.log("Rolling on all " + this.wheels + " wheels!");
}
return car;
}
var myCar=new Car();
myCar.drive();
//
Turning on my engine.
Steering and moving forward!
Rolling on all 4 wheels!
隐式混入
var Something={
cool:function(){
this.greeting="Hello World";
this.count=this.count?this.count+1:1;
}
};
console.log(
Something.cool(),
Something.greeting,
Something.count,);
var Another={
cool:function(){
Something.cool.call(this);
}
};
console.log(
Another.cool(),
Another.greeting,
Another.count);
var anotherObject={a:2};
var myObject=Object.create(anotherObject);
myObject.a;
2
for…in 和 in都会查找原型链
var anotherObject={a:2};
var myObejct=Object.create(anotherObject);
for(var k in myObject){
console.log("found : " +k);
}
("a" in myObject);
VM20944:4 found : a
true
最终会链向Obejct.prototype
产生隐式屏蔽
var anotherObject={a:2};
var myObject=Object.create(anotherObject);
console.log(
anotherObject.a,
myObject.a,
anotherObject.hasOwnProperty("a"),
myObject.hasOwnProperty("a"));
myObject.a++;
console.log(
anotherObject.a,
myObject.a,
myObject.hasOwnProperty("a"));
VM1184:3 2 2 true false
VM1184:9 2 3 true//会调用[[put]]赋值
Foo.prototype是什么?最直接的解释,这个对象是在调用new Foo()时创建的,最后会被关联到这个Foo点prototype对象上。
function Foo(){}
var a=new Foo();
Object.getPrototypeOf(a)===Foo.prototype;
true
令人迷惑的“构造函数”
function Foo(){}
Foo.prototype.constructor === Foo;//true
var a=new Foo();
a.constructor === Foo;
//添加constructor
function Foo(){}
Foo.prototype={}
Object.defineProperty(Foo.prototype,"constructor",{
enumerable:false,
writable:true,
configurable:true,
value:Foo
})
原型继承(委托关系)
function Foo(name){ this.name=name; }
Foo.prototype.myName=function(){ return this.name; }
function Bar(name,label){
Foo.call(this,name);
this.label=label;
}
Bar.prototype=Object.create(Foo.prototype);
Bar.prototype.myLabel=function(){
return this.label;
};
var a=new Bar("a","obj a");
a.myName();
a.myLabel();
"obj a"
会有的一些问题:
1.Bar.prototype=Foo.prototype并不会创建一个关联到Bar.prototype的新对象,只是让Bar.prototype直接引用Foo.prototype对象,执行Bar
2.如果使用Object.create,缺点是需要创建一个新对象然后把旧对象抛弃掉,es6加入了Object.setPrototypeOf()
Bar.prototype=Object.create(Foo.prototype)
Object.setPrototypeOf(Bar.prototype,Foo.prototype)
检查实例的继承祖先(委托关系),内省(反射)
function Foo(){}
Foo.prototype.blah=123;
var a =new Foo();
console.log(a instanceof Foo);
VM307:4 true
instanceof 只能处理对象和函数之间的关系,如果想判断两个对象是否通过Prototype关联
通过类的角度使用instanceof判断两个对象的关系
function isRelatedTo(o1,o2){
function F(){}
F.prototype=o2;
return o1 instanceof F;
}
var a={};
var b=Object.create(a);
isRelatedTo(b,a);
true
另一种判断反射的方法Foo.prototype.isPrototypeOf(a)
判断在a的整条链中是否出现过Foo.prototype
ES5 Object.getPrototypeOf(a) === Foo.prototype;
浏览器支持非标准模式a.__proto__==Foo.prototype
__proto__大致实现
Object.defineProperty(Object.prototype,"__proto__",{
get:function(){
return Object.getPrototypeOf(this);
},
set:function(o){
Object.setPrototypeOf(this,o);
return o;
}
})
对象关联
var foo={
something:function(){ console.log("Tell me something good..."); }
}
var bar=Object.create(foo);
bar.something();
VM11247:2 Tell me something good...
Object.create会创建一个新对象关联到foo,避免new产生prototype和constructor
Object.create创建的对象无法进行委托,不受原型链干扰,适合存储数据
Object.create()的polyfill代码
if(!Object.create){
Object.create=function(o){
function F(){}
F.prototype=o;
return new F();
}
}
内部委托,增加代码可理解性
var anotherObject={
cool:function(){ console.log("cool!"); }
}
var myObject=Object.create(anotherObject);
myObject.doCool=function(){ this.cool(); }
myObject.doCool();
VM13488:2 cool!
行为委托
对象关联
Task={
setID:function(ID){this.id=ID;},
outputID:function(){console.log(this.id);}
};
//让XYZ委托Task
XYZ = Object.create(Task);
XYZ.prepareTask=function(ID,Label){
this.setID(ID);
this.label=Label;
};
XYZ.outputTaskDetails=function(){
this.outputID();
console.log(this.label);
};
XYZ.prepareTask(1,2);
尽量避免相同命名方法(多态)。this.setID仍然会绑定到XYZ,触发this的隐式绑定规则。
禁止互相委托
思维模型的比较
//面向对象(原型)风格
function Foo(who){
this.me=who;
}
Foo.prototype.identify=function(){
return "I am " + this.me;
}
function Bar(who){
Foo.call(this,who);
}
Bar.prototype=Object.create(Foo.prototype);
Bar.prototype.speak=function(){
alert("Hello, "+this.identify()+".");
}
var b1=new Bar("b1");
var b2=new Bar("b2");
//对象关联风格
Foo={
init:function(who){ this.me=who; },
identify:function(){ return "I am " + this.me; }
};
Bar=Object.create(Foo);
Bar.speak=function(){
alert("Hello, " + this.identify() + ".");
};
var b1=Object.create(Bar);
b1.init("b1");
var b2=Object.create(Bar);
b2.init("b2");
b2.speak();
b1.speak();
类与对象
function Widget(width,height){
this.width=width||50;
this.height=height||50;
this.$elem=null;
}
Widget.prototype.render=function($where){
if(this.$elem){
this.$elem.css({
width:this.width+"px",
height:this.height+"px"
}).appendTo($where);
}
};
//子类
function Button(width,height,label){
//调用super构造函数
Widget.call(this,width,height);
this.label=label||"Default";
this.$elem=$("
上面的代码出现了丑陋的多态
class Widget{
constructor(width,height){
this.width=width||50;
this.height=height||50;
this.$elem=null;
}
render($where){
if(this.$elem){
this.$elem.css({
width:this.width+"px",
height:this.height+"px"
}).appendTo($where);
}
}
}
class Button extends Widget{
constructor(width,height,label){
super(width,height);
this.label=label||"Default";
this.$elem=$("
委托控件对象
简洁的设计
var Widget={
init:function(width,height){
this.width=width||50;
this.heigh=height||50;
this.$elem=null;
},
insert:function($where){
if(this.$elem){
this.$elem.css({
width:this.width+"px",
height:this.height+"px"
}).appendTo($where);
}
}
};
var Button=Object.create(Widget);
LoginController.prototype=Object.create(Controller.prototype);
LoginController.prototype.getUser=function(){
return document.getElementById("login_username").value;
}
LoginController.prototype.getPassword=function(){
return document.getElementById("login_password").value;
};
LoginController.prototype.validateEntry=function(user,pw){
user=user || this.getUser();
pw=pw || this.getPassword;
if(!(user && pw)){
return this.failure("Please enter a username & password");
}
else if(user.length < 5){
return this.failure(" Password must be 5+ characters");
}
return true;
}
LoginController.prototype.failure=function(err){
Controller.prototype.failure.call(this,"Login invalid"+err)
}
//子類
function AuthController(login){
Controller.call(this);
this.login=login;
}
AurhController.prototype=Object.create(Controller.prototype);
AuthController.prototype.server=function(url,data){
return $.ajax({url:url,data:data})
}
AuthController.prototype.checkAuth=function(){
var user=this.login.getUser();
pw=this.login.getPassword();
if(this.login.validateEntry(user,pw)){
this.server("/check-auth",{ user:user,pw:pw })
.then(this.success.bind(this))
.fail(this.failure.bind(this))
}
};
//重寫基础的success
AuthController.prototype.success=function(){
Controller.prototype.success.call(this,"Authenticated");
}
AuthController.prototype.failure=function(err){
Controller.prototype.failure.call(this,"AuthFailed"+err);
}
var auth=new AuthController();
auth.checkAuth(new LoginControler());
自省 鸭子类型
“如果看起来像鸭子,叫起来像鸭子,那就一定是鸭子”
if(a1.something) a1.something();
class陷阱,使用的是 基于[[Prototype]]的实时委托;意外屏蔽问题(会因为id属性和方法屏蔽)