new & this & prototype

Clear

Clear

new 关键字深入, this绑定规则, 继承

1.new

new的作用

一般面向类编程语言new是调用类的构造方法、创建类的实例

something = new Class(...);

所以在JavaScript中new的作用是一样吗? JavaScript中new的机制实际上和面向类的语言完全不同

首先我们重新定义一下JavaScript中的 "构造函数"。在JavaScript中、构造函数只是一些使用new操作符时,被调用的函数

它们并不会属于某个类,也不会实例化一个类,它们只是被new操作符调用的普通函数而已

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作

  1. 创建(或者说构造)一个全新的对象
  2. 这个新对象会被执行[[原型]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象, 那么new表达式中的函数调用会自动返回这个新对象

以上摘抄自《你不知道的JavaScript》, 所以总结来说

new就是创建对象,连接原型,并绑定this
function foo() {
console.log(this)
this.a = 1
}
let f = new foo()
foo()

new执行它则被称为一个自定义的类,会默认创建一个对象,而这个对象就是当前类的实例 其this指向这个新创建的实例

模拟new的实现

function newObj() {
let obj = {}
let con = [].shift.call(arguments)
obj.__proto__ = con.prototype
let result = con.apply(obj, arguments) // 绑定this
return typeof result === 'object' ? result : obj
}

不建议通过__proto__修改对象的属性[[Protoytpe]]属性

function newObj(Func, ...args) {
let obj = Object.create(Func.prototype)
let result = Func.call(obj, ...args) // 绑定this
if ((result != null && typeof result === 'object') || typeof result === 'function') {
return result
}
return obj
}

测试demo

function Dog(name) {
this.name = name
}
Dog.prototype.bark = function() {
console.log('wangwang')
}
Dog.prototype.sayName = function() {
console.log('my name is ' + this.name)
}
function _new() {
let o = arguments[0].prototype
arguments[0].apply(o, [].slice.call(arguments, 1))
return o
}
let d = _new(Dog, 'John')
d.bark()
d.sayName()

参考链接

字节:模拟实现 new 操作符 #71

2.this

this的绑定机制

this是在运行时进行绑定的,并不是在编写时, 它指向什么完全取决于函数在哪里被调用

当一个函数被调用时,会创建一个活动记录(执行上下文), 这个记录会包含函数在哪里被调用(调用栈), 函数的调用方法,传入的参数等信息。this就是记录的其中一个属性

this的绑定规则

1.默认绑定

非严格模式(non-strict mode) this指向全局对象
严格模式(strict mode)全局对象将无法使用默认绑定, this会绑定到undefined

2.隐式绑定

当函数中有上下文对象时, 隐式规则会把函数调用中的this绑定到这个上下文对象

function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是 foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"

函数传递其实就是一种隐式绑定,因此我们传入函数时也会被隐式赋值

setTimeout(obj.foo, 100)

就像我们看到的那样, 回调函数丢失this绑定是非常常见的

在分析隐式绑定时,我们必须在一个对象的内部,包含一个指向函数的属性
并通过这个属性,间接引用函数, 从而把this(隐式)绑定到这个对象

3.显示绑定

可以使用函数的 call(..) 和 apply(..) 方法

4.new绑定

优先级

  1. 函数是在new中调用(new 绑定),this绑定的是新创建的对象 var bar = new foo()

  2. 函数通过call, apply(显示绑定)或者硬绑定, this绑定的是指定的对象 var bar = foo.call(obj)

  3. 函数在某个上下文对象中调用(隐式绑定), this绑定的是那个上下文对象 var bar = obj.foo()

  4. 如果上门三种都不是, 使用默认绑定, 如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。 var bar = foo()

例外情况(可能要从ECMAScript规范中找到答案吧)

如果把null或者undefined作为this的绑定对象传入call, apply, bind, 这些值在调用时会被忽略

function foo() {
console.log(this.a)
}
var a = 2
foo.call(null)

this词法、箭头函数

ES6中的arrow function不适用于以上规则, 而是根据外层作用域来决定this

function foo() {
// 返回一个箭头函数
console.log(this.a)
return () => {
//this 继承自 foo()
console.log( this.a );
};
}
var obj1 = { a:2 };
var obj2 = { a:3 };
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

最后在来总结一下😳

由new调用? 绑定到新创建的对象
由call或者apply(bind)调用? 绑定到指定的对象
由上下文调用? 绑定到该上下文
默认: 严格模式下绑定到undefined, 否则绑定到全局对象

Prototype

function Foo() {
}
Foo.prototype

JavaScript 会在两个对象之间创建一个关联,这样其中一个对象就可以通过委托访问另一个对象的属性和函数

function NotingSpecial() {
console.log("Don't mind me")
}
var a = new NotingSpecial()

NotingSpecial只是一个普通的函数,但是当使用new调用时,它就会构造一个对象并赋值给a

这看起来像是new的一个副作用,但是NotingSpecial本身并不是一个构造函数

换句话说, 在JavaScript中对于"构造函数"最准确的解释是,所有带new的函数调用

函数不是构造函数,但是当使用new时, 函数调用会变成"构造函数调用" 函数原型

ES6 语法

ES6中, 我们可以在任意对象的字面形式中使用简洁方法声明(concise method declatation)

var loginController = {
errors: [],
getUser () {
},
getPassword () {
}
}

字面形式与class区别:字面形式使用, 来分割元素

class陷阱: class 并不会像传统面向类的语言一样在声明时静态复制所有的行为. 如果你修改或者替换父类的一个方法,那么子类和所有实例都会受到影响 因为它们在定义时并没有进行复制,只是基于prototype的委托

Object

语法元素含义
for ... in列举对象成员名
for ... of列举数组成员值