diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts
index d60842db450..31300410272 100644
--- a/packages/runtime-core/__tests__/apiOptions.spec.ts
+++ b/packages/runtime-core/__tests__/apiOptions.spec.ts
@@ -8,7 +8,8 @@ import {
nextTick,
renderToString,
ref,
- defineComponent
+ defineComponent,
+ createApp
} from '@vue/runtime-test'
describe('api: options', () => {
@@ -105,6 +106,24 @@ describe('api: options', () => {
expect(serializeInner(root)).toBe(`
2
`)
})
+ test('component’s own methods have higher priority than global properties', async () => {
+ const app = createApp({
+ methods: {
+ foo() {
+ return 'foo'
+ }
+ },
+ render() {
+ return this.foo()
+ }
+ })
+ app.config.globalProperties.foo = () => 'bar'
+
+ const root = nodeOps.createElement('div')
+ app.mount(root)
+ expect(serializeInner(root)).toBe(`foo`)
+ })
+
test('watch', async () => {
function returnThis(this: any) {
return this
diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts
index 2017b98f6af..c836f125493 100644
--- a/packages/runtime-core/src/componentOptions.ts
+++ b/packages/runtime-core/src/componentOptions.ts
@@ -604,7 +604,17 @@ export function applyOptions(
for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) {
- ctx[key] = methodHandler.bind(publicThis)
+ // In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
+ // and those are read-only but reconfigurable, so it needs to be redefined here
+ if (__DEV__) {
+ Object.defineProperty(ctx, key, {
+ value: methodHandler.bind(publicThis),
+ configurable: true,
+ enumerable: false
+ })
+ } else {
+ ctx[key] = methodHandler.bind(publicThis)
+ }
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.METHODS, key)
}