该书不是全面的讲解,这本书可以作为扫过基础知识后的提升和补充.
作者站在js原生语言的角度(而不是站在js营销的角度,营销使得js扭曲本身的含义去迎合其他语言的理解和使用习惯)去从新定义概念和语义化js。
作者的思路是从问题出发,比如为什么存在这样的问题,又或者是为什么会这样的问题,
进而一步步引导读者去解开谜底,中间经过演变和推论,最后列出结论和推荐.
书本里面的例子代码很有趣.
有经验的读者会有所提升,纠正一些认知,比如对象是没有方法的,原来称呼的方法和函数是不同的概念.对象是有函数的,但没有一个函数是属于对象的,因为对象拥有的都是函数的引用等等这些概念的问题
没有经验的读者稍微会吃力一些,有些部分在讲解基础知识点的时候穿插了其他一些中高级知识点进入,比如设计模式,或者柯里化等
全面讲解意味着深长,而这本书里面的知识点比较犀利简短,有时候为了尽量简单的去讲,有意无意的带有知识点的跳跃性,比如函数运行时的运行时上下文,活动对象等.
所以应该可以这么说,作者是从全面知识体系上一步步纠正以往的代码误解,揭开常见的陷阱,解答模糊不清以及平时不解又不会深入去探讨的原理性问题,更正一些语言的营销概念等等.
LHS: 赋值操作的目标是谁,赋值
RHS: 谁是赋值操作的源头,查找值
严格模式:禁隐性或自动创建全局变量,顶级变量为undefined。
错误类型
foo() // 1
function foo() {
console.log(1)
}
var foo = function(){
console.log(2)
}
foo() // 报错,foo重复定义。
function foo() {
console.log(1)
}
let foo = function(){
console.log(2)
}
另一个问题,运行时定义函数。该函数不能提升。
foo() // foo is not a function
let a = true
if (a) {
function foo() {
console.log(1)
}
} else {
function foo() {
console.log(2)
}
}
var moduleClass = (function Manager(){
var modules = {}
function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]] // 查找模块组中的模块装入依赖组中。如果不存在则表示无依赖,比如第一次定义时。
}
modules[name] = impl.apply(impl, deps) // 将依赖组当作参数传入回调函数,并执行impl后返回一个功能函数(即return部分)在模块组中和模块名绑定。
}
function get(name) {
return modules[name]
}
return {
define,
get
}
})()
moduleClass.define('bar', [], function(){
function hello(w) {
console.log(w)
}
return {
hello
}
})
moduleClass.define('foo', ['bar'], function(bar){
let w = 'world'
function say() {
bar.hello(w)
}
return {
say
}
})
let b = moduleClass.get('bar')
let f = moduleClass.get('foo')
b.hello('hjj')
f.say()
非自身
为什么使用this
function getName(name) {
return this.name
}
function say(name) {
console.log('hello ' + getName(name))
}
let hjj = {
name: 'hjj'
}
let zcf = {
name: 'tcl'
}
say(hjj)
say(tcl)
使用this
function getName(name) {
return this.name
}
function say(name) {
console.log('hello ' + getName.call(this))
}
let hjj = {
name: 'hjj'
}
let tcl = {
name: 'tcl'
}
say.call(hjj)
say.call(tcl)
eg. this丢失 for 下函数this指向
function foo(num) {
console.log('num'+ num)
this.count++
}
foo.count = 0
for(var i = 0; i < 5; i++) {
foo(i) // this指向顶级变量
}
console.log(foo.count)
for(var i = 0; i < 5; i++) {
foo.call(foo, i)
}
console.log(foo.count)
function foo(){
console.log(this.a)
}
var a = 1
foo() // 1
顶级变量、严格模式下绑定到undefined
function foo() {
console.log(a)
}
var obj = {
a: 1,
foo
}
obj.foo() // this帮定到obj。//1
ps:链式调用指向最后一个函数
function foo() {
console.log(this.a)
}
var obj = {
a:1,
foo
}
var bar = obj.foo
var a = 2
bar() // 2 this从obj改变成指向顶级变量
eg.2
function foo() {
console.log(this.a)
}
var obj = {
a: 1
foo
}
var a = 2
function bar(fn) {
fn()
}
bar(obj.foo) // 2
function foo() {
console.log(this.a)
}
var obj = {
a: 1
}
var a = 2
foo.call(obj)
call解决的this绑定问题
function foo() {
console.log(this.a)
}
var obj = {
a:1,
foo
}
var bar = obj.foo
var a = 2
bar.call(obj) // 2 this从obj改变成指向顶级变量
eg.2 -call无法解决的this绑定问题
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo
}
var a = 2
function bar(fn) {
fn() // 因为这个位置this指向bar的调用者
}
bar.call(obj, obj.foo) // 2
引出硬绑定
eg.2 硬绑定
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo
}
var a = 2
function bar(fn) {
fn.call(obj) // 内部指向obj
}
bar(foo) // 1
// 即便在外部尝试改变this,也不会有效果,因为this在内部硬绑定了
bar.call(window, foo) // 1
使用apply优化
function foo(b) {
console.log(this.a) // 1
return this.a + b
}
var obj = {
a: 1,
foo
}
var a = 2
function bar() {
return foo.apply(obj, arguments)
}
var sum = bar(4)
console.log(sum) // 5
bind实现
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments)
}
}
var bar = bind(foo,obj) // 柯里化生成上面的bar这个具体函数
var sum = bar(4)
console.log(sum)
使用原生bind
// bind会绑定obj为上下文(即参数被设置为上下文),然后调用foo函数(即obj.foo)
var bar = foo.bind(obj) // 柯里化生成上面的bar这个具体函数。
var sum = bar(4)
console.log(sum)
原生api的this绑定
forEach(function(value,index,ary), thisValue)
function foo(id){
console.log(this.name + id)
}
var obj = {
name: 'hjj'
}
let ary = [1,2,3]
ary.forEach(foo, obj)
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a)
new绑定>显式绑定>隐式绑定>默认绑定
证明1:显示绑定和隐式绑定
function foo(){
consol.log(this.a)
}
var obj1 = {
a:1,
foo
}
var obj2 = {
a:2,
foo
}
obj1.foo() // 1
obj2.foo() // 2
obj1.foo.call(obj2) // 2
obj2.foo.call(obj1) // 1
证明2:new 绑定和隐式绑定
function foo(a) {
this.a = a
}
var obj1 = {
foo
}
obj1.foo(1) // 1
var bar = new obj1.foo(2)
bar.a // 2
obj1.a // 1
证明3:new绑定和显式绑定
function foo(a) {
this.a = a
}
var obj1 = {}
var bar = foo.bind(obj1) // bar的this指向obj1
bar(1)
console.log(obj1.a) // 1
var baz = new bar(3) // 生成新的对象,并传入参数,参数为3,绑定到this.a上。
console.log(obj1.a) // 1
console.log(baz.a) // 3
绑定例外
call、apply、bind传入null或者undefined则会被忽略。转而成为默认绑定。
function foo() {
console.log(this.a)
}
var a = 1
foo.call(null) // 1
参数柯里化
function foo(a, b) {
console.log(a + b)
}
foo.apply(null, [1,2]) // 3
var bar = foo.bind(null, 2)
bar(3) // 5
安全的this
function foo(a,b) {
return a + b
}
// 更简洁的对象`Object.create(null)`,没有property的对象,
var $ = Object.create(null) // 可读性更强,不用担心null或者undefined被修改.
foo.apply($, [2, 3]) // 5
var bar = foo.bind($, 2)
bar(3) // 5
箭头函数中this会用当前的词法作用域覆盖this本来的值,即指向外层作用域,它放弃了this绑定规则。
不推荐使用let self = this的词法作用域+闭包解决问题的写法
比如我以前的认识是:
obj[prot]
obj.prot
obj[a + ‘a’]
属性访问,没有方法,方法也是属于属性。因为函数永远不会属于一个对象。
他们只是对相同函数对象的多个引用
obj = {
a:1,
fn: function() {}
}
浅复制
深复制
死循环
JSON安全
let newObj = JSON.parse(JSON.stringify(obj))
//es6
let newObj = Object.assign(obj)
writeable: false && configurable: falselet obj = {}
Object.defineProperty(obj, 'constValue', {
value: 1,
writeable: false,
configurable: false
})
let obj = {a: 1}
Object.preventExtensions(obj)
obj.b = 2
console.log(obj.b) // undefined
let obj = {a: 1}
Object.seal(obj) // 等于 object.preventExtensions(obj) && configurable:false
obj.b = 2
console.log(obj.b) // undefined
Object.defineProperty(obj, 'a', { // 报错TypeError
value: 2,
writeable: true,
configurable: true,
enumberable: true
})
let obj = {a: 1}
Object.freeze(obj) // 等于 object.seal(obj) && writeable:false
[[Get]] -> getter
[[Put]] -> setter
vue -> computed
computed: {
a:{
set(){},
get(){}
}
}
'a' in obj // 是否存在在obj的prototype中,不管属性是否可枚, for in则只会查找可枚举的属性
obj.hasOwnProperty('a') // 是否存在在obj中
Object.prototype.hasOwnProperty.call(obj, 'a') // 能判断通过Object.create(null)后添加的属性。
let obj = {}
Object.defineProperty(obj, 'a', {
enumberable: true,
value:1
})
Object.defineProperty(obj, 'b', {
enumberable: false,
value:1
})
// 判断是否可枚举
Object.propertyIsEnumberable('a') // true 不是原型上的属性,只对象上查找
Object.propertyIsEnumberable('b') // false
Object.keys(obj) // ['a'] 只会出现可枚举属性
Object.getOwnPropertyNames(obj) // ['a', 'b'] 只对象上查找
for...of...
let a = [1,2,3]
let it = a[Symbol.iterator]() // @@iterator
it.next()
it.next()
it.next()
it.next()
自定义遍历器
let obj = {
a: 1,
b: '22'
}
Object.defineProperty(obj, Symbol.iterator, {
enumberable: false,
writeable: 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)
}
}
}
}
})
let it = obj[Symbol.iterator]()
it.next()
it.next()
it.next()
it.next()
自定义iterator需要解决两个问题,一个是value的值进行推进,一个是done什么时候为true,即终止条件是什么。
let z = 'out var'
function a() {
console.log(z)
let params = [...arguments]
let a = 1
let s = 'abc'
function print () {
console.log(params)
}
print()
return a + 1
}
a(...[1,2,3])
对象、继承、实例
多态:重写方法,可读性和健壮性低
建筑-蓝图
实例-类
构造函数
继承->复制,不是自动执行复制,而是被关联起来
多重继承(不推荐使用,存在以下问题)
js无多重继承,取而代之是mixin
几个概念:对象,构造函数,实例,继承,多重继承,多态,混入.
一般表示为:extend(),或者mixin
实际代码很简单:
function copy() {
let newObj = {}
for (let key in obj) {
if (!(key in newObj)) {
newObj = obj[key]
}
return newObj
}
}
function Vehicle() {
this.engines = 1
}
Vehicle.prototype.ignition = function() {
console.log("Turning on my engine.")
}
Vehicle.prototype.drive = function() {
this.ignition()
console.log('Steering and moving forward!')
}
function Car() {
var car = new Vehicle()
car.wheels = 4
var vehDrive = car.drive
car.drive = function(){
vehDrive.call(this)
console.log('Rolling on all' + this.wheels +' wheels!')
}
return car
}
var myCar = Car()
myCar.drive()
let a = {
init: function(){
this.msg = 'hello'
this.count = this.count? this.count + 1: 1
}
}
let b = {
init: function() {
a.dd.call(this)
}
}
a.init()
a.msg // hello
a.count // 1
b.init()
b.msg // hello
b.count // 1
count数据不会共享,b引用了a的函数,并将属性绑定到自身,从而隐式的继承了属性。
只有第一种情况会被屏蔽
let obj = {
a: 1
}
let o = Object.create(obj)
o.a
obj.a
obj.hasOwnProperty('a')
o.hasOwnProperty('a')
o.a++
obj.a
o.a
obj.hasOwnProperty('a')
o.hasOwnProperty('a')
把原型继承称为委托
作者的观点是:new操作时,函数调用会变成’构造函数的调用’
new Obj()字面上就不成立了,因为实例此时不是由Obj这个对象生成的,而是原型链上查找到的最近一个构造函数生成的.function Foo(name) {
this.name = name
}
Foo.prototype.getName = function() {
return this.name
}
function Bar(name, label) {
Foo.call(this, name)
this.label = label
}
// Bar.prototype = Foo.prototype // 这种写法会在修改Bar的同时同步到Foo,这不如直接修改foo.
// Bar.prototype = new Foo() // 可能会在实例化时生成其他属性或者方法,然后带到后代Bar身上.
// 以前的写法
// Bar.__proto__ = Foo.prototype // 由的浏览器不兼容
// es6前的写法
// Bar.prototype = Object.create(Foo.prototype) // 生成一个新的对象并抛弃掉原有的Foo
// es6后的写法
Object.setPrototypeOf(Bar.prototype, Foo.prototype) // 直接修改
Bar.prototype.getLabel = function() {
return this.label
}
var a = new Bar('a', 'obj a')
a.getName()
a.getLabel()
内省/反射:检查实例和原函数是否存在委托关联(继承关系)
类是否生成过该实例(站在类的角度去理解)
a instanceof Foo
在a的整条[[prototype]]中是否出现过Foo.prototype对象(站在实例的角度去理解.更加语义化和便于理解)
Foo.prototype.isPrototypeOf(a)
两个对象是否通过[[prototype]]链关联
b.isPrototypeOf(a)
Object.getPrototypeOf(a) === Foo.prototype
对象关联, 原型链
function Foo() {
this.a = 1
}
var a = Object.create(Foo)
// VS
var a = new Foo()
var a = Object.create(Foo)会创建一个新对象并把它关联到 Foo 上
__proto__ 指向 Foovar a = new Foo会创建一个新对象并把它关联到 Foo 上
__proto__ 指向 Foo.prototypelet a = Object.create(null) // 空[[prototype]]的对象
Object.create()的实现
if (!Object.create) {
Object.create = function(o) {
function F() {}
F.prototype = o
return new F()
}
}
let Foo = {
a:1,
getA: function(){
console.log(this.a)
}
}
var a = Object.create(Foo)
a.getA()
var a = Object.create(Foo) // 原型链备用的设计模式,不推荐,推荐使用proxy代理来处理方法找不到时的行为
以上的写法会导致理解困难,作者的想法是,该对象中不存在的,不应该去原型链上查找。
推荐的写法的使用委托的设计模式,目的是为了让api更加清晰。
let Foo = {
a:1,
getA: function(){
console.log(this.a)
}
}
var a = Object.create(Foo)
a.getMyA = function(){
this.getA() // 内部委托
}
a.getMyA()