上卷
显示绑定:
JavaScript 中的“所有”函数都有一些有用的特性(这和它们的 [[Prototype]] 有关——之后我们会详细介绍原型),可以用来解决这个问题。具体点说,可以使用函数的 call(..) 和apply(..) 方法。严格来说,JavaScript 的宿主环境有时会提供一些非常特殊的函数,它们并没有这两个方法。但是这样的函数非常罕见,JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用 call(..) 和 apply(..) 方法。
这两个方法是如何工作的呢?它们的第一个参数是一个对象,是给 this 准备的,接着在调用函数时将其绑定到 this。因为你可以直接指定 this 的绑定对象,因此我们称之为显式绑定。
思考下面的代码:
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2
通过 foo.call(..),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。
用call来实现继承
var test = { name: 'He', age: 25 }
var test2 = { write: function () { console.log(this.name) }, writeAge: function () { console.log(this.age) } }
test2.write.call(test) //He test2.writeAge.call(test) //25 test2.write(); //undefined test2.writeAge(); //undefined
判断this
现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:
1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
var bar = new foo()
2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。
var bar = foo.call(obj2)
3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。
var bar = obj1.foo()
4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
var bar = foo()
存在性
var myobject={ //... } myobject.a //undefined
访问myobject对象的a属性,返回的值为 undefined,但是这个值有可能是属性中存储的 undefined,也可能是属性不存在返回的 undefined,那么如何区分这两种情况呢?
我们可以在不访问属性值的情况下判断对象中是否存在这个属性:
var myObject = { a:2 };
("a" in myObject); // true ("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true myObject.hasOwnProperty( "b" ); // false
in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中,hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。
看起来in像是检测容器内是否存在某个值,但是实际上检查的是某个属性名是否存在。
4 in [2,4,6] //false
数组[2,4,6]的属性名是0,1,2 因为检测的是属性名, 所以会输出false
//// 数组有内置的 @@iterator,因此 for..of 可以直接应用在数组上。我们使用内置的 @@iterator 来手动遍历数组,看看它是怎么工作的 var myArr = [1, 2, 3]; var it = myArr[Symbol.iterator](); it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.next(); // { value:3, done:false } it.next(); // { done:true }
如你所见,调用迭代器的 next() 方法会返回形式为 { value: .. , done: .. } 的值,value 是当前的遍历值,done 是一个布尔值,表示是否还有可以遍历的值。
注意,和值“3”一起返回的是 done:false,乍一看好像很奇怪,你必须再调用一次next() 才能得到 done:true,从而确定完成遍历。这个机制和 ES6 中发生器函数的语义相关,不过已经超出了我们的讨论范围。
和数组不同,普通的对象没有内置的 @@iterator,所以无法自动完成 for..of 遍历。之所以要这样做,有许多非常复杂的原因,不过简单来说,这样做是为了避免影响未来的对象类型。
当然,你可以给任何想遍历的对象定义 @@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) }; } }; } } ); // 手动遍历 myObject var it = myObject[Symbol.iterator](); it.next(); // { value:2, done:false } it.next(); // { value:3, done:false } it.next(); // { value:undefined, done:true } // 用 for..of 遍历 myObject for (var v of myObject) { console.log( v ); } // 2 // 3
我们使用 Object.defineProperty(..) 定义了我们自己的 @@iterator(主要是为了让它不可枚举),不过注意,我们把符号当作可计算属性名(本章之前有介绍)。此外,也可以直接在定义对象时进行声明,比如 var myObject = { a:2, b:3, [Symbol.iterator]: function() { /* .. */ } }。
for..of 循环每次调用 myObject 迭代器对象的 next() 方法时,内部的指针都会向前移动并返回对象属性列表的下一个值(再次提醒,需要注意遍历对象属性 / 值时的顺序)。
对象章--小结
1、对象就是键 / 值对的集合。可以通过 .propName 或者 ["propName"] 语法来获取属性值。访问属性时,引擎实际上会调用内部的默认 [[Get]] 操作(在设置属性值时是 [[Put]]),[[Get]] 操作会检查对象本身是否包含这个属性,如果没找到的话还会查找 [[Prototype]]链(参见第 5 章)。
2、属性的特性可以通过属性描述符来控制,比如 writable 和 configurable。此外,可以使用Object.preventExtensions(..)、Object.seal(..) 和 Object.freeze(..) 来设置对象(及其属性)的不可变性级别
3、属性不一定包含值——它们可能是具备 getter/setter 的“访问描述符”。此外,属性可以是可枚举或者不可枚举的,这决定了它们是否会出现在 for..in 循环中。
4、你可以使用 ES6 的 for..of 语法来遍历数据结构(数组、对象,等等)中的值,for..of会寻找内置或者自定义的 @@iterator 对象并调用它的 next() 方法来遍历数据值。
对象关联
var foo2 = { name: 'He', sayName: function () { console.log(this.name) } } var foo2_n = Object.create(foo2); foo2_n.sayName();
委托模式
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,'委托')
对象委托关联
Foo = { init: function (who) { this.me = who }, identify: function () { return 'I am ' + this.me + '.' } } Bar = Object.create(Foo); Bar.speak = function () { console.log("Hello, " + this.identify()) } var a1 = Object.create(Bar); a1.init('A1'); var b1 = Object.create(Bar); b1.init('B1'); a1.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({ this.width + 'px', height: this.height + 'px' }).appendTo($where); } } //子类 function Button(width, height, label) { widget.call(this, width, height); this.label = label || "default"; this.$elem = $('<button>').text(this.label); } // 让Button继承widget Button.prototype = Object.create(widget); // 重写render方法 Button.prototype.render = function ($where) { widget.prototype.render.call(this, $where); this.$elem.click(this.onClick.bind(this)); } Button.prototype.onClick = function (evt) { console.log("Button " + this.label + " clicked!") } $(function () { var $body = $(document.body); var button1 = new Button(70, 70, '按钮1'); var button2 = new Button(90, 90, '按钮2'); button1.render($body); button2.render($body); })
使用对象关联风格委托来更简单地实现 Widget/Button
var widget2={ init:function(width,height){ this.width=width||50; this.height=height||50; this.$elm=null; }, insert:function($where){ if(this.$elm){ this.$elm.css({ this.width+'px', height:this.height+'px', marginLeft:'30px' }).appendTo($where) } } } var Button2=Object.create(widget2); Button2.setup=function(width,height,label){ this.init(width,height); this.label=label||"default"; this.$elm=$("<button>").text(this.label); } Button2.build=function($where){ this.insert($where); this.$elm.click(this.onClick.bind(this)); } Button2.onClick=function(){ console.log("Button "+this.label+" clicked"); } $(document).ready(function(){ var body=$(document.body); var button1=Object.create(Button2); var button2=Object.create(Button2); button1.setup(80,80,'取消') button1.build(body) button2.setup(80,80,'确定!'); button2.build(body); })
中卷
ES5 规范 9.2 节中定义了抽象操作 ToBoolean,列举了布尔强制类型转换所有可能出现的
结果。
以下这些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• ""
假值的布尔强制类型转换结果为 false。
一元运算符 ! 显式地将值强制类型转换为布尔值。但是它同时还将真值反转为假值(或者将假值反转为真值)。所以显式强制类型转换为布尔值最常用的方法是 !!,因为第二个 ! 会将结果反转回原值
var a = "0"; var b = []; var c = {}; var d = ""; var e = 0; var f = null; var g; !!a; // true !!b; // true !!c; // true !!d; // false !!e; // false !!f; // false !!g; // false
优先级
var a = 42; var b = "foo"; var c = false; var d = a && b || c ? c || b ? a : c && b : a; d; // 42
运算符优先级 && > || > ? :
利用优先级将代码分解:
((a && b) || c) ? ((c || b) ? a : (c && b)) : a
现在来逐一执行
(1) (a && b) 结果为 "foo"。
(2) "foo" || c 结果为 "foo"。
(3) 第一个 ? 中,"foo" 为真值。
(4) (c || b) 结果为 "foo"。
(5) 第二个 ? 中,"foo" 为真值。
(6) a 的值为 42。
因此,最后结果为 42。
js类型检测:https://www.imooc.com/video/5677
typeof 适合基本类型和函数对象的判断 不适用array等特殊类型
typeof 100;//number typeof true;//boolean typeof function(){};//function typeof (undefined);//undefined typeof new Object;//Object typeof [1,2];//Object typeof NaN;//number typeof null;//Object
instanceof 常用于判断对象类型,它是基于原型链去判断的操作符
(obj instanceof Object)
instanceof它希望左操作数(obj)是一个对象,如果不是对象,是基本数据类型(比如 string,number),直接返回false。
它希望右操作数必须是一个函数对象或者函数构造器,如果不是会抛出异常Type Error。
instanceof大概原理:判断左操作数对象的原型链上是否有右构造函数的prototype属性
[1,2] instanceof Array;//true new Object() instanceof Array; //false