分词/词法分析
将字符串分解成代码块(词法单元) – var
a
=
2
分词 vs 词法分析
有状态的解析规则 – 词法分析
无状态的解析规则 – 分词
解析/语法分析
将词法单元数组变成抽象语法树
代码生成
AST 转换为可执行代码的过程称被称为代码生成
引擎
编译和执行JavaScript
编译器
语法分析及代码生成
作用域
变量的赋值操作会执行两个动作
作用域共有两种主要的工作模型。
是由你在写代码时将变量和块作用域写在哪里来决定的, 因此当词法分析器处理代码时会保持作用域
不变.
eval – 严格模式下,eval有它自己的作用域,所以严格模式下行不通.
setTimeout(..)/setInterval(..) 也有类似效果 – 所以不提倡使用第一个参数做为字符串.
with – 严格模式也被完全禁止了.
let 可以将变量绑定在块作用域
const 常量声明 且绑定在块作用域
内部函数(定义函数)和外部函数(定义函数所在的函数)之间的作用域的引用叫做闭包.
词法作用域是在写代码或者说定义时确定.
动态作用域是在运行时确定. – 从堆栈一层一层向上找.
利用词法作用域和闭包.解决this指向问题.
var obj = {
count: 0,
cool: function coolFn() {
var self = this;
if (self.count < 1) {
setTimeout( function timer(){
self.count++;
console.log( "awesome?" );
}, 100 );
}
}
};
obj.cool(); // 酷吧?
箭头函数放弃了普通this的绑定规则,将当前的词法作用域(调用SetTimeout的作用域,cool函数的作用域)覆盖了this本来的值.
var obj = {
count: 0,
cool: function coolFn() {
if (this.count < 1) {
setTimeout( () => { // 箭头函数是什么鬼东西?
this.count++;
console.log( "awesome?" );
}, 100 );
}
}
};
obj.cool(); // 很酷吧 ?
另一种方式
var obj = {
count: 0,
cool: function coolFn() {
if (this.count < 1) {
setTimeout( function timer(){
this.count++; // this 是安全的
// 因为 bind(..)
console.log( "more awesome" );
}.bind( this ), 100 ); // look, bind()!
}
}
}
xw.say.call(xh,"实验小学","六年级");
xw.say.apply(xh,["实验小学","六年级"]);
xw.say.bind(xh,"实验小学","六年级")();
xw.say.bind(xh)("实验小学","六年级");//bind还有其他功能 -- 绑定部分参数
function foo(p1,p2) {
this.val = p1 + p2;
} // 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = function() {
return foo.apply( obj, arguments ); // arguments对象是所有(非箭头)函数中都可用的局部变量
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
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 ); // 2 3
console.log( b ); // 5
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
使用new 调用函数
new > 显示绑定(硬绑定) > 隐式绑定 > 默认绑定
call apply bind使用null或者undefined会被忽略. – 使用默认绑定规则.
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
柯里化会用到绑定null
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
} // 把数组“展开” 成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
解决办法
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
} // 我们的 DMZ 空对象
var ø = Object.create( null );
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
间接引用
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2 返回的是foo而不是p.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;
};
}
简单基本类型(string、 boolean、 number、 null 和 undefined) 本身并不是对象。null 有时会被当作一种对象类型, 但是这其实只是语言本身的一个 bug, 即对 null 执行typeof null 时会返回字符串 “object”。实际上, null 本身是基本类型。
原理是这样的, 不同的对象在底层都表示为二进制, 在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0, 自然前三位也是 0, 所以执行 typeof 时会返回“object”。
内置函数并不是类或类型,但是JavaScript会自动装箱在必要的时候.
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
null 和 undefined 没有对应的构造形式, 它们只有文字形式。 相反, Date 只有构造, 没有文字形式
Object、 Array、 Function 和 RegExp(正则表达式) 来说, 无论使用文字形式还是构造形式, 它们都是对象, 不是字面量.
尽量使用字面量形式.
属性并不一定存在于对象内部.对象内部只存储属性名称.
var prefix = "foo";
var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
浅复制 – var newObj = Object.assign( {}, myObject );
深复制 – 没有保准
var myObject = {
a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true,
// enumerable: true,
// confgurable: true
// }
可以配置
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
myObject.a; // 2
configurable: false唯一可以修改的属性是将writable从true –> false
对象常量
var myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
} );
禁止扩展 – 禁止添加属性
var myObject = {
a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
密封 – 2+configurable:false – 禁止添加新属性禁止配置属性可以修改属性的值
Object.seal(..)
冻结 – 3 + writable:false
Object.freeze(..)
get
set
Object.defineProperty(..)
不会触发set
当你给一个属性定义 getter、 setter 或者两者都有时, 这个属性会被定义为“访问描述符”(和“数据描述符” 相对).对于访问描述符来说, JavaScript 会忽略它们的 value 和 writable 特性, 取而代之的是关心 set 和 get(还有 configurable 和 enumerable) 特性。
var myObject = {
// 给 a 定义一个 getter
get a() {
return 2;
}
// 给 a 定义一个 setter
set a(val) {
this._a_ = val * 2;
}
};
Object.defineProperty(
myObject, // 目标对象
"b", // 属性名
{ // 描述符
// 给 b 设置一个 getter
get: function(){ return this.a * 2 },
// 确保 b 会出现在对象的属性列表中
enumerable: true
}
);
myObject.a; // 2
myObject.b; // 4
in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中
hasOwnProperty(..)
只会检查属性是否在 myObject 对象中 ,不会检查原型链
Object.prototype.hasOwnProperty.call(myObject,"a")
Object.keys(..)
会返回一个数组, 包含所有可枚举属性
Object.getOwnPropertyNames(..)
, 无论它们是否可枚举
Foo.prototype
– 公有并且不可枚举 - 在new F00()的时候关联而不是创建.
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 创建一个新原型对象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
手动添加constructor
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 创建一个新原型对象
// 需要在 Foo.prototype 上“修复” 丢失的 .constructor 属性
// 新对象属性起到 Foo.prototype 的作用
// 关于 defineProperty(..), 参见第 3 章
Object.defineProperty( Foo.prototype, "constructor" , {
enumerable: false,
writable: true,
configurable: true,
value: Foo // 让 .constructor 指向 Foo
} );
Foo.prototype.constructor === Foo;
– 共有可写不可枚举
对象(foo)也有一个constructor 指向Foo.prototype.constructor
. – 实际上是委托
Object.create(..)
– 创建一个新对象并把新对象内部的prototype关联到你指定的对象
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 对象并关联到 Foo.prototype
// Bar.prototype = Foo.prototype不合理 -- 直接引用会影响到父类
// Bar.prototype = new Foo() -- 会直接调用父类构造方法,不科学
//ES5 如下 继承方法不继承属性
Bar.prototype = Object.create( Foo.prototype );
// 注意! 现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
//ES6 和 ES5功能完全相同
// Object.setPrototypeOf( Bar.prototype, Foo.prototype );
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a
a instance of Foo
– a的原型链中是否存在Foo.prototype对象Foo.prototype.isPrototypeOf( a ); // true
和上面相同Object.isPrototypeOf( a )
b.isPrototypeOf( c );
Object.getPrototypeOf( a ) === Foo.prototype; // true
a.__proto__ === Foo.prototype; // true
– a.__proto__
存在于Object.prototypeTask = {
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);
};
// ABC = Object.create( Task );
// ABC ... = ...
原型继承基于类 – 显性操作prototype对象.
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");
b1.speak();
b2.speak();
委托基于对象 – 直接创建对象,并关联对象,方法都和对象绑定而不是prototype
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");
b1.speak();
b2.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 = $(").text(this.label);
}
// 让 Button“继承” Widget
Button.prototype = Object.create(Widget.prototype);
// 重写 render(..)
Button.prototype.render = function($where) {
//“super” 调用
Widget.prototype.render.call(this, $where);
this.$elem.click(this.onClick.bind(this));
};
Button.prototype.onClick = function(evt) {
console.log("Button '" + this.label + "' clicked!");
};
$(document).ready(function() {
var $body = $(document.body);
var btn1 = new Button(125, 30, "Hello");
var btn2 = new Button(150, 40, "World");
btn1.render($body);
btn2.render($body);
});
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 = $( " ).text( this.label );
}
render($where) {
super( $where );
this.$elem.click( this.onClick.bind( this ) );
}
onClick(evt) {
console.log( "Button '" + this.label + "' clicked!" );
}
}
$( document ).ready( function(){
var $body = $( document.body );
var btn1 = new Button( 125, 30, "Hello" );
var btn2 = new Button( 150, 40, "World" );
btn1.render( $body );
btn2.render( $body );
} );
var Widget = {
init: function(width, height) {
this.width = width || 50;
this.height = 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);
Button.setup = function(width, height, label) {
// 委托调用
this.init(width, height);
this.label = label || "Default";
this.$elem = $(").text(this.label);
};
Button.build = function($where) {
// 委托调用
this.insert($where);
this.$elem.click(this.onClick.bind(this));
};
Button.onClick = function(evt) {
console.log("Button '" + this.label + "' clicked!");
};
$(document).ready(function() {
var $body = $(document.body);
var btn1 = Object.create(Button);
btn1.setup(125, 30, "Hello");
var btn2 = Object.create(Button);
btn2.setup(150, 40, "World");
btn1.build($body);
btn2.build($body);
});
没有key了.
var AuthController = {
errors: [],
checkAuth() {
// ...
},
server(url,data) {
// ...
}
// ...
};
但是也有问题.
var Foo = {
bar() { /*..*/ },
baz: function baz() { /*..*/ }
};
去掉语法糖之后变成
var Foo = {
bar: function() { /*..*/ }, // 匿名函数
baz: function baz() { /*..*/ }
};
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
console.log( v );
}
// 1
// 2
// 3
var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { done:true }
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)
};
}
};
}
} );
// 手动遍历 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefned, done:true }
// 用 for..of 遍历 myObject
for (var v of myObject) {
console.log(v);
}
// 2
// 3
陷阱太多,不一一列举了.